diff --git a/.github/ci/build-stable.sh b/.github/ci/build-stable.sh new file mode 100755 index 000000000..0dadd6102 --- /dev/null +++ b/.github/ci/build-stable.sh @@ -0,0 +1,16 @@ +#!/bin/bash +## on push branch~=gh-readonly-queue/main/.* +## on pull_request + +set -euo pipefail + +export RUSTUP_HOME=/ci/cache/rustup +export CARGO_HOME=/ci/cache/cargo +export CARGO_TARGET_DIR=/ci/cache/target + +hashtime restore /ci/cache/filetime.json || true +hashtime save /ci/cache/filetime.json + +sed -i 's/channel.*/channel = "stable"/g' rust-toolchain.toml + +./ci_stable.sh diff --git a/.github/ci/build.sh b/.github/ci/build.sh new file mode 100755 index 000000000..30ca1e6f0 --- /dev/null +++ b/.github/ci/build.sh @@ -0,0 +1,19 @@ +#!/bin/bash +## on push branch~=gh-readonly-queue/main/.* +## on pull_request + +set -euo pipefail + +export RUSTUP_HOME=/ci/cache/rustup +export CARGO_HOME=/ci/cache/cargo +export CARGO_TARGET_DIR=/ci/cache/target +if [ -f /ci/secrets/teleprobe-token.txt ]; then + echo Got teleprobe token! + export TELEPROBE_HOST=https://teleprobe.embassy.dev + export TELEPROBE_TOKEN=$(cat /ci/secrets/teleprobe-token.txt) +fi + +hashtime restore /ci/cache/filetime.json || true +hashtime save /ci/cache/filetime.json + +./ci.sh diff --git a/.github/ci/doc.sh b/.github/ci/doc.sh new file mode 100755 index 000000000..9e9c78a42 --- /dev/null +++ b/.github/ci/doc.sh @@ -0,0 +1,45 @@ +#!/bin/bash +## on push branch=main + +set -euo pipefail + +export RUSTUP_HOME=/ci/cache/rustup +export CARGO_HOME=/ci/cache/cargo +export CARGO_TARGET_DIR=/ci/cache/target +export BUILDER_THREADS=4 +export BUILDER_COMPRESS=true + +# force rustup to download the toolchain before starting building. +# Otherwise, the docs builder is running multiple instances of cargo rustdoc concurrently. +# They all see the toolchain is not installed and try to install it in parallel +# which makes rustup very sad +rustc --version > /dev/null + +docserver-builder -i ./embassy-stm32 -o webroot/crates/embassy-stm32/git.zup +docserver-builder -i ./embassy-boot/boot -o webroot/crates/embassy-boot/git.zup +docserver-builder -i ./embassy-boot/nrf -o webroot/crates/embassy-boot-nrf/git.zup +docserver-builder -i ./embassy-boot/rp -o webroot/crates/embassy-boot-rp/git.zup +docserver-builder -i ./embassy-boot/stm32 -o webroot/crates/embassy-boot-stm32/git.zup +docserver-builder -i ./embassy-embedded-hal -o webroot/crates/embassy-embedded-hal/git.zup +docserver-builder -i ./embassy-executor -o webroot/crates/embassy-executor/git.zup +docserver-builder -i ./embassy-futures -o webroot/crates/embassy-futures/git.zup +docserver-builder -i ./embassy-lora -o webroot/crates/embassy-lora/git.zup +docserver-builder -i ./embassy-net -o webroot/crates/embassy-net/git.zup +docserver-builder -i ./embassy-net-driver -o webroot/crates/embassy-net-driver/git.zup +docserver-builder -i ./embassy-net-driver-channel -o webroot/crates/embassy-net-driver-channel/git.zup +docserver-builder -i ./embassy-nrf -o webroot/crates/embassy-nrf/git.zup +docserver-builder -i ./embassy-rp -o webroot/crates/embassy-rp/git.zup +docserver-builder -i ./embassy-sync -o webroot/crates/embassy-sync/git.zup +docserver-builder -i ./embassy-time -o webroot/crates/embassy-time/git.zup +docserver-builder -i ./embassy-usb -o webroot/crates/embassy-usb/git.zup +docserver-builder -i ./embassy-usb-driver -o webroot/crates/embassy-usb-driver/git.zup +docserver-builder -i ./embassy-usb-logger -o webroot/crates/embassy-usb-logger/git.zup +docserver-builder -i ./cyw43 -o webroot/crates/cyw43/git.zup +docserver-builder -i ./cyw43-pio -o webroot/crates/cyw43-pio/git.zup +docserver-builder -i ./embassy-net-w5500 -o webroot/crates/embassy-net-w5500/git.zup +docserver-builder -i ./embassy-stm32-wpan -o webroot/crates/embassy-stm32-wpan/git.zup --output-static webroot/static + +export KUBECONFIG=/ci/secrets/kubeconfig.yml +POD=$(kubectl -n embassy get po -l app=docserver -o jsonpath={.items[0].metadata.name}) +kubectl cp webroot/crates $POD:/data +kubectl cp webroot/static $POD:/data \ No newline at end of file diff --git a/.github/ci/test.sh b/.github/ci/test.sh new file mode 100755 index 000000000..d014e4bd7 --- /dev/null +++ b/.github/ci/test.sh @@ -0,0 +1,30 @@ +#!/bin/bash +## on push branch~=gh-readonly-queue/main/.* +## on pull_request + +set -euo pipefail + +export RUSTUP_HOME=/ci/cache/rustup +export CARGO_HOME=/ci/cache/cargo +export CARGO_TARGET_DIR=/ci/cache/target + +hashtime restore /ci/cache/filetime.json || true +hashtime save /ci/cache/filetime.json + +cargo test --manifest-path ./embassy-sync/Cargo.toml +cargo test --manifest-path ./embassy-embedded-hal/Cargo.toml +cargo test --manifest-path ./embassy-hal-common/Cargo.toml +cargo test --manifest-path ./embassy-time/Cargo.toml --features generic-queue + +cargo test --manifest-path ./embassy-boot/boot/Cargo.toml +cargo test --manifest-path ./embassy-boot/boot/Cargo.toml --features nightly +cargo test --manifest-path ./embassy-boot/boot/Cargo.toml --features nightly,ed25519-dalek +cargo test --manifest-path ./embassy-boot/boot/Cargo.toml --features nightly,ed25519-salty + +cargo test --manifest-path ./embassy-nrf/Cargo.toml --no-default-features --features nightly,nrf52840,time-driver-rtc1,gpiote + +cargo test --manifest-path ./embassy-rp/Cargo.toml --no-default-features --features nightly,time-driver + +cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features nightly,stm32f429vg,exti,time-driver-any,exti +cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features nightly,stm32f732ze,exti,time-driver-any,exti +cargo test --manifest-path ./embassy-stm32/Cargo.toml --no-default-features --features nightly,stm32f769ni,exti,time-driver-any,exti diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml deleted file mode 100644 index d2e8e316b..000000000 --- a/.github/workflows/rust.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: Rust - -on: - push: - branches: [staging, trying, master] - pull_request: - branches: [master] - -env: - CARGO_TERM_COLOR: always - -jobs: - all: - runs-on: ubuntu-20.04 - needs: [build-nightly, build-stable, test] - steps: - - name: Done - run: exit 0 - build-nightly: - runs-on: ubuntu-latest - permissions: - id-token: write - contents: read - steps: - - uses: actions/checkout@v2 - with: - submodules: true - - name: Cache multiple paths - uses: actions/cache@v2 - with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target_ci - key: rust3-${{ runner.os }}-${{ hashFiles('rust-toolchain.toml') }} - - name: build - run: | - curl -L -o /usr/local/bin/cargo-batch https://github.com/embassy-rs/cargo-batch/releases/download/batch-0.3.0/cargo-batch - chmod +x /usr/local/bin/cargo-batch - ./ci.sh - rm -rf target_ci/*{,/release}/{build,deps,.fingerprint}/{lib,}{embassy,stm32}* - build-stable: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - submodules: true - - name: Cache multiple paths - uses: actions/cache@v2 - with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - target_ci_stable - key: rust-stable-${{ runner.os }}-${{ hashFiles('rust-toolchain.toml') }} - - name: build - run: | - curl -L -o /usr/local/bin/cargo-batch https://github.com/embassy-rs/cargo-batch/releases/download/batch-0.3.0/cargo-batch - chmod +x /usr/local/bin/cargo-batch - ./ci_stable.sh - rm -rf target_ci_stable/*{,/release}/{build,deps,.fingerprint}/{lib,}{embassy,stm32}* - - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Test - run: cd embassy-sync && cargo test diff --git a/.gitignore b/.gitignore index 144dd703f..1c221e876 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,4 @@ target_ci_stable Cargo.lock third_party /Cargo.toml -stm32-metapac-gen/out/ -stm32-metapac-backup -stm32-metapac/src/chips -stm32-metapac/src/peripherals out/ diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 8157e36ef..000000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "stm32-data"] - path = stm32-data - url = https://github.com/embassy-rs/stm32-data.git diff --git a/.vscode/.gitignore b/.vscode/.gitignore new file mode 100644 index 000000000..8c3dd8a31 --- /dev/null +++ b/.vscode/.gitignore @@ -0,0 +1,4 @@ +*.cortex-debug.*.json +launch.json +tasks.json +*.cfg diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 000000000..a8bb78adb --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,11 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. + // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp + // List of extensions which should be recommended for users of this workspace. + "recommendations": [ + "rust-lang.rust-analyzer", + "tamasfe.even-better-toml", + ], + // List of extensions recommended by VS Code that should not be recommended for users of this workspace. + "unwantedRecommendations": [] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 5e9e51799..725fb69d0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,44 +1,43 @@ { "editor.formatOnSave": true, - "rust-analyzer.checkOnSave.allTargets": false, - "rust-analyzer.checkOnSave.noDefaultFeatures": true, + "[toml]": { + "editor.formatOnSave": false + }, + "rust-analyzer.check.allTargets": false, + "rust-analyzer.check.noDefaultFeatures": true, "rust-analyzer.cargo.noDefaultFeatures": true, - "rust-analyzer.procMacro.enable": true, - "rust-analyzer.cargo.target": "thumbv7em-none-eabi", + "rust-analyzer.cargo.target": "thumbv7m-none-eabi", + //"rust-analyzer.cargo.target": "thumbv8m.main-none-eabihf", "rust-analyzer.cargo.features": [ - // These are needed to prevent embassy-net from failing to build - //"embassy-net/medium-ethernet", - //"embassy-net/tcp", - //"embassy-net/pool-16", - //"time-tick-16mhz", - //"defmt-timestamp-uptime", - "nightly", - //"unstable-traits", + ///"nightly", ], "rust-analyzer.linkedProjects": [ // Declare for the target you wish to develop - //"embassy-executor/Cargo.toml", - //"embassy-sync/Cargo.toml", - "examples/nrf/Cargo.toml", + // "embassy-executor/Cargo.toml", + // "embassy-sync/Cargo.toml", + "examples/stm32wl/Cargo.toml", + // "examples/nrf5340/Cargo.toml", + // "examples/nrf-rtos-trace/Cargo.toml", // "examples/rp/Cargo.toml", // "examples/std/Cargo.toml", + // "examples/stm32c0/Cargo.toml", // "examples/stm32f0/Cargo.toml", // "examples/stm32f1/Cargo.toml", + // "examples/stm32f2/Cargo.toml", + // "examples/stm32f3/Cargo.toml", // "examples/stm32f4/Cargo.toml", // "examples/stm32f7/Cargo.toml", // "examples/stm32g0/Cargo.toml", // "examples/stm32g4/Cargo.toml", + // "examples/stm32h5/Cargo.toml", // "examples/stm32h7/Cargo.toml", // "examples/stm32l0/Cargo.toml", // "examples/stm32l1/Cargo.toml", // "examples/stm32l4/Cargo.toml", + // "examples/stm32l5/Cargo.toml", // "examples/stm32u5/Cargo.toml", - // "examples/stm32wb55/Cargo.toml", - // "examples/stm32wl55/Cargo.toml", + // "examples/stm32wb/Cargo.toml", + // "examples/stm32wl/Cargo.toml", // "examples/wasm/Cargo.toml", ], - "rust-analyzer.imports.granularity.enforce": true, - "rust-analyzer.imports.granularity.group": "module", - "rust-analyzer.cargo.buildScripts.enable": true, - "rust-analyzer.procMacro.attributes.enable": false, } \ No newline at end of file diff --git a/README.md b/README.md index 4dbbb5f51..b05e55aa5 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Embassy is the next-generation framework for embedded applications. Write safe, correct and energy-efficient embedded code faster, using the Rust programming language, its async facilities, and the Embassy libraries. -## Documentation - API reference - Website - Chat +## Documentation - API reference - Website - Chat ## Rust + async ❤️ embedded The Rust programming language is blazingly fast and memory-efficient, with no runtime, garbage collector or OS. It catches a wide variety of bugs at compile time, thanks to its full memory- and thread-safety, and expressive type system. @@ -14,12 +14,16 @@ Rust's async/await allows - **Hardware Abstraction Layers** - HALs implement safe, idiomatic Rust APIs to use the hardware capabilities, so raw register manipulation is not needed. The Embassy project maintains HALs for select hardware, but you can still use HALs from other projects with Embassy. - embassy-stm32, for all STM32 microcontroller families. - embassy-nrf, for the Nordic Semiconductor nRF52, nRF53, nRF91 series. + - embassy-rp, for the Raspberry Pi RP2040 microcontroller. + - esp-rs, for the Espressif Systems ESP32 series of chips. + - Embassy HAL support for Espressif chips is being developed in the [esp-rs/esp-hal](https://github.com/esp-rs/esp-hal) repository. + - Async WiFi, Bluetooth and ESP-NOW is being developed in the [esp-rs/esp-wifi](https://github.com/esp-rs/esp-wifi) repository. - **Time that Just Works** - No more messing with hardware timers. embassy_time provides Instant, Duration and Timer types that are globally available and never overflow. - **Real-time ready** - -Tasks on the same async executor run cooperatively, but you can create multiple executors with different priorities, so that higher priority tasks preempt lower priority ones. See the example. +Tasks on the same async executor run cooperatively, but you can create multiple executors with different priorities, so that higher priority tasks preempt lower priority ones. See the example. - **Low-power ready** - Easily build devices with years of battery life. The async executor automatically puts the core to sleep when there's no work to do. Tasks are woken by interrupts, there is no busy-loop polling while waiting. @@ -31,7 +35,7 @@ The embassy-net network stac The nrf-softdevice crate provides Bluetooth Low Energy 4.x and 5.x support for nRF52 microcontrollers. - **LoRa** - -embassy-lora supports LoRa networking on STM32WL wireless microcontrollers and Semtech SX127x transceivers. +embassy-lora supports LoRa networking. - **USB** - embassy-usb implements a device-side USB stack. Implementations for common classes such as USB serial (CDC ACM) and USB HID are available, and a rich builder API allows building your own. @@ -87,30 +91,24 @@ async fn main(spawner: Spawner) { Examples are found in the `examples/` folder seperated by the chip manufacturer they are designed to run on. For example: -* `examples/nrf` run on the `nrf52840-dk` board (PCA10056) but should be easily adaptable to other nRF52 chips and boards. +* `examples/nrf52840` run on the `nrf52840-dk` board (PCA10056) but should be easily adaptable to other nRF52 chips and boards. +* `examples/nrf5340` run on the `nrf5340-dk` board (PCA10095). * `examples/stm32xx` for the various STM32 families. * `examples/rp` are for the RP2040 chip. * `examples/std` are designed to run locally on your PC. ### Running examples -- Setup git submodules (needed for STM32 examples) +- Install `probe-rs`. ```bash -git submodule init -git submodule update -``` - -- Install `probe-run` with defmt support. - -```bash -cargo install probe-run +cargo install probe-rs --features cli ``` - Change directory to the sample's base directory. For example: ```bash -cd examples/nrf +cd examples/nrf52840 ``` - Run the example @@ -118,7 +116,7 @@ cd examples/nrf For example: ```bash -cargo run --bin blinky +cargo run --release --bin blinky ``` ## Developing Embassy with Rust Analyzer based editors diff --git a/ci.sh b/ci.sh index 77a8a7e27..376cc8f44 100755 --- a/ci.sh +++ b/ci.sh @@ -2,46 +2,44 @@ set -euo pipefail -export CARGO_TARGET_DIR=$PWD/target_ci export RUSTFLAGS=-Dwarnings -export DEFMT_LOG=trace +export DEFMT_LOG=trace,embassy_net_esp_hosted=debug,cyw43=info,cyw43_pio=info,smoltcp=info TARGET=$(rustc -vV | sed -n 's|host: ||p') +BUILD_EXTRA="" if [ $TARGET = "x86_64-unknown-linux-gnu" ]; then BUILD_EXTRA="--- build --release --manifest-path examples/std/Cargo.toml --target $TARGET --out-dir out/examples/std" -else - BUILD_EXTRA="" fi -find . -name '*.rs' -not -path '*target*' -not -path '*stm32-metapac-gen/out/*' -not -path '*stm32-metapac/src/*' | xargs rustfmt --check --skip-children --unstable-features --edition 2018 - -# Generate stm32-metapac -if [ ! -d "stm32-metapac-backup" ] -then - cp -r stm32-metapac stm32-metapac-backup -fi - -rm -rf stm32-metapac -cp -r stm32-metapac-backup stm32-metapac - -# for some reason Cargo stomps the cache if we don't specify --target. -# This happens with vanilla Cargo, not just cargo-batch. Bug? -(cd stm32-metapac-gen; cargo run --release --target $TARGET) -rm -rf stm32-metapac -mv stm32-metapac-gen/out stm32-metapac +find . -name '*.rs' -not -path '*target*' | xargs rustfmt --check --skip-children --unstable-features --edition 2021 cargo batch \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,log \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features nightly,defmt \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt \ + --- build --release --manifest-path embassy-sync/Cargo.toml --target thumbv6m-none-eabi --features nightly,defmt \ + --- build --release --manifest-path embassy-time/Cargo.toml --target thumbv6m-none-eabi --features nightly,unstable-traits,defmt,defmt-timestamp-uptime,tick-hz-32_768,generic-queue-8 \ + --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,medium-ethernet \ + --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet \ + --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,unstable-traits \ + --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,nightly \ + --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,unstable-traits,nightly \ + --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet \ + --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet,unstable-traits \ + --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet,nightly \ + --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet,unstable-traits,nightly \ + --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ethernet \ + --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ethernet,unstable-traits \ + --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ethernet,nightly \ + --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ethernet,unstable-traits,nightly \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52805,gpiote,time-driver-rtc1 \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52810,gpiote,time-driver-rtc1 \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52811,gpiote,time-driver-rtc1 \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52820,gpiote,time-driver-rtc1 \ - --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52832,gpiote,time-driver-rtc1 \ - --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52833,gpiote,time-driver-rtc1,unstable-traits \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52832,gpiote,time-driver-rtc1,reset-pin-as-gpio \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nightly,nrf52833,gpiote,time-driver-rtc1,unstable-traits,nfc-pins-as-gpio \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nightly,nrf9160-s,gpiote,time-driver-rtc1 \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nightly,nrf9160-ns,gpiote,time-driver-rtc1,unstable-traits \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features nightly,nrf5340-app-s,gpiote,time-driver-rtc1,unstable-traits \ @@ -54,33 +52,64 @@ cargo batch \ --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features nightly,unstable-traits,log \ --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features nightly,unstable-traits \ --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features nightly \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f410tb,defmt,exti,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f411ce,defmt,exti,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f429zi,log,exti,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32h755zi-cm7,defmt,exti,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32h7b3ai,defmt,exti,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32l476vg,defmt,exti,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32wb15cc,defmt,exti,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features nightly,stm32l072cz,defmt,exti,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features nightly,stm32l041f6,defmt,exti,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32l151cb-a,defmt,exti,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f398ve,defmt,exti,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features nightly,stm32g0c1ve,defmt,exti,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f217zg,defmt,exti,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features nightly,stm32l552ze,defmt,exti,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features nightly,stm32wl54jc-cm0p,defmt,exti,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32wle5ub,defmt,exti,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f107vc,defmt,exti,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f103re,defmt,exti,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f100c4,defmt,exti,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \ - --- build --release --manifest-path embassy-boot/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4 \ + --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features nightly,intrinsics \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti,time-driver-any \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,time-driver-any \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,nightly,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,nightly,defmt,exti,time-driver-any \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,nightly,defmt,time-driver-any \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,nightly,defmt,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,nightly,defmt,exti \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,nightly,defmt,exti,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,nightly,defmt \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f410tb,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f411ce,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f413vh,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f429zi,log,exti,time-driver-any,unstable-traits,embedded-sdmmc \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32f730i8,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32h755zi-cm7,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32h7b3ai,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32l476vg,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32l422cb,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32wb15cc,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features nightly,stm32l072cz,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features nightly,stm32l041f6,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32l151cb-a,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f398ve,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features nightly,stm32g0c1ve,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f217zg,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features nightly,stm32l552ze,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features nightly,stm32wl54jc-cm0p,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features nightly,stm32wle5jb,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f107vc,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f103re,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32f100c4,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32h503rb,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features nightly,stm32h562ag,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features ''\ + --- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'log' \ + --- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'defmt' \ + --- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'log,firmware-logs' \ + --- build --release --manifest-path cyw43/Cargo.toml --target thumbv6m-none-eabi --features 'defmt,firmware-logs' \ + --- build --release --manifest-path cyw43-pio/Cargo.toml --target thumbv6m-none-eabi --features '' \ + --- build --release --manifest-path cyw43-pio/Cargo.toml --target thumbv6m-none-eabi --features 'overclock' \ + --- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840,nightly \ + --- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns,nightly \ + --- build --release --manifest-path embassy-boot/rp/Cargo.toml --target thumbv6m-none-eabi --features nightly \ + --- build --release --manifest-path embassy-boot/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4,nightly \ --- build --release --manifest-path docs/modules/ROOT/examples/basic/Cargo.toml --target thumbv7em-none-eabi \ --- build --release --manifest-path docs/modules/ROOT/examples/layer-by-layer/blinky-pac/Cargo.toml --target thumbv7em-none-eabi \ --- build --release --manifest-path docs/modules/ROOT/examples/layer-by-layer/blinky-hal/Cargo.toml --target thumbv7em-none-eabi \ --- build --release --manifest-path docs/modules/ROOT/examples/layer-by-layer/blinky-irq/Cargo.toml --target thumbv7em-none-eabi \ --- build --release --manifest-path docs/modules/ROOT/examples/layer-by-layer/blinky-async/Cargo.toml --target thumbv7em-none-eabi \ - --- build --release --manifest-path examples/nrf/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/nrf \ + --- build --release --manifest-path examples/nrf52840/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/nrf52840 \ + --- build --release --manifest-path examples/nrf52840-rtic/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/nrf52840-rtic \ + --- build --release --manifest-path examples/nrf5340/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/nrf5340 \ --- build --release --manifest-path examples/rp/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/rp \ --- build --release --manifest-path examples/stm32f0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32f0 \ --- build --release --manifest-path examples/stm32f1/Cargo.toml --target thumbv7m-none-eabi --out-dir out/examples/stm32f1 \ @@ -88,64 +117,51 @@ cargo batch \ --- build --release --manifest-path examples/stm32f3/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/stm32f3 \ --- build --release --manifest-path examples/stm32f4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32f4 \ --- build --release --manifest-path examples/stm32f7/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/stm32f7 \ + --- build --release --manifest-path examples/stm32c0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32c0 \ --- build --release --manifest-path examples/stm32g0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32g0 \ --- build --release --manifest-path examples/stm32g4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32g4 \ + --- build --release --manifest-path examples/stm32h5/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h5 \ --- build --release --manifest-path examples/stm32h7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32h7 \ --- build --release --manifest-path examples/stm32l0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/stm32l0 \ --- build --release --manifest-path examples/stm32l1/Cargo.toml --target thumbv7m-none-eabi --out-dir out/examples/stm32l1 \ --- build --release --manifest-path examples/stm32l4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32l4 \ + --- build --release --manifest-path examples/stm32l5/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/stm32l5 \ --- build --release --manifest-path examples/stm32u5/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/stm32u5 \ --- build --release --manifest-path examples/stm32wb/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/stm32wb \ --- build --release --manifest-path examples/stm32wl/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/stm32wl \ - --- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/boot/nrf --bin b \ - --- build --release --manifest-path examples/boot/application/stm32f3/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/boot/stm32f3 --bin b \ - --- build --release --manifest-path examples/boot/application/stm32f7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/boot/stm32f7 --bin b \ - --- build --release --manifest-path examples/boot/application/stm32h7/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/boot/stm32h7 --bin b \ - --- build --release --manifest-path examples/boot/application/stm32l0/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/boot/stm32l0 --bin b \ - --- build --release --manifest-path examples/boot/application/stm32l1/Cargo.toml --target thumbv7m-none-eabi --out-dir out/examples/boot/stm32l1 --bin b \ - --- build --release --manifest-path examples/boot/application/stm32l4/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/boot/stm32l4 --bin b \ - --- build --release --manifest-path examples/boot/application/stm32wl/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/boot/stm32wl --bin b \ + --- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840,skip-include --out-dir out/examples/boot/nrf \ + --- build --release --manifest-path examples/boot/application/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns,skip-include --out-dir out/examples/boot/nrf \ + --- build --release --manifest-path examples/boot/application/rp/Cargo.toml --target thumbv6m-none-eabi --features skip-include --out-dir out/examples/boot/rp \ + --- build --release --manifest-path examples/boot/application/stm32f3/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32f3 \ + --- build --release --manifest-path examples/boot/application/stm32f7/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32f7 \ + --- build --release --manifest-path examples/boot/application/stm32h7/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32h7 \ + --- build --release --manifest-path examples/boot/application/stm32l0/Cargo.toml --target thumbv6m-none-eabi --features skip-include --out-dir out/examples/boot/stm32l0 \ + --- build --release --manifest-path examples/boot/application/stm32l1/Cargo.toml --target thumbv7m-none-eabi --features skip-include --out-dir out/examples/boot/stm32l1 \ + --- build --release --manifest-path examples/boot/application/stm32l4/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32l4 \ + --- build --release --manifest-path examples/boot/application/stm32wl/Cargo.toml --target thumbv7em-none-eabihf --features skip-include --out-dir out/examples/boot/stm32wl \ --- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \ + --- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \ + --- build --release --manifest-path examples/boot/bootloader/rp/Cargo.toml --target thumbv6m-none-eabi \ --- build --release --manifest-path examples/boot/bootloader/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4 \ --- build --release --manifest-path examples/wasm/Cargo.toml --target wasm32-unknown-unknown --out-dir out/examples/wasm \ - --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f103c8 --out-dir out/tests/bluepill-stm32f103c8 \ - --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi --out-dir out/tests/nucleo-stm32f429zi \ - --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32g491re --out-dir out/tests/nucleo-stm32g491re \ - --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32g071rb --out-dir out/tests/nucleo-stm32g071rb \ - --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h755zi --out-dir out/tests/nucleo-stm32h755zi \ - --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wb55rg --out-dir out/tests/nucleo-stm32wb55rg \ - --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32u585ai --out-dir out/tests/iot-stm32u585ai \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f103c8 --out-dir out/tests/stm32f103c8 \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi --out-dir out/tests/stm32f429zi \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32g491re --out-dir out/tests/stm32g491re \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32g071rb --out-dir out/tests/stm32g071rb \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32c031c6 --out-dir out/tests/stm32c031c6 \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h755zi --out-dir out/tests/stm32h755zi \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wb55rg --out-dir out/tests/stm32wb55rg \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h563zi --out-dir out/tests/stm32h563zi \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32u585ai --out-dir out/tests/stm32u585ai \ --- build --release --manifest-path tests/rp/Cargo.toml --target thumbv6m-none-eabi --out-dir out/tests/rpi-pico \ + --- build --release --manifest-path tests/nrf/Cargo.toml --target thumbv7em-none-eabi --out-dir out/tests/nrf52840-dk \ + --- build --release --manifest-path tests/riscv32/Cargo.toml --target riscv32imac-unknown-none-elf \ $BUILD_EXTRA -function run_elf { - echo Running target=$1 elf=$2 - STATUSCODE=$( - curl \ - -sS \ - --output /dev/stderr \ - --write-out "%{http_code}" \ - -H "Authorization: Bearer $TELEPROBE_TOKEN" \ - https://teleprobe.embassy.dev/targets/$1/run --data-binary @$2 - ) - echo - echo HTTP Status code: $STATUSCODE - test "$STATUSCODE" -eq 200 -} - if [[ -z "${TELEPROBE_TOKEN-}" ]]; then - if [[ -z "${ACTIONS_ID_TOKEN_REQUEST_TOKEN-}" ]]; then - echo No teleprobe token found, skipping running HIL tests - exit - fi - - export TELEPROBE_TOKEN=$(curl -sS -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "$ACTIONS_ID_TOKEN_REQUEST_URL" | jq -r '.value') + echo No teleprobe token found, skipping running HIL tests + exit fi -for board in $(ls out/tests); do - echo Running tests for board: $board - for elf in $(ls out/tests/$board); do - run_elf $board out/tests/$board/$elf - done -done +teleprobe client run -r out/tests diff --git a/ci_stable.sh b/ci_stable.sh index d388cfee3..daae98961 100755 --- a/ci_stable.sh +++ b/ci_stable.sh @@ -2,17 +2,23 @@ set -euo pipefail -export CARGO_TARGET_DIR=$PWD/target_ci_stable export RUSTFLAGS=-Dwarnings export DEFMT_LOG=trace -sed -i 's/channel.*/channel = "stable"/g' rust-toolchain.toml - cargo batch \ + --- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \ + --- build --release --manifest-path embassy-boot/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \ + --- build --release --manifest-path embassy-boot/rp/Cargo.toml --target thumbv6m-none-eabi \ + --- build --release --manifest-path embassy-boot/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4 \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features log \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features defmt \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features defmt \ + --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,medium-ethernet \ + --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet \ + --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,dhcpv4,medium-ethernet,unstable-traits \ + --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet \ + --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv6,medium-ethernet,unstable-traits \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52805,gpiote,time-driver-rtc1 \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52810,gpiote,time-driver-rtc1 \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52811,gpiote,time-driver-rtc1 \ @@ -30,38 +36,38 @@ cargo batch \ --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features unstable-traits,defmt \ --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features unstable-traits,log \ --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32g473cc,defmt,exti,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32g491re,defmt,exti,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32u585zi,defmt,exti,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wb55vy,defmt,exti,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wl55uc-cm4,defmt,exti,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l4r9zi,defmt,exti,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f303vc,defmt,exti,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f411ce,defmt,time-driver-any,embassy-time?/tick-32768hz \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f411ce,defmt,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi,log,time-driver-any,embassy-time?/tick-32768hz \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi,log,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h755zi-cm7,defmt,time-driver-any,embassy-time?/tick-32768hz \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h755zi-cm7,defmt,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l476vg,defmt,time-driver-any,embassy-time?/tick-32768hz \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l476vg,defmt,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32l072cz,defmt,time-driver-any,embassy-time?/tick-32768hz \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32l072cz,defmt,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32l151cb-a,defmt,time-driver-any,embassy-time?/tick-32768hz \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32l151cb-a,defmt,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f410tb,defmt,exti,time-driver-any,embassy-time?/tick-32768hz \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f410tb,defmt,exti,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi,log,exti,time-driver-any,embassy-time?/tick-32768hz \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi,log,exti,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h755zi-cm7,defmt,exti,time-driver-any,embassy-time?/tick-32768hz \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h755zi-cm7,defmt,exti,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l476vg,defmt,exti,time-driver-any,embassy-time?/tick-32768hz \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l476vg,defmt,exti,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32l072cz,defmt,exti,time-driver-any,embassy-time?/tick-32768hz \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32l072cz,defmt,exti,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32l151cb-a,defmt,exti,time-driver-any,embassy-time?/tick-32768hz \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32l151cb-a,defmt,exti,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f217zg,defmt,exti,time-driver-any,embassy-time?/tick-32768hz \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f217zg,defmt,exti,time-driver-any,embassy-time?/tick-32768hz,unstable-traits \ - --- build --release --manifest-path examples/nrf/Cargo.toml --target thumbv7em-none-eabi --no-default-features --out-dir out/examples/nrf --bin raw_spawn \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32g473cc,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32g491re,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32u585zi,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wb55vy,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wl55cc-cm4,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l4r9zi,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f303vc,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f411ce,defmt,time-driver-any \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f411ce,defmt,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi,log,time-driver-any \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi,log,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h755zi-cm7,defmt,time-driver-any \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h755zi-cm7,defmt,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l476vg,defmt,time-driver-any \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l476vg,defmt,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32l072cz,defmt,time-driver-any \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32l072cz,defmt,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32l151cb-a,defmt,time-driver-any \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32l151cb-a,defmt,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f410tb,defmt,exti,time-driver-any \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f410tb,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi,log,exti,time-driver-any \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi,log,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h755zi-cm7,defmt,exti,time-driver-any \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h755zi-cm7,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l476vg,defmt,exti,time-driver-any \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l476vg,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32l072cz,defmt,exti,time-driver-any \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32l072cz,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32l151cb-a,defmt,exti,time-driver-any \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32l151cb-a,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f217zg,defmt,exti,time-driver-any \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f217zg,defmt,exti,time-driver-any,unstable-traits \ + --- build --release --manifest-path examples/nrf52840/Cargo.toml --target thumbv7em-none-eabi --no-default-features --out-dir out/examples/nrf52840 --bin raw_spawn \ --- build --release --manifest-path examples/stm32l0/Cargo.toml --target thumbv6m-none-eabi --no-default-features --out-dir out/examples/stm32l0 --bin raw_spawn \ diff --git a/cyw43-firmware/43439A0.bin b/cyw43-firmware/43439A0.bin new file mode 100755 index 000000000..b46b3beff Binary files /dev/null and b/cyw43-firmware/43439A0.bin differ diff --git a/cyw43-firmware/43439A0_clm.bin b/cyw43-firmware/43439A0_clm.bin new file mode 100755 index 000000000..6e3ba786b Binary files /dev/null and b/cyw43-firmware/43439A0_clm.bin differ diff --git a/cyw43-firmware/LICENSE-permissive-binary-license-1.0.txt b/cyw43-firmware/LICENSE-permissive-binary-license-1.0.txt new file mode 100644 index 000000000..cbb51f9c9 --- /dev/null +++ b/cyw43-firmware/LICENSE-permissive-binary-license-1.0.txt @@ -0,0 +1,49 @@ +Permissive Binary License + +Version 1.0, July 2019 + +Redistribution. Redistribution and use in binary form, without +modification, are permitted provided that the following conditions are +met: + +1) Redistributions must reproduce the above copyright notice and the + following disclaimer in the documentation and/or other materials + provided with the distribution. + +2) Unless to the extent explicitly permitted by law, no reverse + engineering, decompilation, or disassembly of this software is + permitted. + +3) Redistribution as part of a software development kit must include the + accompanying file named �DEPENDENCIES� and any dependencies listed in + that file. + +4) Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +Limited patent license. The copyright holders (and contributors) grant a +worldwide, non-exclusive, no-charge, royalty-free patent license to +make, have made, use, offer to sell, sell, import, and otherwise +transfer this software, where such license applies only to those patent +claims licensable by the copyright holders (and contributors) that are +necessarily infringed by this software. This patent license shall not +apply to any combinations that include this software. No hardware is +licensed hereunder. + +If you institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the software +itself infringes your patent(s), then your rights granted under this +license shall terminate as of the date such litigation is filed. + +DISCLAIMER. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +CONTRIBUTORS "AS IS." ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT +NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/cyw43-firmware/README.md b/cyw43-firmware/README.md new file mode 100644 index 000000000..7381fdc56 --- /dev/null +++ b/cyw43-firmware/README.md @@ -0,0 +1,5 @@ +# WiFi firmware + +Firmware obtained from https://github.com/Infineon/wifi-host-driver/tree/master/WiFi_Host_Driver/resources/firmware/COMPONENT_43439 + +Licensed under the [Infineon Permissive Binary License](./LICENSE-permissive-binary-license-1.0.txt) \ No newline at end of file diff --git a/cyw43-pio/Cargo.toml b/cyw43-pio/Cargo.toml new file mode 100644 index 000000000..14c07178f --- /dev/null +++ b/cyw43-pio/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "cyw43-pio" +version = "0.1.0" +edition = "2021" + +[features] +# If disabled, SPI runs at 31.25MHz +# If enabled, SPI runs at 62.5MHz, which is 25% higher than 50Mhz which is the maximum according to the CYW43439 datasheet. +overclock = [] + +[dependencies] +cyw43 = { version = "0.1.0", path = "../cyw43" } +embassy-rp = { version = "0.1.0", path = "../embassy-rp" } +pio-proc = "0.2" +pio = "0.2.1" +fixed = "1.23.1" +defmt = { version = "0.3", optional = true } + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/cyw43-pio-v$VERSION/cyw43-pio/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/cyw43-pio/src/" +target = "thumbv6m-none-eabi" diff --git a/cyw43-pio/src/lib.rs b/cyw43-pio/src/lib.rs new file mode 100644 index 000000000..dca30c74d --- /dev/null +++ b/cyw43-pio/src/lib.rs @@ -0,0 +1,229 @@ +#![no_std] +#![allow(incomplete_features)] +#![feature(async_fn_in_trait)] + +use core::slice; + +use cyw43::SpiBusCyw43; +use embassy_rp::dma::Channel; +use embassy_rp::gpio::{Drive, Level, Output, Pin, Pull, SlewRate}; +use embassy_rp::pio::{Common, Config, Direction, Instance, Irq, PioPin, ShiftDirection, StateMachine}; +use embassy_rp::relocate::RelocatedProgram; +use embassy_rp::{pio_instr_util, Peripheral, PeripheralRef}; +use fixed::FixedU32; +use pio_proc::pio_asm; + +pub struct PioSpi<'d, CS: Pin, PIO: Instance, const SM: usize, DMA> { + cs: Output<'d, CS>, + sm: StateMachine<'d, PIO, SM>, + irq: Irq<'d, PIO, 0>, + dma: PeripheralRef<'d, DMA>, + wrap_target: u8, +} + +impl<'d, CS, PIO, const SM: usize, DMA> PioSpi<'d, CS, PIO, SM, DMA> +where + DMA: Channel, + CS: Pin, + PIO: Instance, +{ + pub fn new( + common: &mut Common<'d, PIO>, + mut sm: StateMachine<'d, PIO, SM>, + irq: Irq<'d, PIO, 0>, + cs: Output<'d, CS>, + dio: DIO, + clk: CLK, + dma: impl Peripheral

+ 'd, + ) -> Self + where + DIO: PioPin, + CLK: PioPin, + { + #[cfg(feature = "overclock")] + let program = pio_asm!( + ".side_set 1" + + ".wrap_target" + // write out x-1 bits + "lp:" + "out pins, 1 side 0" + "jmp x-- lp side 1" + // switch directions + "set pindirs, 0 side 0" + "nop side 1" // necessary for clkdiv=1. + "nop side 0" + // read in y-1 bits + "lp2:" + "in pins, 1 side 1" + "jmp y-- lp2 side 0" + + // wait for event and irq host + "wait 1 pin 0 side 0" + "irq 0 side 0" + + ".wrap" + ); + #[cfg(not(feature = "overclock"))] + let program = pio_asm!( + ".side_set 1" + + ".wrap_target" + // write out x-1 bits + "lp:" + "out pins, 1 side 0" + "jmp x-- lp side 1" + // switch directions + "set pindirs, 0 side 0" + "nop side 0" + // read in y-1 bits + "lp2:" + "in pins, 1 side 1" + "jmp y-- lp2 side 0" + + // wait for event and irq host + "wait 1 pin 0 side 0" + "irq 0 side 0" + + ".wrap" + ); + + let relocated = RelocatedProgram::new(&program.program); + + let mut pin_io: embassy_rp::pio::Pin = common.make_pio_pin(dio); + pin_io.set_pull(Pull::None); + pin_io.set_schmitt(true); + pin_io.set_input_sync_bypass(true); + pin_io.set_drive_strength(Drive::_12mA); + pin_io.set_slew_rate(SlewRate::Fast); + + let mut pin_clk = common.make_pio_pin(clk); + pin_clk.set_drive_strength(Drive::_12mA); + pin_clk.set_slew_rate(SlewRate::Fast); + + let mut cfg = Config::default(); + cfg.use_program(&common.load_program(&relocated), &[&pin_clk]); + cfg.set_out_pins(&[&pin_io]); + cfg.set_in_pins(&[&pin_io]); + cfg.set_set_pins(&[&pin_io]); + cfg.shift_out.direction = ShiftDirection::Left; + cfg.shift_out.auto_fill = true; + //cfg.shift_out.threshold = 32; + cfg.shift_in.direction = ShiftDirection::Left; + cfg.shift_in.auto_fill = true; + //cfg.shift_in.threshold = 32; + + #[cfg(feature = "overclock")] + { + // 125mhz Pio => 62.5Mhz SPI Freq. 25% higher than theoretical maximum according to + // data sheet, but seems to work fine. + cfg.clock_divider = FixedU32::from_bits(0x0100); + } + + #[cfg(not(feature = "overclock"))] + { + // same speed as pico-sdk, 62.5Mhz + // This is actually the fastest we can go without overclocking. + // According to data sheet, the theoretical maximum is 100Mhz Pio => 50Mhz SPI Freq. + // However, the PIO uses a fractional divider, which works by introducing jitter when + // the divider is not an integer. It does some clocks at 125mhz and others at 62.5mhz + // so that it averages out to the desired frequency of 100mhz. The 125mhz clock cycles + // violate the maximum from the data sheet. + cfg.clock_divider = FixedU32::from_bits(0x0200); + } + + sm.set_config(&cfg); + + sm.set_pin_dirs(Direction::Out, &[&pin_clk, &pin_io]); + sm.set_pins(Level::Low, &[&pin_clk, &pin_io]); + + Self { + cs, + sm, + irq, + dma: dma.into_ref(), + wrap_target: relocated.wrap().target, + } + } + + pub async fn write(&mut self, write: &[u32]) -> u32 { + self.sm.set_enable(false); + let write_bits = write.len() * 32 - 1; + let read_bits = 31; + + #[cfg(feature = "defmt")] + defmt::trace!("write={} read={}", write_bits, read_bits); + + unsafe { + pio_instr_util::set_x(&mut self.sm, write_bits as u32); + pio_instr_util::set_y(&mut self.sm, read_bits as u32); + pio_instr_util::set_pindir(&mut self.sm, 0b1); + pio_instr_util::exec_jmp(&mut self.sm, self.wrap_target); + } + + self.sm.set_enable(true); + + self.sm.tx().dma_push(self.dma.reborrow(), write).await; + + let mut status = 0; + self.sm + .rx() + .dma_pull(self.dma.reborrow(), slice::from_mut(&mut status)) + .await; + status + } + + pub async fn cmd_read(&mut self, cmd: u32, read: &mut [u32]) -> u32 { + self.sm.set_enable(false); + let write_bits = 31; + let read_bits = read.len() * 32 + 32 - 1; + + #[cfg(feature = "defmt")] + defmt::trace!("write={} read={}", write_bits, read_bits); + + unsafe { + pio_instr_util::set_y(&mut self.sm, read_bits as u32); + pio_instr_util::set_x(&mut self.sm, write_bits as u32); + pio_instr_util::set_pindir(&mut self.sm, 0b1); + pio_instr_util::exec_jmp(&mut self.sm, self.wrap_target); + } + + // self.cs.set_low(); + self.sm.set_enable(true); + + self.sm.tx().dma_push(self.dma.reborrow(), slice::from_ref(&cmd)).await; + self.sm.rx().dma_pull(self.dma.reborrow(), read).await; + + let mut status = 0; + self.sm + .rx() + .dma_pull(self.dma.reborrow(), slice::from_mut(&mut status)) + .await; + status + } +} + +impl<'d, CS, PIO, const SM: usize, DMA> SpiBusCyw43 for PioSpi<'d, CS, PIO, SM, DMA> +where + CS: Pin, + PIO: Instance, + DMA: Channel, +{ + async fn cmd_write(&mut self, write: &[u32]) -> u32 { + self.cs.set_low(); + let status = self.write(write).await; + self.cs.set_high(); + status + } + + async fn cmd_read(&mut self, write: u32, read: &mut [u32]) -> u32 { + self.cs.set_low(); + let status = self.cmd_read(write, read).await; + self.cs.set_high(); + status + } + + async fn wait_for_event(&mut self) { + self.irq.wait().await; + } +} diff --git a/cyw43/Cargo.toml b/cyw43/Cargo.toml new file mode 100644 index 000000000..50fb7c5db --- /dev/null +++ b/cyw43/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "cyw43" +version = "0.1.0" +edition = "2021" + +[features] +defmt = ["dep:defmt"] +log = ["dep:log"] + +# Fetch console logs from the WiFi firmware and forward them to `log` or `defmt`. +firmware-logs = [] + +[dependencies] +embassy-time = { version = "0.1.2", path = "../embassy-time"} +embassy-sync = { version = "0.2.0", path = "../embassy-sync"} +embassy-futures = { version = "0.1.0", path = "../embassy-futures"} +embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel"} +atomic-polyfill = "0.1.5" + +defmt = { version = "0.3", optional = true } +log = { version = "0.4.17", optional = true } + +cortex-m = "0.7.6" +cortex-m-rt = "0.7.0" +futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] } + +embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.11" } +num_enum = { version = "0.5.7", default-features = false } + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/cyw43-v$VERSION/cyw43/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/cyw43/src/" +target = "thumbv6m-none-eabi" +features = ["defmt", "firmware-logs"] \ No newline at end of file diff --git a/cyw43/README.md b/cyw43/README.md new file mode 100644 index 000000000..5b8f3cf40 --- /dev/null +++ b/cyw43/README.md @@ -0,0 +1,57 @@ +# cyw43 + +Rust driver for the CYW43439 wifi chip, used in the Raspberry Pi Pico W. Implementation based on [Infineon/wifi-host-driver](https://github.com/Infineon/wifi-host-driver). + +## Current status + +Working: + +- Station mode (joining an AP). +- AP mode (creating an AP) +- Scanning +- Sending and receiving Ethernet frames. +- Using the default MAC address. +- [`embassy-net`](https://embassy.dev) integration. +- RP2040 PIO driver for the nonstandard half-duplex SPI used in the Pico W. +- Using IRQ for device events +- GPIO support (for LED on the Pico W) + +TODO: + +- Setting a custom MAC address. +- Bus sleep (for power consumption optimization) + +## Running the examples + +- `cargo install probe-rs --features cli` +- `cd examples/rp` +### Example 1: Scan the wifi stations +- `cargo run --release --bin wifi_scan` +### Example 2: Create an access point (IP and credentials in the code) +- `cargo run --release --bin wifi_ap_tcp_server` +### Example 3: Connect to an existing network and create a server +- `cargo run --release --bin wifi_tcp_server` + +After a few seconds, you should see that DHCP picks up an IP address like this +``` +11.944489 DEBUG Acquired IP configuration: +11.944517 DEBUG IP address: 192.168.0.250/24 +11.944620 DEBUG Default gateway: 192.168.0.33 +11.944722 DEBUG DNS server 0: 192.168.0.33 +``` +This example implements a TCP echo server on port 1234. You can try connecting to it with: +``` +nc 192.168.0.250 1234 +``` +Send it some data, you should see it echoed back and printed in the firmware's logs. + +## License + +This work is licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. + diff --git a/cyw43/src/bus.rs b/cyw43/src/bus.rs new file mode 100644 index 000000000..e26f11120 --- /dev/null +++ b/cyw43/src/bus.rs @@ -0,0 +1,328 @@ +use embassy_futures::yield_now; +use embassy_time::{Duration, Timer}; +use embedded_hal_1::digital::OutputPin; +use futures::FutureExt; + +use crate::consts::*; +use crate::slice8_mut; + +/// Custom Spi Trait that _only_ supports the bus operation of the cyw43 +/// Implementors are expected to hold the CS pin low during an operation. +pub trait SpiBusCyw43 { + /// Issues a write command on the bus + /// First 32 bits of `word` are expected to be a cmd word + async fn cmd_write(&mut self, write: &[u32]) -> u32; + + /// Issues a read command on the bus + /// `write` is expected to be a 32 bit cmd word + /// `read` will contain the response of the device + /// Backplane reads have a response delay that produces one extra unspecified word at the beginning of `read`. + /// Callers that want to read `n` word from the backplane, have to provide a slice that is `n+1` words long. + async fn cmd_read(&mut self, write: u32, read: &mut [u32]) -> u32; + + /// Wait for events from the Device. A typical implementation would wait for the IRQ pin to be high. + /// The default implementation always reports ready, resulting in active polling of the device. + async fn wait_for_event(&mut self) { + yield_now().await; + } +} + +pub(crate) struct Bus { + backplane_window: u32, + pwr: PWR, + spi: SPI, + status: u32, +} + +impl Bus +where + PWR: OutputPin, + SPI: SpiBusCyw43, +{ + pub(crate) fn new(pwr: PWR, spi: SPI) -> Self { + Self { + backplane_window: 0xAAAA_AAAA, + pwr, + spi, + status: 0, + } + } + + pub async fn init(&mut self) { + // Reset + self.pwr.set_low().unwrap(); + Timer::after(Duration::from_millis(20)).await; + self.pwr.set_high().unwrap(); + Timer::after(Duration::from_millis(250)).await; + + while self + .read32_swapped(REG_BUS_TEST_RO) + .inspect(|v| trace!("{:#x}", v)) + .await + != FEEDBEAD + {} + + self.write32_swapped(REG_BUS_TEST_RW, TEST_PATTERN).await; + let val = self.read32_swapped(REG_BUS_TEST_RW).await; + trace!("{:#x}", val); + assert_eq!(val, TEST_PATTERN); + + let val = self.read32_swapped(REG_BUS_CTRL).await; + trace!("{:#010b}", (val & 0xff)); + + // 32-bit word length, little endian (which is the default endianess). + self.write32_swapped( + REG_BUS_CTRL, + WORD_LENGTH_32 | HIGH_SPEED | INTERRUPT_HIGH | WAKE_UP | STATUS_ENABLE | INTERRUPT_WITH_STATUS, + ) + .await; + + let val = self.read8(FUNC_BUS, REG_BUS_CTRL).await; + trace!("{:#b}", val); + + let val = self.read32(FUNC_BUS, REG_BUS_TEST_RO).await; + trace!("{:#x}", val); + assert_eq!(val, FEEDBEAD); + let val = self.read32(FUNC_BUS, REG_BUS_TEST_RW).await; + trace!("{:#x}", val); + assert_eq!(val, TEST_PATTERN); + } + + pub async fn wlan_read(&mut self, buf: &mut [u32], len_in_u8: u32) { + let cmd = cmd_word(READ, INC_ADDR, FUNC_WLAN, 0, len_in_u8); + let len_in_u32 = (len_in_u8 as usize + 3) / 4; + + self.status = self.spi.cmd_read(cmd, &mut buf[..len_in_u32]).await; + } + + pub async fn wlan_write(&mut self, buf: &[u32]) { + let cmd = cmd_word(WRITE, INC_ADDR, FUNC_WLAN, 0, buf.len() as u32 * 4); + //TODO try to remove copy? + let mut cmd_buf = [0_u32; 513]; + cmd_buf[0] = cmd; + cmd_buf[1..][..buf.len()].copy_from_slice(buf); + + self.status = self.spi.cmd_write(&cmd_buf).await; + } + + #[allow(unused)] + pub async fn bp_read(&mut self, mut addr: u32, mut data: &mut [u8]) { + // It seems the HW force-aligns the addr + // to 2 if data.len() >= 2 + // to 4 if data.len() >= 4 + // To simplify, enforce 4-align for now. + assert!(addr % 4 == 0); + + // Backplane read buffer has one extra word for the response delay. + let mut buf = [0u32; BACKPLANE_MAX_TRANSFER_SIZE / 4 + 1]; + + while !data.is_empty() { + // Ensure transfer doesn't cross a window boundary. + let window_offs = addr & BACKPLANE_ADDRESS_MASK; + let window_remaining = BACKPLANE_WINDOW_SIZE - window_offs as usize; + + let len = data.len().min(BACKPLANE_MAX_TRANSFER_SIZE).min(window_remaining); + + self.backplane_set_window(addr).await; + + let cmd = cmd_word(READ, INC_ADDR, FUNC_BACKPLANE, window_offs, len as u32); + + // round `buf` to word boundary, add one extra word for the response delay + self.status = self.spi.cmd_read(cmd, &mut buf[..(len + 3) / 4 + 1]).await; + + // when writing out the data, we skip the response-delay byte + data[..len].copy_from_slice(&slice8_mut(&mut buf[1..])[..len]); + + // Advance ptr. + addr += len as u32; + data = &mut data[len..]; + } + } + + pub async fn bp_write(&mut self, mut addr: u32, mut data: &[u8]) { + // It seems the HW force-aligns the addr + // to 2 if data.len() >= 2 + // to 4 if data.len() >= 4 + // To simplify, enforce 4-align for now. + assert!(addr % 4 == 0); + + let mut buf = [0u32; BACKPLANE_MAX_TRANSFER_SIZE / 4 + 1]; + + while !data.is_empty() { + // Ensure transfer doesn't cross a window boundary. + let window_offs = addr & BACKPLANE_ADDRESS_MASK; + let window_remaining = BACKPLANE_WINDOW_SIZE - window_offs as usize; + + let len = data.len().min(BACKPLANE_MAX_TRANSFER_SIZE).min(window_remaining); + slice8_mut(&mut buf[1..])[..len].copy_from_slice(&data[..len]); + + self.backplane_set_window(addr).await; + + let cmd = cmd_word(WRITE, INC_ADDR, FUNC_BACKPLANE, window_offs, len as u32); + buf[0] = cmd; + + self.status = self.spi.cmd_write(&buf[..(len + 3) / 4 + 1]).await; + + // Advance ptr. + addr += len as u32; + data = &data[len..]; + } + } + + pub async fn bp_read8(&mut self, addr: u32) -> u8 { + self.backplane_readn(addr, 1).await as u8 + } + + pub async fn bp_write8(&mut self, addr: u32, val: u8) { + self.backplane_writen(addr, val as u32, 1).await + } + + pub async fn bp_read16(&mut self, addr: u32) -> u16 { + self.backplane_readn(addr, 2).await as u16 + } + + #[allow(unused)] + pub async fn bp_write16(&mut self, addr: u32, val: u16) { + self.backplane_writen(addr, val as u32, 2).await + } + + #[allow(unused)] + pub async fn bp_read32(&mut self, addr: u32) -> u32 { + self.backplane_readn(addr, 4).await + } + + pub async fn bp_write32(&mut self, addr: u32, val: u32) { + self.backplane_writen(addr, val, 4).await + } + + async fn backplane_readn(&mut self, addr: u32, len: u32) -> u32 { + self.backplane_set_window(addr).await; + + let mut bus_addr = addr & BACKPLANE_ADDRESS_MASK; + if len == 4 { + bus_addr |= BACKPLANE_ADDRESS_32BIT_FLAG + } + self.readn(FUNC_BACKPLANE, bus_addr, len).await + } + + async fn backplane_writen(&mut self, addr: u32, val: u32, len: u32) { + self.backplane_set_window(addr).await; + + let mut bus_addr = addr & BACKPLANE_ADDRESS_MASK; + if len == 4 { + bus_addr |= BACKPLANE_ADDRESS_32BIT_FLAG + } + self.writen(FUNC_BACKPLANE, bus_addr, val, len).await + } + + async fn backplane_set_window(&mut self, addr: u32) { + let new_window = addr & !BACKPLANE_ADDRESS_MASK; + + if (new_window >> 24) as u8 != (self.backplane_window >> 24) as u8 { + self.write8( + FUNC_BACKPLANE, + REG_BACKPLANE_BACKPLANE_ADDRESS_HIGH, + (new_window >> 24) as u8, + ) + .await; + } + if (new_window >> 16) as u8 != (self.backplane_window >> 16) as u8 { + self.write8( + FUNC_BACKPLANE, + REG_BACKPLANE_BACKPLANE_ADDRESS_MID, + (new_window >> 16) as u8, + ) + .await; + } + if (new_window >> 8) as u8 != (self.backplane_window >> 8) as u8 { + self.write8( + FUNC_BACKPLANE, + REG_BACKPLANE_BACKPLANE_ADDRESS_LOW, + (new_window >> 8) as u8, + ) + .await; + } + self.backplane_window = new_window; + } + + pub async fn read8(&mut self, func: u32, addr: u32) -> u8 { + self.readn(func, addr, 1).await as u8 + } + + pub async fn write8(&mut self, func: u32, addr: u32, val: u8) { + self.writen(func, addr, val as u32, 1).await + } + + pub async fn read16(&mut self, func: u32, addr: u32) -> u16 { + self.readn(func, addr, 2).await as u16 + } + + #[allow(unused)] + pub async fn write16(&mut self, func: u32, addr: u32, val: u16) { + self.writen(func, addr, val as u32, 2).await + } + + pub async fn read32(&mut self, func: u32, addr: u32) -> u32 { + self.readn(func, addr, 4).await + } + + #[allow(unused)] + pub async fn write32(&mut self, func: u32, addr: u32, val: u32) { + self.writen(func, addr, val, 4).await + } + + async fn readn(&mut self, func: u32, addr: u32, len: u32) -> u32 { + let cmd = cmd_word(READ, INC_ADDR, func, addr, len); + let mut buf = [0; 2]; + // if we are reading from the backplane, we need an extra word for the response delay + let len = if func == FUNC_BACKPLANE { 2 } else { 1 }; + + self.status = self.spi.cmd_read(cmd, &mut buf[..len]).await; + + // if we read from the backplane, the result is in the second word, after the response delay + if func == FUNC_BACKPLANE { + buf[1] + } else { + buf[0] + } + } + + async fn writen(&mut self, func: u32, addr: u32, val: u32, len: u32) { + let cmd = cmd_word(WRITE, INC_ADDR, func, addr, len); + + self.status = self.spi.cmd_write(&[cmd, val]).await; + } + + async fn read32_swapped(&mut self, addr: u32) -> u32 { + let cmd = cmd_word(READ, INC_ADDR, FUNC_BUS, addr, 4); + let cmd = swap16(cmd); + let mut buf = [0; 1]; + + self.status = self.spi.cmd_read(cmd, &mut buf).await; + + swap16(buf[0]) + } + + async fn write32_swapped(&mut self, addr: u32, val: u32) { + let cmd = cmd_word(WRITE, INC_ADDR, FUNC_BUS, addr, 4); + let buf = [swap16(cmd), swap16(val)]; + + self.status = self.spi.cmd_write(&buf).await; + } + + pub async fn wait_for_event(&mut self) { + self.spi.wait_for_event().await; + } + + pub fn status(&self) -> u32 { + self.status + } +} + +fn swap16(x: u32) -> u32 { + x.rotate_left(16) +} + +fn cmd_word(write: bool, incr: bool, func: u32, addr: u32, len: u32) -> u32 { + (write as u32) << 31 | (incr as u32) << 30 | (func & 0b11) << 28 | (addr & 0x1FFFF) << 11 | (len & 0x7FF) +} diff --git a/cyw43/src/consts.rs b/cyw43/src/consts.rs new file mode 100644 index 000000000..1f6551589 --- /dev/null +++ b/cyw43/src/consts.rs @@ -0,0 +1,318 @@ +#![allow(unused)] + +pub(crate) const FUNC_BUS: u32 = 0; +pub(crate) const FUNC_BACKPLANE: u32 = 1; +pub(crate) const FUNC_WLAN: u32 = 2; +pub(crate) const FUNC_BT: u32 = 3; + +pub(crate) const REG_BUS_CTRL: u32 = 0x0; +pub(crate) const REG_BUS_INTERRUPT: u32 = 0x04; // 16 bits - Interrupt status +pub(crate) const REG_BUS_INTERRUPT_ENABLE: u32 = 0x06; // 16 bits - Interrupt mask +pub(crate) const REG_BUS_STATUS: u32 = 0x8; +pub(crate) const REG_BUS_TEST_RO: u32 = 0x14; +pub(crate) const REG_BUS_TEST_RW: u32 = 0x18; +pub(crate) const REG_BUS_RESP_DELAY: u32 = 0x1c; +pub(crate) const WORD_LENGTH_32: u32 = 0x1; +pub(crate) const HIGH_SPEED: u32 = 0x10; +pub(crate) const INTERRUPT_HIGH: u32 = 1 << 5; +pub(crate) const WAKE_UP: u32 = 1 << 7; +pub(crate) const STATUS_ENABLE: u32 = 1 << 16; +pub(crate) const INTERRUPT_WITH_STATUS: u32 = 1 << 17; + +// SPI_STATUS_REGISTER bits +pub(crate) const STATUS_DATA_NOT_AVAILABLE: u32 = 0x00000001; +pub(crate) const STATUS_UNDERFLOW: u32 = 0x00000002; +pub(crate) const STATUS_OVERFLOW: u32 = 0x00000004; +pub(crate) const STATUS_F2_INTR: u32 = 0x00000008; +pub(crate) const STATUS_F3_INTR: u32 = 0x00000010; +pub(crate) const STATUS_F2_RX_READY: u32 = 0x00000020; +pub(crate) const STATUS_F3_RX_READY: u32 = 0x00000040; +pub(crate) const STATUS_HOST_CMD_DATA_ERR: u32 = 0x00000080; +pub(crate) const STATUS_F2_PKT_AVAILABLE: u32 = 0x00000100; +pub(crate) const STATUS_F2_PKT_LEN_MASK: u32 = 0x000FFE00; +pub(crate) const STATUS_F2_PKT_LEN_SHIFT: u32 = 9; +pub(crate) const STATUS_F3_PKT_AVAILABLE: u32 = 0x00100000; +pub(crate) const STATUS_F3_PKT_LEN_MASK: u32 = 0xFFE00000; +pub(crate) const STATUS_F3_PKT_LEN_SHIFT: u32 = 21; + +pub(crate) const REG_BACKPLANE_GPIO_SELECT: u32 = 0x10005; +pub(crate) const REG_BACKPLANE_GPIO_OUTPUT: u32 = 0x10006; +pub(crate) const REG_BACKPLANE_GPIO_ENABLE: u32 = 0x10007; +pub(crate) const REG_BACKPLANE_FUNCTION2_WATERMARK: u32 = 0x10008; +pub(crate) const REG_BACKPLANE_DEVICE_CONTROL: u32 = 0x10009; +pub(crate) const REG_BACKPLANE_BACKPLANE_ADDRESS_LOW: u32 = 0x1000A; +pub(crate) const REG_BACKPLANE_BACKPLANE_ADDRESS_MID: u32 = 0x1000B; +pub(crate) const REG_BACKPLANE_BACKPLANE_ADDRESS_HIGH: u32 = 0x1000C; +pub(crate) const REG_BACKPLANE_FRAME_CONTROL: u32 = 0x1000D; +pub(crate) const REG_BACKPLANE_CHIP_CLOCK_CSR: u32 = 0x1000E; +pub(crate) const REG_BACKPLANE_PULL_UP: u32 = 0x1000F; +pub(crate) const REG_BACKPLANE_READ_FRAME_BC_LOW: u32 = 0x1001B; +pub(crate) const REG_BACKPLANE_READ_FRAME_BC_HIGH: u32 = 0x1001C; +pub(crate) const REG_BACKPLANE_WAKEUP_CTRL: u32 = 0x1001E; +pub(crate) const REG_BACKPLANE_SLEEP_CSR: u32 = 0x1001F; + +pub(crate) const BACKPLANE_WINDOW_SIZE: usize = 0x8000; +pub(crate) const BACKPLANE_ADDRESS_MASK: u32 = 0x7FFF; +pub(crate) const BACKPLANE_ADDRESS_32BIT_FLAG: u32 = 0x08000; +pub(crate) const BACKPLANE_MAX_TRANSFER_SIZE: usize = 64; +// Active Low Power (ALP) clock constants +pub(crate) const BACKPLANE_ALP_AVAIL_REQ: u8 = 0x08; +pub(crate) const BACKPLANE_ALP_AVAIL: u8 = 0x40; + +// Broadcom AMBA (Advanced Microcontroller Bus Architecture) Interconnect +// (AI) pub (crate) constants +pub(crate) const AI_IOCTRL_OFFSET: u32 = 0x408; +pub(crate) const AI_IOCTRL_BIT_FGC: u8 = 0x0002; +pub(crate) const AI_IOCTRL_BIT_CLOCK_EN: u8 = 0x0001; +pub(crate) const AI_IOCTRL_BIT_CPUHALT: u8 = 0x0020; + +pub(crate) const AI_RESETCTRL_OFFSET: u32 = 0x800; +pub(crate) const AI_RESETCTRL_BIT_RESET: u8 = 1; + +pub(crate) const AI_RESETSTATUS_OFFSET: u32 = 0x804; + +pub(crate) const TEST_PATTERN: u32 = 0x12345678; +pub(crate) const FEEDBEAD: u32 = 0xFEEDBEAD; + +// SPI_INTERRUPT_REGISTER and SPI_INTERRUPT_ENABLE_REGISTER Bits +pub(crate) const IRQ_DATA_UNAVAILABLE: u16 = 0x0001; // Requested data not available; Clear by writing a "1" +pub(crate) const IRQ_F2_F3_FIFO_RD_UNDERFLOW: u16 = 0x0002; +pub(crate) const IRQ_F2_F3_FIFO_WR_OVERFLOW: u16 = 0x0004; +pub(crate) const IRQ_COMMAND_ERROR: u16 = 0x0008; // Cleared by writing 1 +pub(crate) const IRQ_DATA_ERROR: u16 = 0x0010; // Cleared by writing 1 +pub(crate) const IRQ_F2_PACKET_AVAILABLE: u16 = 0x0020; +pub(crate) const IRQ_F3_PACKET_AVAILABLE: u16 = 0x0040; +pub(crate) const IRQ_F1_OVERFLOW: u16 = 0x0080; // Due to last write. Bkplane has pending write requests +pub(crate) const IRQ_MISC_INTR0: u16 = 0x0100; +pub(crate) const IRQ_MISC_INTR1: u16 = 0x0200; +pub(crate) const IRQ_MISC_INTR2: u16 = 0x0400; +pub(crate) const IRQ_MISC_INTR3: u16 = 0x0800; +pub(crate) const IRQ_MISC_INTR4: u16 = 0x1000; +pub(crate) const IRQ_F1_INTR: u16 = 0x2000; +pub(crate) const IRQ_F2_INTR: u16 = 0x4000; +pub(crate) const IRQ_F3_INTR: u16 = 0x8000; + +pub(crate) const IOCTL_CMD_UP: u32 = 2; +pub(crate) const IOCTL_CMD_DOWN: u32 = 3; +pub(crate) const IOCTL_CMD_SET_SSID: u32 = 26; +pub(crate) const IOCTL_CMD_SET_CHANNEL: u32 = 30; +pub(crate) const IOCTL_CMD_ANTDIV: u32 = 64; +pub(crate) const IOCTL_CMD_SET_AP: u32 = 118; +pub(crate) const IOCTL_CMD_SET_VAR: u32 = 263; +pub(crate) const IOCTL_CMD_GET_VAR: u32 = 262; +pub(crate) const IOCTL_CMD_SET_PASSPHRASE: u32 = 268; + +pub(crate) const CHANNEL_TYPE_CONTROL: u8 = 0; +pub(crate) const CHANNEL_TYPE_EVENT: u8 = 1; +pub(crate) const CHANNEL_TYPE_DATA: u8 = 2; + +// CYW_SPID command structure constants. +pub(crate) const WRITE: bool = true; +pub(crate) const READ: bool = false; +pub(crate) const INC_ADDR: bool = true; +pub(crate) const FIXED_ADDR: bool = false; + +pub(crate) const AES_ENABLED: u32 = 0x0004; +pub(crate) const WPA2_SECURITY: u32 = 0x00400000; + +pub(crate) const MIN_PSK_LEN: usize = 8; +pub(crate) const MAX_PSK_LEN: usize = 64; + +// Security type (authentication and encryption types are combined using bit mask) +#[allow(non_camel_case_types)] +#[derive(Copy, Clone, PartialEq)] +#[repr(u32)] +pub(crate) enum Security { + OPEN = 0, + WPA2_AES_PSK = WPA2_SECURITY | AES_ENABLED, +} + +#[allow(non_camel_case_types)] +#[derive(Copy, Clone)] +#[repr(u8)] +pub enum EStatus { + /// operation was successful + SUCCESS = 0, + /// operation failed + FAIL = 1, + /// operation timed out + TIMEOUT = 2, + /// failed due to no matching network found + NO_NETWORKS = 3, + /// operation was aborted + ABORT = 4, + /// protocol failure: packet not ack'd + NO_ACK = 5, + /// AUTH or ASSOC packet was unsolicited + UNSOLICITED = 6, + /// attempt to assoc to an auto auth configuration + ATTEMPT = 7, + /// scan results are incomplete + PARTIAL = 8, + /// scan aborted by another scan + NEWSCAN = 9, + /// scan aborted due to assoc in progress + NEWASSOC = 10, + /// 802.11h quiet period started + _11HQUIET = 11, + /// user disabled scanning (WLC_SET_SCANSUPPRESS) + SUPPRESS = 12, + /// no allowable channels to scan + NOCHANS = 13, + /// scan aborted due to CCX fast roam + CCXFASTRM = 14, + /// abort channel select + CS_ABORT = 15, +} + +impl PartialEq for u32 { + fn eq(&self, other: &EStatus) -> bool { + *self == *other as Self + } +} + +#[allow(dead_code)] +pub(crate) struct FormatStatus(pub u32); + +#[cfg(feature = "defmt")] +impl defmt::Format for FormatStatus { + fn format(&self, fmt: defmt::Formatter) { + macro_rules! implm { + ($($name:ident),*) => { + $( + if self.0 & $name > 0 { + defmt::write!(fmt, " | {}", &stringify!($name)[7..]); + } + )* + }; + } + + implm!( + STATUS_DATA_NOT_AVAILABLE, + STATUS_UNDERFLOW, + STATUS_OVERFLOW, + STATUS_F2_INTR, + STATUS_F3_INTR, + STATUS_F2_RX_READY, + STATUS_F3_RX_READY, + STATUS_HOST_CMD_DATA_ERR, + STATUS_F2_PKT_AVAILABLE, + STATUS_F3_PKT_AVAILABLE + ); + } +} + +#[cfg(feature = "log")] +impl core::fmt::Debug for FormatStatus { + fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { + macro_rules! implm { + ($($name:ident),*) => { + $( + if self.0 & $name > 0 { + core::write!(fmt, " | {}", &stringify!($name)[7..])?; + } + )* + }; + } + + implm!( + STATUS_DATA_NOT_AVAILABLE, + STATUS_UNDERFLOW, + STATUS_OVERFLOW, + STATUS_F2_INTR, + STATUS_F3_INTR, + STATUS_F2_RX_READY, + STATUS_F3_RX_READY, + STATUS_HOST_CMD_DATA_ERR, + STATUS_F2_PKT_AVAILABLE, + STATUS_F3_PKT_AVAILABLE + ); + Ok(()) + } +} + +#[cfg(feature = "log")] +impl core::fmt::Display for FormatStatus { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Debug::fmt(self, f) + } +} + +#[allow(dead_code)] +pub(crate) struct FormatInterrupt(pub u16); + +#[cfg(feature = "defmt")] +impl defmt::Format for FormatInterrupt { + fn format(&self, fmt: defmt::Formatter) { + macro_rules! implm { + ($($name:ident),*) => { + $( + if self.0 & $name > 0 { + defmt::write!(fmt, " | {}", &stringify!($name)[4..]); + } + )* + }; + } + + implm!( + IRQ_DATA_UNAVAILABLE, + IRQ_F2_F3_FIFO_RD_UNDERFLOW, + IRQ_F2_F3_FIFO_WR_OVERFLOW, + IRQ_COMMAND_ERROR, + IRQ_DATA_ERROR, + IRQ_F2_PACKET_AVAILABLE, + IRQ_F3_PACKET_AVAILABLE, + IRQ_F1_OVERFLOW, + IRQ_MISC_INTR0, + IRQ_MISC_INTR1, + IRQ_MISC_INTR2, + IRQ_MISC_INTR3, + IRQ_MISC_INTR4, + IRQ_F1_INTR, + IRQ_F2_INTR, + IRQ_F3_INTR + ); + } +} + +#[cfg(feature = "log")] +impl core::fmt::Debug for FormatInterrupt { + fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { + macro_rules! implm { + ($($name:ident),*) => { + $( + if self.0 & $name > 0 { + core::write!(fmt, " | {}", &stringify!($name)[7..])?; + } + )* + }; + } + + implm!( + IRQ_DATA_UNAVAILABLE, + IRQ_F2_F3_FIFO_RD_UNDERFLOW, + IRQ_F2_F3_FIFO_WR_OVERFLOW, + IRQ_COMMAND_ERROR, + IRQ_DATA_ERROR, + IRQ_F2_PACKET_AVAILABLE, + IRQ_F3_PACKET_AVAILABLE, + IRQ_F1_OVERFLOW, + IRQ_MISC_INTR0, + IRQ_MISC_INTR1, + IRQ_MISC_INTR2, + IRQ_MISC_INTR3, + IRQ_MISC_INTR4, + IRQ_F1_INTR, + IRQ_F2_INTR, + IRQ_F3_INTR + ); + Ok(()) + } +} + +#[cfg(feature = "log")] +impl core::fmt::Display for FormatInterrupt { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Debug::fmt(self, f) + } +} diff --git a/cyw43/src/control.rs b/cyw43/src/control.rs new file mode 100644 index 000000000..c67614dd6 --- /dev/null +++ b/cyw43/src/control.rs @@ -0,0 +1,454 @@ +use core::cmp::{max, min}; + +use ch::driver::LinkState; +use embassy_net_driver_channel as ch; +use embassy_time::{Duration, Timer}; + +pub use crate::bus::SpiBusCyw43; +use crate::consts::*; +use crate::events::{Event, EventSubscriber, Events}; +use crate::fmt::Bytes; +use crate::ioctl::{IoctlState, IoctlType}; +use crate::structs::*; +use crate::{countries, events, PowerManagementMode}; + +#[derive(Debug)] +pub struct Error { + pub status: u32, +} + +pub struct Control<'a> { + state_ch: ch::StateRunner<'a>, + events: &'a Events, + ioctl_state: &'a IoctlState, +} + +impl<'a> Control<'a> { + pub(crate) fn new(state_ch: ch::StateRunner<'a>, event_sub: &'a Events, ioctl_state: &'a IoctlState) -> Self { + Self { + state_ch, + events: event_sub, + ioctl_state, + } + } + + pub async fn init(&mut self, clm: &[u8]) { + const CHUNK_SIZE: usize = 1024; + + debug!("Downloading CLM..."); + + let mut offs = 0; + for chunk in clm.chunks(CHUNK_SIZE) { + let mut flag = DOWNLOAD_FLAG_HANDLER_VER; + if offs == 0 { + flag |= DOWNLOAD_FLAG_BEGIN; + } + offs += chunk.len(); + if offs == clm.len() { + flag |= DOWNLOAD_FLAG_END; + } + + let header = DownloadHeader { + flag, + dload_type: DOWNLOAD_TYPE_CLM, + len: chunk.len() as _, + crc: 0, + }; + let mut buf = [0; 8 + 12 + CHUNK_SIZE]; + buf[0..8].copy_from_slice(b"clmload\x00"); + buf[8..20].copy_from_slice(&header.to_bytes()); + buf[20..][..chunk.len()].copy_from_slice(&chunk); + self.ioctl(IoctlType::Set, IOCTL_CMD_SET_VAR, 0, &mut buf[..8 + 12 + chunk.len()]) + .await; + } + + // check clmload ok + assert_eq!(self.get_iovar_u32("clmload_status").await, 0); + + debug!("Configuring misc stuff..."); + + // Disable tx gloming which transfers multiple packets in one request. + // 'glom' is short for "conglomerate" which means "gather together into + // a compact mass". + self.set_iovar_u32("bus:txglom", 0).await; + self.set_iovar_u32("apsta", 1).await; + + // read MAC addr. + let mut mac_addr = [0; 6]; + assert_eq!(self.get_iovar("cur_etheraddr", &mut mac_addr).await, 6); + debug!("mac addr: {:02x}", Bytes(&mac_addr)); + + let country = countries::WORLD_WIDE_XX; + let country_info = CountryInfo { + country_abbrev: [country.code[0], country.code[1], 0, 0], + country_code: [country.code[0], country.code[1], 0, 0], + rev: if country.rev == 0 { -1 } else { country.rev as _ }, + }; + self.set_iovar("country", &country_info.to_bytes()).await; + + // set country takes some time, next ioctls fail if we don't wait. + Timer::after(Duration::from_millis(100)).await; + + // Set antenna to chip antenna + self.ioctl_set_u32(IOCTL_CMD_ANTDIV, 0, 0).await; + + self.set_iovar_u32("bus:txglom", 0).await; + Timer::after(Duration::from_millis(100)).await; + //self.set_iovar_u32("apsta", 1).await; // this crashes, also we already did it before...?? + //Timer::after(Duration::from_millis(100)).await; + self.set_iovar_u32("ampdu_ba_wsize", 8).await; + Timer::after(Duration::from_millis(100)).await; + self.set_iovar_u32("ampdu_mpdu", 4).await; + Timer::after(Duration::from_millis(100)).await; + //self.set_iovar_u32("ampdu_rx_factor", 0).await; // this crashes + + //Timer::after(Duration::from_millis(100)).await; + + // evts + let mut evts = EventMask { + iface: 0, + events: [0xFF; 24], + }; + + // Disable spammy uninteresting events. + evts.unset(Event::RADIO); + evts.unset(Event::IF); + evts.unset(Event::PROBREQ_MSG); + evts.unset(Event::PROBREQ_MSG_RX); + evts.unset(Event::PROBRESP_MSG); + evts.unset(Event::PROBRESP_MSG); + evts.unset(Event::ROAM); + + self.set_iovar("bsscfg:event_msgs", &evts.to_bytes()).await; + + Timer::after(Duration::from_millis(100)).await; + + // set wifi up + self.ioctl(IoctlType::Set, IOCTL_CMD_UP, 0, &mut []).await; + + Timer::after(Duration::from_millis(100)).await; + + self.ioctl_set_u32(110, 0, 1).await; // SET_GMODE = auto + self.ioctl_set_u32(142, 0, 0).await; // SET_BAND = any + + Timer::after(Duration::from_millis(100)).await; + + self.state_ch.set_ethernet_address(mac_addr); + + debug!("INIT DONE"); + } + + pub async fn set_power_management(&mut self, mode: PowerManagementMode) { + // power save mode + let mode_num = mode.mode(); + if mode_num == 2 { + self.set_iovar_u32("pm2_sleep_ret", mode.sleep_ret_ms() as u32).await; + self.set_iovar_u32("bcn_li_bcn", mode.beacon_period() as u32).await; + self.set_iovar_u32("bcn_li_dtim", mode.dtim_period() as u32).await; + self.set_iovar_u32("assoc_listen", mode.assoc() as u32).await; + } + self.ioctl_set_u32(86, 0, mode_num).await; + } + + pub async fn join_open(&mut self, ssid: &str) -> Result<(), Error> { + self.set_iovar_u32("ampdu_ba_wsize", 8).await; + + self.ioctl_set_u32(134, 0, 0).await; // wsec = open + self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 0).await; + self.ioctl_set_u32(20, 0, 1).await; // set_infra = 1 + self.ioctl_set_u32(22, 0, 0).await; // set_auth = open (0) + + let mut i = SsidInfo { + len: ssid.len() as _, + ssid: [0; 32], + }; + i.ssid[..ssid.len()].copy_from_slice(ssid.as_bytes()); + + self.wait_for_join(i).await + } + + pub async fn join_wpa2(&mut self, ssid: &str, passphrase: &str) -> Result<(), Error> { + self.set_iovar_u32("ampdu_ba_wsize", 8).await; + + self.ioctl_set_u32(134, 0, 4).await; // wsec = wpa2 + self.set_iovar_u32x2("bsscfg:sup_wpa", 0, 1).await; + self.set_iovar_u32x2("bsscfg:sup_wpa2_eapver", 0, 0xFFFF_FFFF).await; + self.set_iovar_u32x2("bsscfg:sup_wpa_tmo", 0, 2500).await; + + Timer::after(Duration::from_millis(100)).await; + + let mut pfi = PassphraseInfo { + len: passphrase.len() as _, + flags: 1, + passphrase: [0; 64], + }; + pfi.passphrase[..passphrase.len()].copy_from_slice(passphrase.as_bytes()); + self.ioctl(IoctlType::Set, IOCTL_CMD_SET_PASSPHRASE, 0, &mut pfi.to_bytes()) + .await; // WLC_SET_WSEC_PMK + + self.ioctl_set_u32(20, 0, 1).await; // set_infra = 1 + self.ioctl_set_u32(22, 0, 0).await; // set_auth = 0 (open) + self.ioctl_set_u32(165, 0, 0x80).await; // set_wpa_auth + + let mut i = SsidInfo { + len: ssid.len() as _, + ssid: [0; 32], + }; + i.ssid[..ssid.len()].copy_from_slice(ssid.as_bytes()); + + self.wait_for_join(i).await + } + + async fn wait_for_join(&mut self, i: SsidInfo) -> Result<(), Error> { + self.events.mask.enable(&[Event::SET_SSID, Event::AUTH]); + let mut subscriber = self.events.queue.subscriber().unwrap(); + // the actual join operation starts here + // we make sure to enable events before so we don't miss any + + // set_ssid + self.ioctl(IoctlType::Set, IOCTL_CMD_SET_SSID, 0, &mut i.to_bytes()) + .await; + + // to complete the join, we wait for a SET_SSID event + // we also save the AUTH status for the user, it may be interesting + let mut auth_status = 0; + let status = loop { + let msg = subscriber.next_message_pure().await; + if msg.header.event_type == Event::AUTH && msg.header.status != EStatus::SUCCESS { + auth_status = msg.header.status; + } else if msg.header.event_type == Event::SET_SSID { + // join operation ends with SET_SSID event + break msg.header.status; + } + }; + + self.events.mask.disable_all(); + if status == EStatus::SUCCESS { + // successful join + self.state_ch.set_link_state(LinkState::Up); + debug!("JOINED"); + Ok(()) + } else { + warn!("JOIN failed with status={} auth={}", status, auth_status); + Err(Error { status }) + } + } + + pub async fn gpio_set(&mut self, gpio_n: u8, gpio_en: bool) { + assert!(gpio_n < 3); + self.set_iovar_u32x2("gpioout", 1 << gpio_n, if gpio_en { 1 << gpio_n } else { 0 }) + .await + } + + pub async fn start_ap_open(&mut self, ssid: &str, channel: u8) { + self.start_ap(ssid, "", Security::OPEN, channel).await; + } + + pub async fn start_ap_wpa2(&mut self, ssid: &str, passphrase: &str, channel: u8) { + self.start_ap(ssid, passphrase, Security::WPA2_AES_PSK, channel).await; + } + + async fn start_ap(&mut self, ssid: &str, passphrase: &str, security: Security, channel: u8) { + if security != Security::OPEN + && (passphrase.as_bytes().len() < MIN_PSK_LEN || passphrase.as_bytes().len() > MAX_PSK_LEN) + { + panic!("Passphrase is too short or too long"); + } + + // Temporarily set wifi down + self.ioctl(IoctlType::Set, IOCTL_CMD_DOWN, 0, &mut []).await; + + // Turn off APSTA mode + self.set_iovar_u32("apsta", 0).await; + + // Set wifi up again + self.ioctl(IoctlType::Set, IOCTL_CMD_UP, 0, &mut []).await; + + // Turn on AP mode + self.ioctl_set_u32(IOCTL_CMD_SET_AP, 0, 1).await; + + // Set SSID + let mut i = SsidInfoWithIndex { + index: 0, + ssid_info: SsidInfo { + len: ssid.as_bytes().len() as _, + ssid: [0; 32], + }, + }; + i.ssid_info.ssid[..ssid.as_bytes().len()].copy_from_slice(ssid.as_bytes()); + self.set_iovar("bsscfg:ssid", &i.to_bytes()).await; + + // Set channel number + self.ioctl_set_u32(IOCTL_CMD_SET_CHANNEL, 0, channel as u32).await; + + // Set security + self.set_iovar_u32x2("bsscfg:wsec", 0, (security as u32) & 0xFF).await; + + if security != Security::OPEN { + self.set_iovar_u32x2("bsscfg:wpa_auth", 0, 0x0084).await; // wpa_auth = WPA2_AUTH_PSK | WPA_AUTH_PSK + + Timer::after(Duration::from_millis(100)).await; + + // Set passphrase + let mut pfi = PassphraseInfo { + len: passphrase.as_bytes().len() as _, + flags: 1, // WSEC_PASSPHRASE + passphrase: [0; 64], + }; + pfi.passphrase[..passphrase.as_bytes().len()].copy_from_slice(passphrase.as_bytes()); + self.ioctl(IoctlType::Set, IOCTL_CMD_SET_PASSPHRASE, 0, &mut pfi.to_bytes()) + .await; + } + + // Change mutlicast rate from 1 Mbps to 11 Mbps + self.set_iovar_u32("2g_mrate", 11000000 / 500000).await; + + // Start AP + self.set_iovar_u32x2("bss", 0, 1).await; // bss = BSS_UP + } + + async fn set_iovar_u32x2(&mut self, name: &str, val1: u32, val2: u32) { + let mut buf = [0; 8]; + buf[0..4].copy_from_slice(&val1.to_le_bytes()); + buf[4..8].copy_from_slice(&val2.to_le_bytes()); + self.set_iovar(name, &buf).await + } + + async fn set_iovar_u32(&mut self, name: &str, val: u32) { + self.set_iovar(name, &val.to_le_bytes()).await + } + + async fn get_iovar_u32(&mut self, name: &str) -> u32 { + let mut buf = [0; 4]; + let len = self.get_iovar(name, &mut buf).await; + assert_eq!(len, 4); + u32::from_le_bytes(buf) + } + + async fn set_iovar(&mut self, name: &str, val: &[u8]) { + self.set_iovar_v::<64>(name, val).await + } + + async fn set_iovar_v(&mut self, name: &str, val: &[u8]) { + debug!("set {} = {:02x}", name, Bytes(val)); + + let mut buf = [0; BUFSIZE]; + buf[..name.len()].copy_from_slice(name.as_bytes()); + buf[name.len()] = 0; + buf[name.len() + 1..][..val.len()].copy_from_slice(val); + + let total_len = name.len() + 1 + val.len(); + self.ioctl(IoctlType::Set, IOCTL_CMD_SET_VAR, 0, &mut buf[..total_len]) + .await; + } + + // TODO this is not really working, it always returns all zeros. + async fn get_iovar(&mut self, name: &str, res: &mut [u8]) -> usize { + debug!("get {}", name); + + let mut buf = [0; 64]; + buf[..name.len()].copy_from_slice(name.as_bytes()); + buf[name.len()] = 0; + + let total_len = max(name.len() + 1, res.len()); + let res_len = self + .ioctl(IoctlType::Get, IOCTL_CMD_GET_VAR, 0, &mut buf[..total_len]) + .await; + + let out_len = min(res.len(), res_len); + res[..out_len].copy_from_slice(&buf[..out_len]); + out_len + } + + async fn ioctl_set_u32(&mut self, cmd: u32, iface: u32, val: u32) { + let mut buf = val.to_le_bytes(); + self.ioctl(IoctlType::Set, cmd, iface, &mut buf).await; + } + + async fn ioctl(&mut self, kind: IoctlType, cmd: u32, iface: u32, buf: &mut [u8]) -> usize { + struct CancelOnDrop<'a>(&'a IoctlState); + + impl CancelOnDrop<'_> { + fn defuse(self) { + core::mem::forget(self); + } + } + + impl Drop for CancelOnDrop<'_> { + fn drop(&mut self) { + self.0.cancel_ioctl(); + } + } + + let ioctl = CancelOnDrop(self.ioctl_state); + let resp_len = ioctl.0.do_ioctl(kind, cmd, iface, buf).await; + ioctl.defuse(); + + resp_len + } + + /// Start a wifi scan + /// + /// Returns a `Stream` of networks found by the device + /// + /// # Note + /// Device events are currently implemented using a bounded queue. + /// To not miss any events, you should make sure to always await the stream. + pub async fn scan(&mut self) -> Scanner<'_> { + const SCANTYPE_PASSIVE: u8 = 1; + + let scan_params = ScanParams { + version: 1, + action: 1, + sync_id: 1, + ssid_len: 0, + ssid: [0; 32], + bssid: [0xff; 6], + bss_type: 2, + scan_type: SCANTYPE_PASSIVE, + nprobes: !0, + active_time: !0, + passive_time: !0, + home_time: !0, + channel_num: 0, + channel_list: [0; 1], + }; + + self.events.mask.enable(&[Event::ESCAN_RESULT]); + let subscriber = self.events.queue.subscriber().unwrap(); + self.set_iovar_v::<256>("escan", &scan_params.to_bytes()).await; + + Scanner { + subscriber, + events: &self.events, + } + } +} + +pub struct Scanner<'a> { + subscriber: EventSubscriber<'a>, + events: &'a Events, +} + +impl Scanner<'_> { + /// wait for the next found network + pub async fn next(&mut self) -> Option { + let event = self.subscriber.next_message_pure().await; + if event.header.status != EStatus::PARTIAL { + self.events.mask.disable_all(); + return None; + } + + if let events::Payload::BssInfo(bss) = event.payload { + Some(bss) + } else { + None + } + } +} + +impl Drop for Scanner<'_> { + fn drop(&mut self) { + self.events.mask.disable_all(); + } +} diff --git a/cyw43/src/countries.rs b/cyw43/src/countries.rs new file mode 100644 index 000000000..fa1e8cace --- /dev/null +++ b/cyw43/src/countries.rs @@ -0,0 +1,481 @@ +#![allow(unused)] + +pub struct Country { + pub code: [u8; 2], + pub rev: u16, +} + +/// AF Afghanistan +pub const AFGHANISTAN: Country = Country { code: *b"AF", rev: 0 }; +/// AL Albania +pub const ALBANIA: Country = Country { code: *b"AL", rev: 0 }; +/// DZ Algeria +pub const ALGERIA: Country = Country { code: *b"DZ", rev: 0 }; +/// AS American_Samoa +pub const AMERICAN_SAMOA: Country = Country { code: *b"AS", rev: 0 }; +/// AO Angola +pub const ANGOLA: Country = Country { code: *b"AO", rev: 0 }; +/// AI Anguilla +pub const ANGUILLA: Country = Country { code: *b"AI", rev: 0 }; +/// AG Antigua_and_Barbuda +pub const ANTIGUA_AND_BARBUDA: Country = Country { code: *b"AG", rev: 0 }; +/// AR Argentina +pub const ARGENTINA: Country = Country { code: *b"AR", rev: 0 }; +/// AM Armenia +pub const ARMENIA: Country = Country { code: *b"AM", rev: 0 }; +/// AW Aruba +pub const ARUBA: Country = Country { code: *b"AW", rev: 0 }; +/// AU Australia +pub const AUSTRALIA: Country = Country { code: *b"AU", rev: 0 }; +/// AT Austria +pub const AUSTRIA: Country = Country { code: *b"AT", rev: 0 }; +/// AZ Azerbaijan +pub const AZERBAIJAN: Country = Country { code: *b"AZ", rev: 0 }; +/// BS Bahamas +pub const BAHAMAS: Country = Country { code: *b"BS", rev: 0 }; +/// BH Bahrain +pub const BAHRAIN: Country = Country { code: *b"BH", rev: 0 }; +/// 0B Baker_Island +pub const BAKER_ISLAND: Country = Country { code: *b"0B", rev: 0 }; +/// BD Bangladesh +pub const BANGLADESH: Country = Country { code: *b"BD", rev: 0 }; +/// BB Barbados +pub const BARBADOS: Country = Country { code: *b"BB", rev: 0 }; +/// BY Belarus +pub const BELARUS: Country = Country { code: *b"BY", rev: 0 }; +/// BE Belgium +pub const BELGIUM: Country = Country { code: *b"BE", rev: 0 }; +/// BZ Belize +pub const BELIZE: Country = Country { code: *b"BZ", rev: 0 }; +/// BJ Benin +pub const BENIN: Country = Country { code: *b"BJ", rev: 0 }; +/// BM Bermuda +pub const BERMUDA: Country = Country { code: *b"BM", rev: 0 }; +/// BT Bhutan +pub const BHUTAN: Country = Country { code: *b"BT", rev: 0 }; +/// BO Bolivia +pub const BOLIVIA: Country = Country { code: *b"BO", rev: 0 }; +/// BA Bosnia_and_Herzegovina +pub const BOSNIA_AND_HERZEGOVINA: Country = Country { code: *b"BA", rev: 0 }; +/// BW Botswana +pub const BOTSWANA: Country = Country { code: *b"BW", rev: 0 }; +/// BR Brazil +pub const BRAZIL: Country = Country { code: *b"BR", rev: 0 }; +/// IO British_Indian_Ocean_Territory +pub const BRITISH_INDIAN_OCEAN_TERRITORY: Country = Country { code: *b"IO", rev: 0 }; +/// BN Brunei_Darussalam +pub const BRUNEI_DARUSSALAM: Country = Country { code: *b"BN", rev: 0 }; +/// BG Bulgaria +pub const BULGARIA: Country = Country { code: *b"BG", rev: 0 }; +/// BF Burkina_Faso +pub const BURKINA_FASO: Country = Country { code: *b"BF", rev: 0 }; +/// BI Burundi +pub const BURUNDI: Country = Country { code: *b"BI", rev: 0 }; +/// KH Cambodia +pub const CAMBODIA: Country = Country { code: *b"KH", rev: 0 }; +/// CM Cameroon +pub const CAMEROON: Country = Country { code: *b"CM", rev: 0 }; +/// CA Canada +pub const CANADA: Country = Country { code: *b"CA", rev: 0 }; +/// CA Canada Revision 950 +pub const CANADA_REV950: Country = Country { code: *b"CA", rev: 950 }; +/// CV Cape_Verde +pub const CAPE_VERDE: Country = Country { code: *b"CV", rev: 0 }; +/// KY Cayman_Islands +pub const CAYMAN_ISLANDS: Country = Country { code: *b"KY", rev: 0 }; +/// CF Central_African_Republic +pub const CENTRAL_AFRICAN_REPUBLIC: Country = Country { code: *b"CF", rev: 0 }; +/// TD Chad +pub const CHAD: Country = Country { code: *b"TD", rev: 0 }; +/// CL Chile +pub const CHILE: Country = Country { code: *b"CL", rev: 0 }; +/// CN China +pub const CHINA: Country = Country { code: *b"CN", rev: 0 }; +/// CX Christmas_Island +pub const CHRISTMAS_ISLAND: Country = Country { code: *b"CX", rev: 0 }; +/// CO Colombia +pub const COLOMBIA: Country = Country { code: *b"CO", rev: 0 }; +/// KM Comoros +pub const COMOROS: Country = Country { code: *b"KM", rev: 0 }; +/// CG Congo +pub const CONGO: Country = Country { code: *b"CG", rev: 0 }; +/// CD Congo,_The_Democratic_Republic_Of_The +pub const CONGO_THE_DEMOCRATIC_REPUBLIC_OF_THE: Country = Country { code: *b"CD", rev: 0 }; +/// CR Costa_Rica +pub const COSTA_RICA: Country = Country { code: *b"CR", rev: 0 }; +/// CI Cote_D'ivoire +pub const COTE_DIVOIRE: Country = Country { code: *b"CI", rev: 0 }; +/// HR Croatia +pub const CROATIA: Country = Country { code: *b"HR", rev: 0 }; +/// CU Cuba +pub const CUBA: Country = Country { code: *b"CU", rev: 0 }; +/// CY Cyprus +pub const CYPRUS: Country = Country { code: *b"CY", rev: 0 }; +/// CZ Czech_Republic +pub const CZECH_REPUBLIC: Country = Country { code: *b"CZ", rev: 0 }; +/// DK Denmark +pub const DENMARK: Country = Country { code: *b"DK", rev: 0 }; +/// DJ Djibouti +pub const DJIBOUTI: Country = Country { code: *b"DJ", rev: 0 }; +/// DM Dominica +pub const DOMINICA: Country = Country { code: *b"DM", rev: 0 }; +/// DO Dominican_Republic +pub const DOMINICAN_REPUBLIC: Country = Country { code: *b"DO", rev: 0 }; +/// AU G'Day mate! +pub const DOWN_UNDER: Country = Country { code: *b"AU", rev: 0 }; +/// EC Ecuador +pub const ECUADOR: Country = Country { code: *b"EC", rev: 0 }; +/// EG Egypt +pub const EGYPT: Country = Country { code: *b"EG", rev: 0 }; +/// SV El_Salvador +pub const EL_SALVADOR: Country = Country { code: *b"SV", rev: 0 }; +/// GQ Equatorial_Guinea +pub const EQUATORIAL_GUINEA: Country = Country { code: *b"GQ", rev: 0 }; +/// ER Eritrea +pub const ERITREA: Country = Country { code: *b"ER", rev: 0 }; +/// EE Estonia +pub const ESTONIA: Country = Country { code: *b"EE", rev: 0 }; +/// ET Ethiopia +pub const ETHIOPIA: Country = Country { code: *b"ET", rev: 0 }; +/// FK Falkland_Islands_(Malvinas) +pub const FALKLAND_ISLANDS_MALVINAS: Country = Country { code: *b"FK", rev: 0 }; +/// FO Faroe_Islands +pub const FAROE_ISLANDS: Country = Country { code: *b"FO", rev: 0 }; +/// FJ Fiji +pub const FIJI: Country = Country { code: *b"FJ", rev: 0 }; +/// FI Finland +pub const FINLAND: Country = Country { code: *b"FI", rev: 0 }; +/// FR France +pub const FRANCE: Country = Country { code: *b"FR", rev: 0 }; +/// GF French_Guina +pub const FRENCH_GUINA: Country = Country { code: *b"GF", rev: 0 }; +/// PF French_Polynesia +pub const FRENCH_POLYNESIA: Country = Country { code: *b"PF", rev: 0 }; +/// TF French_Southern_Territories +pub const FRENCH_SOUTHERN_TERRITORIES: Country = Country { code: *b"TF", rev: 0 }; +/// GA Gabon +pub const GABON: Country = Country { code: *b"GA", rev: 0 }; +/// GM Gambia +pub const GAMBIA: Country = Country { code: *b"GM", rev: 0 }; +/// GE Georgia +pub const GEORGIA: Country = Country { code: *b"GE", rev: 0 }; +/// DE Germany +pub const GERMANY: Country = Country { code: *b"DE", rev: 0 }; +/// E0 European_Wide Revision 895 +pub const EUROPEAN_WIDE_REV895: Country = Country { code: *b"E0", rev: 895 }; +/// GH Ghana +pub const GHANA: Country = Country { code: *b"GH", rev: 0 }; +/// GI Gibraltar +pub const GIBRALTAR: Country = Country { code: *b"GI", rev: 0 }; +/// GR Greece +pub const GREECE: Country = Country { code: *b"GR", rev: 0 }; +/// GD Grenada +pub const GRENADA: Country = Country { code: *b"GD", rev: 0 }; +/// GP Guadeloupe +pub const GUADELOUPE: Country = Country { code: *b"GP", rev: 0 }; +/// GU Guam +pub const GUAM: Country = Country { code: *b"GU", rev: 0 }; +/// GT Guatemala +pub const GUATEMALA: Country = Country { code: *b"GT", rev: 0 }; +/// GG Guernsey +pub const GUERNSEY: Country = Country { code: *b"GG", rev: 0 }; +/// GN Guinea +pub const GUINEA: Country = Country { code: *b"GN", rev: 0 }; +/// GW Guinea-bissau +pub const GUINEA_BISSAU: Country = Country { code: *b"GW", rev: 0 }; +/// GY Guyana +pub const GUYANA: Country = Country { code: *b"GY", rev: 0 }; +/// HT Haiti +pub const HAITI: Country = Country { code: *b"HT", rev: 0 }; +/// VA Holy_See_(Vatican_City_State) +pub const HOLY_SEE_VATICAN_CITY_STATE: Country = Country { code: *b"VA", rev: 0 }; +/// HN Honduras +pub const HONDURAS: Country = Country { code: *b"HN", rev: 0 }; +/// HK Hong_Kong +pub const HONG_KONG: Country = Country { code: *b"HK", rev: 0 }; +/// HU Hungary +pub const HUNGARY: Country = Country { code: *b"HU", rev: 0 }; +/// IS Iceland +pub const ICELAND: Country = Country { code: *b"IS", rev: 0 }; +/// IN India +pub const INDIA: Country = Country { code: *b"IN", rev: 0 }; +/// ID Indonesia +pub const INDONESIA: Country = Country { code: *b"ID", rev: 0 }; +/// IR Iran,_Islamic_Republic_Of +pub const IRAN_ISLAMIC_REPUBLIC_OF: Country = Country { code: *b"IR", rev: 0 }; +/// IQ Iraq +pub const IRAQ: Country = Country { code: *b"IQ", rev: 0 }; +/// IE Ireland +pub const IRELAND: Country = Country { code: *b"IE", rev: 0 }; +/// IL Israel +pub const ISRAEL: Country = Country { code: *b"IL", rev: 0 }; +/// IT Italy +pub const ITALY: Country = Country { code: *b"IT", rev: 0 }; +/// JM Jamaica +pub const JAMAICA: Country = Country { code: *b"JM", rev: 0 }; +/// JP Japan +pub const JAPAN: Country = Country { code: *b"JP", rev: 0 }; +/// JE Jersey +pub const JERSEY: Country = Country { code: *b"JE", rev: 0 }; +/// JO Jordan +pub const JORDAN: Country = Country { code: *b"JO", rev: 0 }; +/// KZ Kazakhstan +pub const KAZAKHSTAN: Country = Country { code: *b"KZ", rev: 0 }; +/// KE Kenya +pub const KENYA: Country = Country { code: *b"KE", rev: 0 }; +/// KI Kiribati +pub const KIRIBATI: Country = Country { code: *b"KI", rev: 0 }; +/// KR Korea,_Republic_Of +pub const KOREA_REPUBLIC_OF: Country = Country { code: *b"KR", rev: 1 }; +/// 0A Kosovo +pub const KOSOVO: Country = Country { code: *b"0A", rev: 0 }; +/// KW Kuwait +pub const KUWAIT: Country = Country { code: *b"KW", rev: 0 }; +/// KG Kyrgyzstan +pub const KYRGYZSTAN: Country = Country { code: *b"KG", rev: 0 }; +/// LA Lao_People's_Democratic_Repubic +pub const LAO_PEOPLES_DEMOCRATIC_REPUBIC: Country = Country { code: *b"LA", rev: 0 }; +/// LV Latvia +pub const LATVIA: Country = Country { code: *b"LV", rev: 0 }; +/// LB Lebanon +pub const LEBANON: Country = Country { code: *b"LB", rev: 0 }; +/// LS Lesotho +pub const LESOTHO: Country = Country { code: *b"LS", rev: 0 }; +/// LR Liberia +pub const LIBERIA: Country = Country { code: *b"LR", rev: 0 }; +/// LY Libyan_Arab_Jamahiriya +pub const LIBYAN_ARAB_JAMAHIRIYA: Country = Country { code: *b"LY", rev: 0 }; +/// LI Liechtenstein +pub const LIECHTENSTEIN: Country = Country { code: *b"LI", rev: 0 }; +/// LT Lithuania +pub const LITHUANIA: Country = Country { code: *b"LT", rev: 0 }; +/// LU Luxembourg +pub const LUXEMBOURG: Country = Country { code: *b"LU", rev: 0 }; +/// MO Macao +pub const MACAO: Country = Country { code: *b"MO", rev: 0 }; +/// MK Macedonia,_Former_Yugoslav_Republic_Of +pub const MACEDONIA_FORMER_YUGOSLAV_REPUBLIC_OF: Country = Country { code: *b"MK", rev: 0 }; +/// MG Madagascar +pub const MADAGASCAR: Country = Country { code: *b"MG", rev: 0 }; +/// MW Malawi +pub const MALAWI: Country = Country { code: *b"MW", rev: 0 }; +/// MY Malaysia +pub const MALAYSIA: Country = Country { code: *b"MY", rev: 0 }; +/// MV Maldives +pub const MALDIVES: Country = Country { code: *b"MV", rev: 0 }; +/// ML Mali +pub const MALI: Country = Country { code: *b"ML", rev: 0 }; +/// MT Malta +pub const MALTA: Country = Country { code: *b"MT", rev: 0 }; +/// IM Man,_Isle_Of +pub const MAN_ISLE_OF: Country = Country { code: *b"IM", rev: 0 }; +/// MQ Martinique +pub const MARTINIQUE: Country = Country { code: *b"MQ", rev: 0 }; +/// MR Mauritania +pub const MAURITANIA: Country = Country { code: *b"MR", rev: 0 }; +/// MU Mauritius +pub const MAURITIUS: Country = Country { code: *b"MU", rev: 0 }; +/// YT Mayotte +pub const MAYOTTE: Country = Country { code: *b"YT", rev: 0 }; +/// MX Mexico +pub const MEXICO: Country = Country { code: *b"MX", rev: 0 }; +/// FM Micronesia,_Federated_States_Of +pub const MICRONESIA_FEDERATED_STATES_OF: Country = Country { code: *b"FM", rev: 0 }; +/// MD Moldova,_Republic_Of +pub const MOLDOVA_REPUBLIC_OF: Country = Country { code: *b"MD", rev: 0 }; +/// MC Monaco +pub const MONACO: Country = Country { code: *b"MC", rev: 0 }; +/// MN Mongolia +pub const MONGOLIA: Country = Country { code: *b"MN", rev: 0 }; +/// ME Montenegro +pub const MONTENEGRO: Country = Country { code: *b"ME", rev: 0 }; +/// MS Montserrat +pub const MONTSERRAT: Country = Country { code: *b"MS", rev: 0 }; +/// MA Morocco +pub const MOROCCO: Country = Country { code: *b"MA", rev: 0 }; +/// MZ Mozambique +pub const MOZAMBIQUE: Country = Country { code: *b"MZ", rev: 0 }; +/// MM Myanmar +pub const MYANMAR: Country = Country { code: *b"MM", rev: 0 }; +/// NA Namibia +pub const NAMIBIA: Country = Country { code: *b"NA", rev: 0 }; +/// NR Nauru +pub const NAURU: Country = Country { code: *b"NR", rev: 0 }; +/// NP Nepal +pub const NEPAL: Country = Country { code: *b"NP", rev: 0 }; +/// NL Netherlands +pub const NETHERLANDS: Country = Country { code: *b"NL", rev: 0 }; +/// AN Netherlands_Antilles +pub const NETHERLANDS_ANTILLES: Country = Country { code: *b"AN", rev: 0 }; +/// NC New_Caledonia +pub const NEW_CALEDONIA: Country = Country { code: *b"NC", rev: 0 }; +/// NZ New_Zealand +pub const NEW_ZEALAND: Country = Country { code: *b"NZ", rev: 0 }; +/// NI Nicaragua +pub const NICARAGUA: Country = Country { code: *b"NI", rev: 0 }; +/// NE Niger +pub const NIGER: Country = Country { code: *b"NE", rev: 0 }; +/// NG Nigeria +pub const NIGERIA: Country = Country { code: *b"NG", rev: 0 }; +/// NF Norfolk_Island +pub const NORFOLK_ISLAND: Country = Country { code: *b"NF", rev: 0 }; +/// MP Northern_Mariana_Islands +pub const NORTHERN_MARIANA_ISLANDS: Country = Country { code: *b"MP", rev: 0 }; +/// NO Norway +pub const NORWAY: Country = Country { code: *b"NO", rev: 0 }; +/// OM Oman +pub const OMAN: Country = Country { code: *b"OM", rev: 0 }; +/// PK Pakistan +pub const PAKISTAN: Country = Country { code: *b"PK", rev: 0 }; +/// PW Palau +pub const PALAU: Country = Country { code: *b"PW", rev: 0 }; +/// PA Panama +pub const PANAMA: Country = Country { code: *b"PA", rev: 0 }; +/// PG Papua_New_Guinea +pub const PAPUA_NEW_GUINEA: Country = Country { code: *b"PG", rev: 0 }; +/// PY Paraguay +pub const PARAGUAY: Country = Country { code: *b"PY", rev: 0 }; +/// PE Peru +pub const PERU: Country = Country { code: *b"PE", rev: 0 }; +/// PH Philippines +pub const PHILIPPINES: Country = Country { code: *b"PH", rev: 0 }; +/// PL Poland +pub const POLAND: Country = Country { code: *b"PL", rev: 0 }; +/// PT Portugal +pub const PORTUGAL: Country = Country { code: *b"PT", rev: 0 }; +/// PR Pueto_Rico +pub const PUETO_RICO: Country = Country { code: *b"PR", rev: 0 }; +/// QA Qatar +pub const QATAR: Country = Country { code: *b"QA", rev: 0 }; +/// RE Reunion +pub const REUNION: Country = Country { code: *b"RE", rev: 0 }; +/// RO Romania +pub const ROMANIA: Country = Country { code: *b"RO", rev: 0 }; +/// RU Russian_Federation +pub const RUSSIAN_FEDERATION: Country = Country { code: *b"RU", rev: 0 }; +/// RW Rwanda +pub const RWANDA: Country = Country { code: *b"RW", rev: 0 }; +/// KN Saint_Kitts_and_Nevis +pub const SAINT_KITTS_AND_NEVIS: Country = Country { code: *b"KN", rev: 0 }; +/// LC Saint_Lucia +pub const SAINT_LUCIA: Country = Country { code: *b"LC", rev: 0 }; +/// PM Saint_Pierre_and_Miquelon +pub const SAINT_PIERRE_AND_MIQUELON: Country = Country { code: *b"PM", rev: 0 }; +/// VC Saint_Vincent_and_The_Grenadines +pub const SAINT_VINCENT_AND_THE_GRENADINES: Country = Country { code: *b"VC", rev: 0 }; +/// WS Samoa +pub const SAMOA: Country = Country { code: *b"WS", rev: 0 }; +/// MF Sanit_Martin_/_Sint_Marteen +pub const SANIT_MARTIN_SINT_MARTEEN: Country = Country { code: *b"MF", rev: 0 }; +/// ST Sao_Tome_and_Principe +pub const SAO_TOME_AND_PRINCIPE: Country = Country { code: *b"ST", rev: 0 }; +/// SA Saudi_Arabia +pub const SAUDI_ARABIA: Country = Country { code: *b"SA", rev: 0 }; +/// SN Senegal +pub const SENEGAL: Country = Country { code: *b"SN", rev: 0 }; +/// RS Serbia +pub const SERBIA: Country = Country { code: *b"RS", rev: 0 }; +/// SC Seychelles +pub const SEYCHELLES: Country = Country { code: *b"SC", rev: 0 }; +/// SL Sierra_Leone +pub const SIERRA_LEONE: Country = Country { code: *b"SL", rev: 0 }; +/// SG Singapore +pub const SINGAPORE: Country = Country { code: *b"SG", rev: 0 }; +/// SK Slovakia +pub const SLOVAKIA: Country = Country { code: *b"SK", rev: 0 }; +/// SI Slovenia +pub const SLOVENIA: Country = Country { code: *b"SI", rev: 0 }; +/// SB Solomon_Islands +pub const SOLOMON_ISLANDS: Country = Country { code: *b"SB", rev: 0 }; +/// SO Somalia +pub const SOMALIA: Country = Country { code: *b"SO", rev: 0 }; +/// ZA South_Africa +pub const SOUTH_AFRICA: Country = Country { code: *b"ZA", rev: 0 }; +/// ES Spain +pub const SPAIN: Country = Country { code: *b"ES", rev: 0 }; +/// LK Sri_Lanka +pub const SRI_LANKA: Country = Country { code: *b"LK", rev: 0 }; +/// SR Suriname +pub const SURINAME: Country = Country { code: *b"SR", rev: 0 }; +/// SZ Swaziland +pub const SWAZILAND: Country = Country { code: *b"SZ", rev: 0 }; +/// SE Sweden +pub const SWEDEN: Country = Country { code: *b"SE", rev: 0 }; +/// CH Switzerland +pub const SWITZERLAND: Country = Country { code: *b"CH", rev: 0 }; +/// SY Syrian_Arab_Republic +pub const SYRIAN_ARAB_REPUBLIC: Country = Country { code: *b"SY", rev: 0 }; +/// TW Taiwan,_Province_Of_China +pub const TAIWAN_PROVINCE_OF_CHINA: Country = Country { code: *b"TW", rev: 0 }; +/// TJ Tajikistan +pub const TAJIKISTAN: Country = Country { code: *b"TJ", rev: 0 }; +/// TZ Tanzania,_United_Republic_Of +pub const TANZANIA_UNITED_REPUBLIC_OF: Country = Country { code: *b"TZ", rev: 0 }; +/// TH Thailand +pub const THAILAND: Country = Country { code: *b"TH", rev: 0 }; +/// TG Togo +pub const TOGO: Country = Country { code: *b"TG", rev: 0 }; +/// TO Tonga +pub const TONGA: Country = Country { code: *b"TO", rev: 0 }; +/// TT Trinidad_and_Tobago +pub const TRINIDAD_AND_TOBAGO: Country = Country { code: *b"TT", rev: 0 }; +/// TN Tunisia +pub const TUNISIA: Country = Country { code: *b"TN", rev: 0 }; +/// TR Turkey +pub const TURKEY: Country = Country { code: *b"TR", rev: 0 }; +/// TM Turkmenistan +pub const TURKMENISTAN: Country = Country { code: *b"TM", rev: 0 }; +/// TC Turks_and_Caicos_Islands +pub const TURKS_AND_CAICOS_ISLANDS: Country = Country { code: *b"TC", rev: 0 }; +/// TV Tuvalu +pub const TUVALU: Country = Country { code: *b"TV", rev: 0 }; +/// UG Uganda +pub const UGANDA: Country = Country { code: *b"UG", rev: 0 }; +/// UA Ukraine +pub const UKRAINE: Country = Country { code: *b"UA", rev: 0 }; +/// AE United_Arab_Emirates +pub const UNITED_ARAB_EMIRATES: Country = Country { code: *b"AE", rev: 0 }; +/// GB United_Kingdom +pub const UNITED_KINGDOM: Country = Country { code: *b"GB", rev: 0 }; +/// US United_States +pub const UNITED_STATES: Country = Country { code: *b"US", rev: 0 }; +/// US United_States Revision 4 +pub const UNITED_STATES_REV4: Country = Country { code: *b"US", rev: 4 }; +/// Q1 United_States Revision 931 +pub const UNITED_STATES_REV931: Country = Country { code: *b"Q1", rev: 931 }; +/// Q2 United_States_(No_DFS) +pub const UNITED_STATES_NO_DFS: Country = Country { code: *b"Q2", rev: 0 }; +/// UM United_States_Minor_Outlying_Islands +pub const UNITED_STATES_MINOR_OUTLYING_ISLANDS: Country = Country { code: *b"UM", rev: 0 }; +/// UY Uruguay +pub const URUGUAY: Country = Country { code: *b"UY", rev: 0 }; +/// UZ Uzbekistan +pub const UZBEKISTAN: Country = Country { code: *b"UZ", rev: 0 }; +/// VU Vanuatu +pub const VANUATU: Country = Country { code: *b"VU", rev: 0 }; +/// VE Venezuela +pub const VENEZUELA: Country = Country { code: *b"VE", rev: 0 }; +/// VN Viet_Nam +pub const VIET_NAM: Country = Country { code: *b"VN", rev: 0 }; +/// VG Virgin_Islands,_British +pub const VIRGIN_ISLANDS_BRITISH: Country = Country { code: *b"VG", rev: 0 }; +/// VI Virgin_Islands,_U.S. +pub const VIRGIN_ISLANDS_US: Country = Country { code: *b"VI", rev: 0 }; +/// WF Wallis_and_Futuna +pub const WALLIS_AND_FUTUNA: Country = Country { code: *b"WF", rev: 0 }; +/// 0C West_Bank +pub const WEST_BANK: Country = Country { code: *b"0C", rev: 0 }; +/// EH Western_Sahara +pub const WESTERN_SAHARA: Country = Country { code: *b"EH", rev: 0 }; +/// Worldwide Locale Revision 983 +pub const WORLD_WIDE_XV_REV983: Country = Country { code: *b"XV", rev: 983 }; +/// Worldwide Locale (passive Ch12-14) +pub const WORLD_WIDE_XX: Country = Country { code: *b"XX", rev: 0 }; +/// Worldwide Locale (passive Ch12-14) Revision 17 +pub const WORLD_WIDE_XX_REV17: Country = Country { code: *b"XX", rev: 17 }; +/// YE Yemen +pub const YEMEN: Country = Country { code: *b"YE", rev: 0 }; +/// ZM Zambia +pub const ZAMBIA: Country = Country { code: *b"ZM", rev: 0 }; +/// ZW Zimbabwe +pub const ZIMBABWE: Country = Country { code: *b"ZW", rev: 0 }; diff --git a/cyw43/src/events.rs b/cyw43/src/events.rs new file mode 100644 index 000000000..a94c49a0c --- /dev/null +++ b/cyw43/src/events.rs @@ -0,0 +1,400 @@ +#![allow(dead_code)] +#![allow(non_camel_case_types)] + +use core::cell::RefCell; + +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::pubsub::{PubSubChannel, Subscriber}; + +use crate::structs::BssInfo; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, num_enum::FromPrimitive)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum Event { + #[num_enum(default)] + Unknown = 0xFF, + /// indicates status of set SSID + SET_SSID = 0, + /// differentiates join IBSS from found (START) IBSS + JOIN = 1, + /// STA founded an IBSS or AP started a BSS + START = 2, + /// 802.11 AUTH request + AUTH = 3, + /// 802.11 AUTH indication + AUTH_IND = 4, + /// 802.11 DEAUTH request + DEAUTH = 5, + /// 802.11 DEAUTH indication + DEAUTH_IND = 6, + /// 802.11 ASSOC request + ASSOC = 7, + /// 802.11 ASSOC indication + ASSOC_IND = 8, + /// 802.11 REASSOC request + REASSOC = 9, + /// 802.11 REASSOC indication + REASSOC_IND = 10, + /// 802.11 DISASSOC request + DISASSOC = 11, + /// 802.11 DISASSOC indication + DISASSOC_IND = 12, + /// 802.11h Quiet period started + QUIET_START = 13, + /// 802.11h Quiet period ended + QUIET_END = 14, + /// BEACONS received/lost indication + BEACON_RX = 15, + /// generic link indication + LINK = 16, + /// TKIP MIC error occurred + MIC_ERROR = 17, + /// NDIS style link indication + NDIS_LINK = 18, + /// roam attempt occurred: indicate status & reason + ROAM = 19, + /// change in dot11FailedCount (txfail) + TXFAIL = 20, + /// WPA2 pmkid cache indication + PMKID_CACHE = 21, + /// current AP's TSF value went backward + RETROGRADE_TSF = 22, + /// AP was pruned from join list for reason + PRUNE = 23, + /// report AutoAuth table entry match for join attempt + AUTOAUTH = 24, + /// Event encapsulating an EAPOL message + EAPOL_MSG = 25, + /// Scan results are ready or scan was aborted + SCAN_COMPLETE = 26, + /// indicate to host addts fail/success + ADDTS_IND = 27, + /// indicate to host delts fail/success + DELTS_IND = 28, + /// indicate to host of beacon transmit + BCNSENT_IND = 29, + /// Send the received beacon up to the host + BCNRX_MSG = 30, + /// indicate to host loss of beacon + BCNLOST_MSG = 31, + /// before attempting to roam + ROAM_PREP = 32, + /// PFN network found event + PFN_NET_FOUND = 33, + /// PFN network lost event + PFN_NET_LOST = 34, + RESET_COMPLETE = 35, + JOIN_START = 36, + ROAM_START = 37, + ASSOC_START = 38, + IBSS_ASSOC = 39, + RADIO = 40, + /// PSM microcode watchdog fired + PSM_WATCHDOG = 41, + /// CCX association start + CCX_ASSOC_START = 42, + /// CCX association abort + CCX_ASSOC_ABORT = 43, + /// probe request received + PROBREQ_MSG = 44, + SCAN_CONFIRM_IND = 45, + /// WPA Handshake + PSK_SUP = 46, + COUNTRY_CODE_CHANGED = 47, + /// WMMAC excedded medium time + EXCEEDED_MEDIUM_TIME = 48, + /// WEP ICV error occurred + ICV_ERROR = 49, + /// Unsupported unicast encrypted frame + UNICAST_DECODE_ERROR = 50, + /// Unsupported multicast encrypted frame + MULTICAST_DECODE_ERROR = 51, + TRACE = 52, + /// BT-AMP HCI event + BTA_HCI_EVENT = 53, + /// I/F change (for wlan host notification) + IF = 54, + /// P2P Discovery listen state expires + P2P_DISC_LISTEN_COMPLETE = 55, + /// indicate RSSI change based on configured levels + RSSI = 56, + /// PFN best network batching event + PFN_BEST_BATCHING = 57, + EXTLOG_MSG = 58, + /// Action frame reception + ACTION_FRAME = 59, + /// Action frame Tx complete + ACTION_FRAME_COMPLETE = 60, + /// assoc request received + PRE_ASSOC_IND = 61, + /// re-assoc request received + PRE_REASSOC_IND = 62, + /// channel adopted (xxx: obsoleted) + CHANNEL_ADOPTED = 63, + /// AP started + AP_STARTED = 64, + /// AP stopped due to DFS + DFS_AP_STOP = 65, + /// AP resumed due to DFS + DFS_AP_RESUME = 66, + /// WAI stations event + WAI_STA_EVENT = 67, + /// event encapsulating an WAI message + WAI_MSG = 68, + /// escan result event + ESCAN_RESULT = 69, + /// action frame off channel complete + ACTION_FRAME_OFF_CHAN_COMPLETE = 70, + /// probe response received + PROBRESP_MSG = 71, + /// P2P Probe request received + P2P_PROBREQ_MSG = 72, + DCS_REQUEST = 73, + /// credits for D11 FIFOs. [AC0,AC1,AC2,AC3,BC_MC,ATIM] + FIFO_CREDIT_MAP = 74, + /// Received action frame event WITH wl_event_rx_frame_data_t header + ACTION_FRAME_RX = 75, + /// Wake Event timer fired, used for wake WLAN test mode + WAKE_EVENT = 76, + /// Radio measurement complete + RM_COMPLETE = 77, + /// Synchronize TSF with the host + HTSFSYNC = 78, + /// request an overlay IOCTL/iovar from the host + OVERLAY_REQ = 79, + CSA_COMPLETE_IND = 80, + /// excess PM Wake Event to inform host + EXCESS_PM_WAKE_EVENT = 81, + /// no PFN networks around + PFN_SCAN_NONE = 82, + /// last found PFN network gets lost + PFN_SCAN_ALLGONE = 83, + GTK_PLUMBED = 84, + /// 802.11 ASSOC indication for NDIS only + ASSOC_IND_NDIS = 85, + /// 802.11 REASSOC indication for NDIS only + REASSOC_IND_NDIS = 86, + ASSOC_REQ_IE = 87, + ASSOC_RESP_IE = 88, + /// association recreated on resume + ASSOC_RECREATED = 89, + /// rx action frame event for NDIS only + ACTION_FRAME_RX_NDIS = 90, + /// authentication request received + AUTH_REQ = 91, + /// fast assoc recreation failed + SPEEDY_RECREATE_FAIL = 93, + /// port-specific event and payload (e.g. NDIS) + NATIVE = 94, + /// event for tx pkt delay suddently jump + PKTDELAY_IND = 95, + /// AWDL AW period starts + AWDL_AW = 96, + /// AWDL Master/Slave/NE master role event + AWDL_ROLE = 97, + /// Generic AWDL event + AWDL_EVENT = 98, + /// NIC AF txstatus + NIC_AF_TXS = 99, + /// NAN event + NAN = 100, + BEACON_FRAME_RX = 101, + /// desired service found + SERVICE_FOUND = 102, + /// GAS fragment received + GAS_FRAGMENT_RX = 103, + /// GAS sessions all complete + GAS_COMPLETE = 104, + /// New device found by p2p offload + P2PO_ADD_DEVICE = 105, + /// device has been removed by p2p offload + P2PO_DEL_DEVICE = 106, + /// WNM event to notify STA enter sleep mode + WNM_STA_SLEEP = 107, + /// Indication of MAC tx failures (exhaustion of 802.11 retries) exceeding threshold(s) + TXFAIL_THRESH = 108, + /// Proximity Detection event + PROXD = 109, + /// AWDL RX Probe response + AWDL_RX_PRB_RESP = 111, + /// AWDL RX Action Frames + AWDL_RX_ACT_FRAME = 112, + /// AWDL Wowl nulls + AWDL_WOWL_NULLPKT = 113, + /// AWDL Phycal status + AWDL_PHYCAL_STATUS = 114, + /// AWDL OOB AF status + AWDL_OOB_AF_STATUS = 115, + /// Interleaved Scan status + AWDL_SCAN_STATUS = 116, + /// AWDL AW Start + AWDL_AW_START = 117, + /// AWDL AW End + AWDL_AW_END = 118, + /// AWDL AW Extensions + AWDL_AW_EXT = 119, + AWDL_PEER_CACHE_CONTROL = 120, + CSA_START_IND = 121, + CSA_DONE_IND = 122, + CSA_FAILURE_IND = 123, + /// CCA based channel quality report + CCA_CHAN_QUAL = 124, + /// to report change in BSSID while roaming + BSSID = 125, + /// tx error indication + TX_STAT_ERROR = 126, + /// credit check for BCMC supported + BCMC_CREDIT_SUPPORT = 127, + /// psta primary interface indication + PSTA_PRIMARY_INTF_IND = 128, + /// Handover Request Initiated + BT_WIFI_HANDOVER_REQ = 130, + /// Southpaw TxInhibit notification + SPW_TXINHIBIT = 131, + /// FBT Authentication Request Indication + FBT_AUTH_REQ_IND = 132, + /// Enhancement addition for RSSI + RSSI_LQM = 133, + /// Full probe/beacon (IEs etc) results + PFN_GSCAN_FULL_RESULT = 134, + /// Significant change in rssi of bssids being tracked + PFN_SWC = 135, + /// a STA been authroized for traffic + AUTHORIZED = 136, + /// probe req with wl_event_rx_frame_data_t header + PROBREQ_MSG_RX = 137, + /// PFN completed scan of network list + PFN_SCAN_COMPLETE = 138, + /// RMC Event + RMC_EVENT = 139, + /// DPSTA interface indication + DPSTA_INTF_IND = 140, + /// RRM Event + RRM = 141, + /// ULP entry event + ULP = 146, + /// TCP Keep Alive Offload Event + TKO = 151, + /// authentication request received + EXT_AUTH_REQ = 187, + /// authentication request received + EXT_AUTH_FRAME_RX = 188, + /// mgmt frame Tx complete + MGMT_FRAME_TXSTATUS = 189, + /// highest val + 1 for range checking + LAST = 190, +} + +// TODO this PubSub can probably be replaced with shared memory to make it a bit more efficient. +pub type EventQueue = PubSubChannel; +pub type EventSubscriber<'a> = Subscriber<'a, NoopRawMutex, Message, 2, 1, 1>; + +pub struct Events { + pub queue: EventQueue, + pub mask: SharedEventMask, +} + +impl Events { + pub fn new() -> Self { + Self { + queue: EventQueue::new(), + mask: SharedEventMask::default(), + } + } +} + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Status { + pub event_type: Event, + pub status: u32, +} + +#[derive(Clone, Copy)] +pub enum Payload { + None, + BssInfo(BssInfo), +} + +#[derive(Clone, Copy)] + +pub struct Message { + pub header: Status, + pub payload: Payload, +} + +impl Message { + pub fn new(status: Status, payload: Payload) -> Self { + Self { + header: status, + payload, + } + } +} + +#[derive(Default)] +struct EventMask { + mask: [u32; Self::WORD_COUNT], +} + +impl EventMask { + const WORD_COUNT: usize = ((Event::LAST as u32 + (u32::BITS - 1)) / u32::BITS) as usize; + + fn enable(&mut self, event: Event) { + let n = event as u32; + let word = n / u32::BITS; + let bit = n % u32::BITS; + + self.mask[word as usize] |= 1 << bit; + } + + fn disable(&mut self, event: Event) { + let n = event as u32; + let word = n / u32::BITS; + let bit = n % u32::BITS; + + self.mask[word as usize] &= !(1 << bit); + } + + fn is_enabled(&self, event: Event) -> bool { + let n = event as u32; + let word = n / u32::BITS; + let bit = n % u32::BITS; + + self.mask[word as usize] & (1 << bit) > 0 + } +} + +#[derive(Default)] + +pub struct SharedEventMask { + mask: RefCell, +} + +impl SharedEventMask { + pub fn enable(&self, events: &[Event]) { + let mut mask = self.mask.borrow_mut(); + for event in events { + mask.enable(*event); + } + } + + #[allow(dead_code)] + pub fn disable(&self, events: &[Event]) { + let mut mask = self.mask.borrow_mut(); + for event in events { + mask.disable(*event); + } + } + + pub fn disable_all(&self) { + let mut mask = self.mask.borrow_mut(); + mask.mask = Default::default(); + } + + pub fn is_enabled(&self, event: Event) -> bool { + let mask = self.mask.borrow(); + mask.is_enabled(event) + } +} diff --git a/embassy-cortex-m/src/fmt.rs b/cyw43/src/fmt.rs similarity index 88% rename from embassy-cortex-m/src/fmt.rs rename to cyw43/src/fmt.rs index f8bb0a035..9534c101c 100644 --- a/embassy-cortex-m/src/fmt.rs +++ b/cyw43/src/fmt.rs @@ -1,6 +1,8 @@ #![macro_use] #![allow(unused_macros)] +use core::fmt::{Debug, Display, LowerHex}; + #[cfg(all(feature = "defmt", feature = "log"))] compile_error!("You may not enable both `defmt` and `log` features."); @@ -195,9 +197,6 @@ macro_rules! unwrap { } } -#[cfg(feature = "defmt-timestamp-uptime")] -defmt::timestamp! {"{=u64:us}", crate::time::Instant::now().as_micros() } - #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct NoneError; @@ -226,3 +225,30 @@ impl Try for Result { self } } + +pub struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/cyw43/src/ioctl.rs b/cyw43/src/ioctl.rs new file mode 100644 index 000000000..61524c274 --- /dev/null +++ b/cyw43/src/ioctl.rs @@ -0,0 +1,126 @@ +use core::cell::{Cell, RefCell}; +use core::future::poll_fn; +use core::task::{Poll, Waker}; + +use embassy_sync::waitqueue::WakerRegistration; + +use crate::fmt::Bytes; + +#[derive(Clone, Copy)] +pub enum IoctlType { + Get = 0, + Set = 2, +} + +#[derive(Clone, Copy)] +pub struct PendingIoctl { + pub buf: *mut [u8], + pub kind: IoctlType, + pub cmd: u32, + pub iface: u32, +} + +#[derive(Clone, Copy)] +enum IoctlStateInner { + Pending(PendingIoctl), + Sent { buf: *mut [u8] }, + Done { resp_len: usize }, +} + +struct Wakers { + control: WakerRegistration, + runner: WakerRegistration, +} + +impl Default for Wakers { + fn default() -> Self { + Self { + control: WakerRegistration::new(), + runner: WakerRegistration::new(), + } + } +} + +pub struct IoctlState { + state: Cell, + wakers: RefCell, +} + +impl IoctlState { + pub fn new() -> Self { + Self { + state: Cell::new(IoctlStateInner::Done { resp_len: 0 }), + wakers: Default::default(), + } + } + + fn wake_control(&self) { + self.wakers.borrow_mut().control.wake(); + } + + fn register_control(&self, waker: &Waker) { + self.wakers.borrow_mut().control.register(waker); + } + + fn wake_runner(&self) { + self.wakers.borrow_mut().runner.wake(); + } + + fn register_runner(&self, waker: &Waker) { + self.wakers.borrow_mut().runner.register(waker); + } + + pub async fn wait_complete(&self) -> usize { + poll_fn(|cx| { + if let IoctlStateInner::Done { resp_len } = self.state.get() { + Poll::Ready(resp_len) + } else { + self.register_control(cx.waker()); + Poll::Pending + } + }) + .await + } + + pub async fn wait_pending(&self) -> PendingIoctl { + let pending = poll_fn(|cx| { + if let IoctlStateInner::Pending(pending) = self.state.get() { + Poll::Ready(pending) + } else { + self.register_runner(cx.waker()); + Poll::Pending + } + }) + .await; + + self.state.set(IoctlStateInner::Sent { buf: pending.buf }); + pending + } + + pub fn cancel_ioctl(&self) { + self.state.set(IoctlStateInner::Done { resp_len: 0 }); + } + + pub async fn do_ioctl(&self, kind: IoctlType, cmd: u32, iface: u32, buf: &mut [u8]) -> usize { + self.state + .set(IoctlStateInner::Pending(PendingIoctl { buf, kind, cmd, iface })); + self.wake_runner(); + self.wait_complete().await + } + + pub fn ioctl_done(&self, response: &[u8]) { + if let IoctlStateInner::Sent { buf } = self.state.get() { + trace!("IOCTL Response: {:02x}", Bytes(response)); + + // TODO fix this + (unsafe { &mut *buf }[..response.len()]).copy_from_slice(response); + + self.state.set(IoctlStateInner::Done { + resp_len: response.len(), + }); + self.wake_control(); + } else { + warn!("IOCTL Response but no pending Ioctl"); + } + } +} diff --git a/cyw43/src/lib.rs b/cyw43/src/lib.rs new file mode 100644 index 000000000..fd11f3674 --- /dev/null +++ b/cyw43/src/lib.rs @@ -0,0 +1,236 @@ +#![no_std] +#![no_main] +#![allow(incomplete_features)] +#![feature(async_fn_in_trait, type_alias_impl_trait, concat_bytes)] +#![deny(unused_must_use)] + +// This mod MUST go first, so that the others see its macros. +pub(crate) mod fmt; + +mod bus; +mod consts; +mod countries; +mod events; +mod ioctl; +mod structs; + +mod control; +mod nvram; +mod runner; + +use core::slice; + +use embassy_net_driver_channel as ch; +use embedded_hal_1::digital::OutputPin; +use events::Events; +use ioctl::IoctlState; + +use crate::bus::Bus; +pub use crate::bus::SpiBusCyw43; +pub use crate::control::{Control, Error as ControlError}; +pub use crate::runner::Runner; +pub use crate::structs::BssInfo; + +const MTU: usize = 1514; + +#[allow(unused)] +#[derive(Clone, Copy, PartialEq, Eq)] +enum Core { + WLAN = 0, + SOCSRAM = 1, + SDIOD = 2, +} + +impl Core { + fn base_addr(&self) -> u32 { + match self { + Self::WLAN => CHIP.arm_core_base_address, + Self::SOCSRAM => CHIP.socsram_wrapper_base_address, + Self::SDIOD => CHIP.sdiod_core_base_address, + } + } +} + +#[allow(unused)] +struct Chip { + arm_core_base_address: u32, + socsram_base_address: u32, + socsram_wrapper_base_address: u32, + sdiod_core_base_address: u32, + pmu_base_address: u32, + chip_ram_size: u32, + atcm_ram_base_address: u32, + socram_srmem_size: u32, + chanspec_band_mask: u32, + chanspec_band_2g: u32, + chanspec_band_5g: u32, + chanspec_band_shift: u32, + chanspec_bw_10: u32, + chanspec_bw_20: u32, + chanspec_bw_40: u32, + chanspec_bw_mask: u32, + chanspec_bw_shift: u32, + chanspec_ctl_sb_lower: u32, + chanspec_ctl_sb_upper: u32, + chanspec_ctl_sb_none: u32, + chanspec_ctl_sb_mask: u32, +} + +const WRAPPER_REGISTER_OFFSET: u32 = 0x100000; + +// Data for CYW43439 +const CHIP: Chip = Chip { + arm_core_base_address: 0x18003000 + WRAPPER_REGISTER_OFFSET, + socsram_base_address: 0x18004000, + socsram_wrapper_base_address: 0x18004000 + WRAPPER_REGISTER_OFFSET, + sdiod_core_base_address: 0x18002000, + pmu_base_address: 0x18000000, + chip_ram_size: 512 * 1024, + atcm_ram_base_address: 0, + socram_srmem_size: 64 * 1024, + chanspec_band_mask: 0xc000, + chanspec_band_2g: 0x0000, + chanspec_band_5g: 0xc000, + chanspec_band_shift: 14, + chanspec_bw_10: 0x0800, + chanspec_bw_20: 0x1000, + chanspec_bw_40: 0x1800, + chanspec_bw_mask: 0x3800, + chanspec_bw_shift: 11, + chanspec_ctl_sb_lower: 0x0000, + chanspec_ctl_sb_upper: 0x0100, + chanspec_ctl_sb_none: 0x0000, + chanspec_ctl_sb_mask: 0x0700, +}; + +pub struct State { + ioctl_state: IoctlState, + ch: ch::State, + events: Events, +} + +impl State { + pub fn new() -> Self { + Self { + ioctl_state: IoctlState::new(), + ch: ch::State::new(), + events: Events::new(), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PowerManagementMode { + /// Custom, officially unsupported mode. Use at your own risk. + /// All power-saving features set to their max at only a marginal decrease in power consumption + /// as oppposed to `Aggressive`. + SuperSave, + + /// Aggressive power saving mode. + Aggressive, + + /// The default mode. + PowerSave, + + /// Performance is prefered over power consumption but still some power is conserved as opposed to + /// `None`. + Performance, + + /// Unlike all the other PM modes, this lowers the power consumption at all times at the cost of + /// a much lower throughput. + ThroughputThrottling, + + /// No power management is configured. This consumes the most power. + None, +} + +impl Default for PowerManagementMode { + fn default() -> Self { + Self::PowerSave + } +} + +impl PowerManagementMode { + fn sleep_ret_ms(&self) -> u16 { + match self { + PowerManagementMode::SuperSave => 2000, + PowerManagementMode::Aggressive => 2000, + PowerManagementMode::PowerSave => 200, + PowerManagementMode::Performance => 20, + PowerManagementMode::ThroughputThrottling => 0, // value doesn't matter + PowerManagementMode::None => 0, // value doesn't matter + } + } + + fn beacon_period(&self) -> u8 { + match self { + PowerManagementMode::SuperSave => 255, + PowerManagementMode::Aggressive => 1, + PowerManagementMode::PowerSave => 1, + PowerManagementMode::Performance => 1, + PowerManagementMode::ThroughputThrottling => 0, // value doesn't matter + PowerManagementMode::None => 0, // value doesn't matter + } + } + + fn dtim_period(&self) -> u8 { + match self { + PowerManagementMode::SuperSave => 255, + PowerManagementMode::Aggressive => 1, + PowerManagementMode::PowerSave => 1, + PowerManagementMode::Performance => 1, + PowerManagementMode::ThroughputThrottling => 0, // value doesn't matter + PowerManagementMode::None => 0, // value doesn't matter + } + } + + fn assoc(&self) -> u8 { + match self { + PowerManagementMode::SuperSave => 255, + PowerManagementMode::Aggressive => 10, + PowerManagementMode::PowerSave => 10, + PowerManagementMode::Performance => 1, + PowerManagementMode::ThroughputThrottling => 0, // value doesn't matter + PowerManagementMode::None => 0, // value doesn't matter + } + } + + fn mode(&self) -> u32 { + match self { + PowerManagementMode::ThroughputThrottling => 1, + PowerManagementMode::None => 0, + _ => 2, + } + } +} + +pub type NetDriver<'a> = ch::Device<'a, MTU>; + +pub async fn new<'a, PWR, SPI>( + state: &'a mut State, + pwr: PWR, + spi: SPI, + firmware: &[u8], +) -> (NetDriver<'a>, Control<'a>, Runner<'a, PWR, SPI>) +where + PWR: OutputPin, + SPI: SpiBusCyw43, +{ + let (ch_runner, device) = ch::new(&mut state.ch, [0; 6]); + let state_ch = ch_runner.state_runner(); + + let mut runner = Runner::new(ch_runner, Bus::new(pwr, spi), &state.ioctl_state, &state.events); + + runner.init(firmware).await; + + ( + device, + Control::new(state_ch, &state.events, &state.ioctl_state), + runner, + ) +} + +fn slice8_mut(x: &mut [u32]) -> &mut [u8] { + let len = x.len() * 4; + unsafe { slice::from_raw_parts_mut(x.as_mut_ptr() as _, len) } +} diff --git a/cyw43/src/nvram.rs b/cyw43/src/nvram.rs new file mode 100644 index 000000000..964a3128d --- /dev/null +++ b/cyw43/src/nvram.rs @@ -0,0 +1,54 @@ +macro_rules! nvram { + ($($s:literal,)*) => { + concat_bytes!($($s, b"\x00",)* b"\x00\x00") + }; +} + +pub static NVRAM: &'static [u8] = &*nvram!( + b"NVRAMRev=$Rev$", + b"manfid=0x2d0", + b"prodid=0x0727", + b"vendid=0x14e4", + b"devid=0x43e2", + b"boardtype=0x0887", + b"boardrev=0x1100", + b"boardnum=22", + b"macaddr=00:A0:50:b5:59:5e", + b"sromrev=11", + b"boardflags=0x00404001", + b"boardflags3=0x04000000", + b"xtalfreq=37400", + b"nocrc=1", + b"ag0=255", + b"aa2g=1", + b"ccode=ALL", + b"pa0itssit=0x20", + b"extpagain2g=0", + b"pa2ga0=-168,6649,-778", + b"AvVmid_c0=0x0,0xc8", + b"cckpwroffset0=5", + b"maxp2ga0=84", + b"txpwrbckof=6", + b"cckbw202gpo=0", + b"legofdmbw202gpo=0x66111111", + b"mcsbw202gpo=0x77711111", + b"propbw202gpo=0xdd", + b"ofdmdigfilttype=18", + b"ofdmdigfilttypebe=18", + b"papdmode=1", + b"papdvalidtest=1", + b"pacalidx2g=45", + b"papdepsoffset=-30", + b"papdendidx=58", + b"ltecxmux=0", + b"ltecxpadnum=0x0102", + b"ltecxfnsel=0x44", + b"ltecxgcigpio=0x01", + b"il0macaddr=00:90:4c:c5:12:38", + b"wl0id=0x431b", + b"deadman_to=0xffffffff", + b"muxenab=0x100", + b"spurconfig=0x3", + b"glitch_based_crsmin=1", + b"btc_mode=1", +); diff --git a/cyw43/src/runner.rs b/cyw43/src/runner.rs new file mode 100644 index 000000000..1c187faa5 --- /dev/null +++ b/cyw43/src/runner.rs @@ -0,0 +1,585 @@ +use embassy_futures::select::{select3, Either3}; +use embassy_net_driver_channel as ch; +use embassy_sync::pubsub::PubSubBehavior; +use embassy_time::{block_for, Duration, Timer}; +use embedded_hal_1::digital::OutputPin; + +use crate::bus::Bus; +pub use crate::bus::SpiBusCyw43; +use crate::consts::*; +use crate::events::{Event, Events, Status}; +use crate::fmt::Bytes; +use crate::ioctl::{IoctlState, IoctlType, PendingIoctl}; +use crate::nvram::NVRAM; +use crate::structs::*; +use crate::{events, slice8_mut, Core, CHIP, MTU}; + +#[cfg(feature = "firmware-logs")] +struct LogState { + addr: u32, + last_idx: usize, + buf: [u8; 256], + buf_count: usize, +} + +#[cfg(feature = "firmware-logs")] +impl Default for LogState { + fn default() -> Self { + Self { + addr: Default::default(), + last_idx: Default::default(), + buf: [0; 256], + buf_count: Default::default(), + } + } +} + +pub struct Runner<'a, PWR, SPI> { + ch: ch::Runner<'a, MTU>, + bus: Bus, + + ioctl_state: &'a IoctlState, + ioctl_id: u16, + sdpcm_seq: u8, + sdpcm_seq_max: u8, + + events: &'a Events, + + #[cfg(feature = "firmware-logs")] + log: LogState, +} + +impl<'a, PWR, SPI> Runner<'a, PWR, SPI> +where + PWR: OutputPin, + SPI: SpiBusCyw43, +{ + pub(crate) fn new( + ch: ch::Runner<'a, MTU>, + bus: Bus, + ioctl_state: &'a IoctlState, + events: &'a Events, + ) -> Self { + Self { + ch, + bus, + ioctl_state, + ioctl_id: 0, + sdpcm_seq: 0, + sdpcm_seq_max: 1, + events, + #[cfg(feature = "firmware-logs")] + log: LogState::default(), + } + } + + pub(crate) async fn init(&mut self, firmware: &[u8]) { + self.bus.init().await; + + // Init ALP (Active Low Power) clock + self.bus + .write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, BACKPLANE_ALP_AVAIL_REQ) + .await; + debug!("waiting for clock..."); + while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & BACKPLANE_ALP_AVAIL == 0 {} + debug!("clock ok"); + + let chip_id = self.bus.bp_read16(0x1800_0000).await; + debug!("chip ID: {}", chip_id); + + // Upload firmware. + self.core_disable(Core::WLAN).await; + self.core_reset(Core::SOCSRAM).await; + self.bus.bp_write32(CHIP.socsram_base_address + 0x10, 3).await; + self.bus.bp_write32(CHIP.socsram_base_address + 0x44, 0).await; + + let ram_addr = CHIP.atcm_ram_base_address; + + debug!("loading fw"); + self.bus.bp_write(ram_addr, firmware).await; + + debug!("loading nvram"); + // Round up to 4 bytes. + let nvram_len = (NVRAM.len() + 3) / 4 * 4; + self.bus + .bp_write(ram_addr + CHIP.chip_ram_size - 4 - nvram_len as u32, NVRAM) + .await; + + let nvram_len_words = nvram_len as u32 / 4; + let nvram_len_magic = (!nvram_len_words << 16) | nvram_len_words; + self.bus + .bp_write32(ram_addr + CHIP.chip_ram_size - 4, nvram_len_magic) + .await; + + // Start core! + debug!("starting up core..."); + self.core_reset(Core::WLAN).await; + assert!(self.core_is_up(Core::WLAN).await); + + while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & 0x80 == 0 {} + + // "Set up the interrupt mask and enable interrupts" + // self.bus.bp_write32(CHIP.sdiod_core_base_address + 0x24, 0xF0).await; + + self.bus + .write16(FUNC_BUS, REG_BUS_INTERRUPT_ENABLE, IRQ_F2_PACKET_AVAILABLE) + .await; + + // "Lower F2 Watermark to avoid DMA Hang in F2 when SD Clock is stopped." + // Sounds scary... + self.bus + .write8(FUNC_BACKPLANE, REG_BACKPLANE_FUNCTION2_WATERMARK, 32) + .await; + + // wait for wifi startup + debug!("waiting for wifi init..."); + while self.bus.read32(FUNC_BUS, REG_BUS_STATUS).await & STATUS_F2_RX_READY == 0 {} + + // Some random configs related to sleep. + // These aren't needed if we don't want to sleep the bus. + // TODO do we need to sleep the bus to read the irq line, due to + // being on the same pin as MOSI/MISO? + + /* + let mut val = self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_WAKEUP_CTRL).await; + val |= 0x02; // WAKE_TILL_HT_AVAIL + self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_WAKEUP_CTRL, val).await; + self.bus.write8(FUNC_BUS, 0xF0, 0x08).await; // SDIOD_CCCR_BRCM_CARDCAP.CMD_NODEC = 1 + self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0x02).await; // SBSDIO_FORCE_HT + + let mut val = self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_SLEEP_CSR).await; + val |= 0x01; // SBSDIO_SLPCSR_KEEP_SDIO_ON + self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_SLEEP_CSR, val).await; + */ + + // clear pulls + self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_PULL_UP, 0).await; + let _ = self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_PULL_UP).await; + + // start HT clock + //self.bus.write8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR, 0x10).await; + //debug!("waiting for HT clock..."); + //while self.bus.read8(FUNC_BACKPLANE, REG_BACKPLANE_CHIP_CLOCK_CSR).await & 0x80 == 0 {} + //debug!("clock ok"); + + #[cfg(feature = "firmware-logs")] + self.log_init().await; + + debug!("wifi init done"); + } + + #[cfg(feature = "firmware-logs")] + async fn log_init(&mut self) { + // Initialize shared memory for logging. + + let addr = CHIP.atcm_ram_base_address + CHIP.chip_ram_size - 4 - CHIP.socram_srmem_size; + let shared_addr = self.bus.bp_read32(addr).await; + debug!("shared_addr {:08x}", shared_addr); + + let mut shared = [0; SharedMemData::SIZE]; + self.bus.bp_read(shared_addr, &mut shared).await; + let shared = SharedMemData::from_bytes(&shared); + + self.log.addr = shared.console_addr + 8; + } + + #[cfg(feature = "firmware-logs")] + async fn log_read(&mut self) { + // Read log struct + let mut log = [0; SharedMemLog::SIZE]; + self.bus.bp_read(self.log.addr, &mut log).await; + let log = SharedMemLog::from_bytes(&log); + + let idx = log.idx as usize; + + // If pointer hasn't moved, no need to do anything. + if idx == self.log.last_idx { + return; + } + + // Read entire buf for now. We could read only what we need, but then we + // run into annoying alignment issues in `bp_read`. + let mut buf = [0; 0x400]; + self.bus.bp_read(log.buf, &mut buf).await; + + while self.log.last_idx != idx as usize { + let b = buf[self.log.last_idx]; + if b == b'\r' || b == b'\n' { + if self.log.buf_count != 0 { + let s = unsafe { core::str::from_utf8_unchecked(&self.log.buf[..self.log.buf_count]) }; + debug!("LOGS: {}", s); + self.log.buf_count = 0; + } + } else if self.log.buf_count < self.log.buf.len() { + self.log.buf[self.log.buf_count] = b; + self.log.buf_count += 1; + } + + self.log.last_idx += 1; + if self.log.last_idx == 0x400 { + self.log.last_idx = 0; + } + } + } + + pub async fn run(mut self) -> ! { + let mut buf = [0; 512]; + loop { + #[cfg(feature = "firmware-logs")] + self.log_read().await; + + if self.has_credit() { + let ioctl = self.ioctl_state.wait_pending(); + let tx = self.ch.tx_buf(); + let ev = self.bus.wait_for_event(); + + match select3(ioctl, tx, ev).await { + Either3::First(PendingIoctl { + buf: iobuf, + kind, + cmd, + iface, + }) => { + self.send_ioctl(kind, cmd, iface, unsafe { &*iobuf }).await; + self.check_status(&mut buf).await; + } + Either3::Second(packet) => { + trace!("tx pkt {:02x}", Bytes(&packet[..packet.len().min(48)])); + + let mut buf = [0; 512]; + let buf8 = slice8_mut(&mut buf); + + // There MUST be 2 bytes of padding between the SDPCM and BDC headers. + // And ONLY for data packets! + // No idea why, but the firmware will append two zero bytes to the tx'd packets + // otherwise. If the packet is exactly 1514 bytes (the max MTU), this makes it + // be oversized and get dropped. + // WHD adds it here https://github.com/Infineon/wifi-host-driver/blob/c04fcbb6b0d049304f376cf483fd7b1b570c8cd5/WiFi_Host_Driver/src/include/whd_sdpcm.h#L90 + // and adds it to the header size her https://github.com/Infineon/wifi-host-driver/blob/c04fcbb6b0d049304f376cf483fd7b1b570c8cd5/WiFi_Host_Driver/src/whd_sdpcm.c#L597 + // ¯\_(ツ)_/¯ + const PADDING_SIZE: usize = 2; + let total_len = SdpcmHeader::SIZE + PADDING_SIZE + BdcHeader::SIZE + packet.len(); + + let seq = self.sdpcm_seq; + self.sdpcm_seq = self.sdpcm_seq.wrapping_add(1); + + let sdpcm_header = SdpcmHeader { + len: total_len as u16, // TODO does this len need to be rounded up to u32? + len_inv: !total_len as u16, + sequence: seq, + channel_and_flags: CHANNEL_TYPE_DATA, + next_length: 0, + header_length: (SdpcmHeader::SIZE + PADDING_SIZE) as _, + wireless_flow_control: 0, + bus_data_credit: 0, + reserved: [0, 0], + }; + + let bdc_header = BdcHeader { + flags: BDC_VERSION << BDC_VERSION_SHIFT, + priority: 0, + flags2: 0, + data_offset: 0, + }; + trace!("tx {:?}", sdpcm_header); + trace!(" {:?}", bdc_header); + + buf8[0..SdpcmHeader::SIZE].copy_from_slice(&sdpcm_header.to_bytes()); + buf8[SdpcmHeader::SIZE + PADDING_SIZE..][..BdcHeader::SIZE] + .copy_from_slice(&bdc_header.to_bytes()); + buf8[SdpcmHeader::SIZE + PADDING_SIZE + BdcHeader::SIZE..][..packet.len()] + .copy_from_slice(packet); + + let total_len = (total_len + 3) & !3; // round up to 4byte + + trace!(" {:02x}", Bytes(&buf8[..total_len.min(48)])); + + self.bus.wlan_write(&buf[..(total_len / 4)]).await; + self.ch.tx_done(); + self.check_status(&mut buf).await; + } + Either3::Third(()) => { + self.handle_irq(&mut buf).await; + } + } + } else { + warn!("TX stalled"); + self.bus.wait_for_event().await; + self.handle_irq(&mut buf).await; + } + } + } + + /// Wait for IRQ on F2 packet available + async fn handle_irq(&mut self, buf: &mut [u32; 512]) { + // Receive stuff + let irq = self.bus.read16(FUNC_BUS, REG_BUS_INTERRUPT).await; + trace!("irq{}", FormatInterrupt(irq)); + + if irq & IRQ_F2_PACKET_AVAILABLE != 0 { + self.check_status(buf).await; + } + + if irq & IRQ_DATA_UNAVAILABLE != 0 { + // TODO what should we do here? + warn!("IRQ DATA_UNAVAILABLE, clearing..."); + self.bus.write16(FUNC_BUS, REG_BUS_INTERRUPT, 1).await; + } + } + + /// Handle F2 events while status register is set + async fn check_status(&mut self, buf: &mut [u32; 512]) { + loop { + let status = self.bus.status(); + trace!("check status{}", FormatStatus(status)); + + if status & STATUS_F2_PKT_AVAILABLE != 0 { + let len = (status & STATUS_F2_PKT_LEN_MASK) >> STATUS_F2_PKT_LEN_SHIFT; + self.bus.wlan_read(buf, len).await; + trace!("rx {:02x}", Bytes(&slice8_mut(buf)[..(len as usize).min(48)])); + self.rx(&mut slice8_mut(buf)[..len as usize]); + } else { + break; + } + } + } + + fn rx(&mut self, packet: &mut [u8]) { + let Some((sdpcm_header, payload)) = SdpcmHeader::parse(packet) else { + return; + }; + + self.update_credit(&sdpcm_header); + + let channel = sdpcm_header.channel_and_flags & 0x0f; + + match channel { + CHANNEL_TYPE_CONTROL => { + let Some((cdc_header, response)) = CdcHeader::parse(payload) else { + return; + }; + trace!(" {:?}", cdc_header); + + if cdc_header.id == self.ioctl_id { + if cdc_header.status != 0 { + // TODO: propagate error instead + panic!("IOCTL error {}", cdc_header.status as i32); + } + + self.ioctl_state.ioctl_done(response); + } + } + CHANNEL_TYPE_EVENT => { + let Some((_, bdc_packet)) = BdcHeader::parse(payload) else { + warn!("BDC event, incomplete header"); + return; + }; + + let Some((event_packet, evt_data)) = EventPacket::parse(bdc_packet) else { + warn!("BDC event, incomplete data"); + return; + }; + + const ETH_P_LINK_CTL: u16 = 0x886c; // HPNA, wlan link local tunnel, according to linux if_ether.h + if event_packet.eth.ether_type != ETH_P_LINK_CTL { + warn!( + "unexpected ethernet type 0x{:04x}, expected Broadcom ether type 0x{:04x}", + event_packet.eth.ether_type, ETH_P_LINK_CTL + ); + return; + } + const BROADCOM_OUI: &[u8] = &[0x00, 0x10, 0x18]; + if event_packet.hdr.oui != BROADCOM_OUI { + warn!( + "unexpected ethernet OUI {:02x}, expected Broadcom OUI {:02x}", + Bytes(&event_packet.hdr.oui), + Bytes(BROADCOM_OUI) + ); + return; + } + const BCMILCP_SUBTYPE_VENDOR_LONG: u16 = 32769; + if event_packet.hdr.subtype != BCMILCP_SUBTYPE_VENDOR_LONG { + warn!("unexpected subtype {}", event_packet.hdr.subtype); + return; + } + + const BCMILCP_BCM_SUBTYPE_EVENT: u16 = 1; + if event_packet.hdr.user_subtype != BCMILCP_BCM_SUBTYPE_EVENT { + warn!("unexpected user_subtype {}", event_packet.hdr.subtype); + return; + } + + let evt_type = events::Event::from(event_packet.msg.event_type as u8); + debug!( + "=== EVENT {:?}: {:?} {:02x}", + evt_type, + event_packet.msg, + Bytes(evt_data) + ); + + if self.events.mask.is_enabled(evt_type) { + let status = event_packet.msg.status; + let event_payload = match evt_type { + Event::ESCAN_RESULT if status == EStatus::PARTIAL => { + let Some((_, bss_info)) = ScanResults::parse(evt_data) else { + return; + }; + let Some(bss_info) = BssInfo::parse(bss_info) else { + return; + }; + events::Payload::BssInfo(*bss_info) + } + Event::ESCAN_RESULT => events::Payload::None, + _ => events::Payload::None, + }; + + // this intentionally uses the non-blocking publish immediate + // publish() is a deadlock risk in the current design as awaiting here prevents ioctls + // The `Runner` always yields when accessing the device, so consumers always have a chance to receive the event + // (if they are actively awaiting the queue) + self.events.queue.publish_immediate(events::Message::new( + Status { + event_type: evt_type, + status, + }, + event_payload, + )); + } + } + CHANNEL_TYPE_DATA => { + let Some((_, packet)) = BdcHeader::parse(payload) else { + return; + }; + trace!("rx pkt {:02x}", Bytes(&packet[..packet.len().min(48)])); + + match self.ch.try_rx_buf() { + Some(buf) => { + buf[..packet.len()].copy_from_slice(packet); + self.ch.rx_done(packet.len()) + } + None => warn!("failed to push rxd packet to the channel."), + } + } + _ => {} + } + } + + fn update_credit(&mut self, sdpcm_header: &SdpcmHeader) { + if sdpcm_header.channel_and_flags & 0xf < 3 { + let mut sdpcm_seq_max = sdpcm_header.bus_data_credit; + if sdpcm_seq_max.wrapping_sub(self.sdpcm_seq) > 0x40 { + sdpcm_seq_max = self.sdpcm_seq + 2; + } + self.sdpcm_seq_max = sdpcm_seq_max; + } + } + + fn has_credit(&self) -> bool { + self.sdpcm_seq != self.sdpcm_seq_max && self.sdpcm_seq_max.wrapping_sub(self.sdpcm_seq) & 0x80 == 0 + } + + async fn send_ioctl(&mut self, kind: IoctlType, cmd: u32, iface: u32, data: &[u8]) { + let mut buf = [0; 512]; + let buf8 = slice8_mut(&mut buf); + + let total_len = SdpcmHeader::SIZE + CdcHeader::SIZE + data.len(); + + let sdpcm_seq = self.sdpcm_seq; + self.sdpcm_seq = self.sdpcm_seq.wrapping_add(1); + self.ioctl_id = self.ioctl_id.wrapping_add(1); + + let sdpcm_header = SdpcmHeader { + len: total_len as u16, // TODO does this len need to be rounded up to u32? + len_inv: !total_len as u16, + sequence: sdpcm_seq, + channel_and_flags: CHANNEL_TYPE_CONTROL, + next_length: 0, + header_length: SdpcmHeader::SIZE as _, + wireless_flow_control: 0, + bus_data_credit: 0, + reserved: [0, 0], + }; + + let cdc_header = CdcHeader { + cmd: cmd, + len: data.len() as _, + flags: kind as u16 | (iface as u16) << 12, + id: self.ioctl_id, + status: 0, + }; + trace!("tx {:?}", sdpcm_header); + trace!(" {:?}", cdc_header); + + buf8[0..SdpcmHeader::SIZE].copy_from_slice(&sdpcm_header.to_bytes()); + buf8[SdpcmHeader::SIZE..][..CdcHeader::SIZE].copy_from_slice(&cdc_header.to_bytes()); + buf8[SdpcmHeader::SIZE + CdcHeader::SIZE..][..data.len()].copy_from_slice(data); + + let total_len = (total_len + 3) & !3; // round up to 4byte + + trace!(" {:02x}", Bytes(&buf8[..total_len.min(48)])); + + self.bus.wlan_write(&buf[..total_len / 4]).await; + } + + async fn core_disable(&mut self, core: Core) { + let base = core.base_addr(); + + // Dummy read? + let _ = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await; + + // Check it isn't already reset + let r = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await; + if r & AI_RESETCTRL_BIT_RESET != 0 { + return; + } + + self.bus.bp_write8(base + AI_IOCTRL_OFFSET, 0).await; + let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await; + + block_for(Duration::from_millis(1)); + + self.bus + .bp_write8(base + AI_RESETCTRL_OFFSET, AI_RESETCTRL_BIT_RESET) + .await; + let _ = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await; + } + + async fn core_reset(&mut self, core: Core) { + self.core_disable(core).await; + + let base = core.base_addr(); + self.bus + .bp_write8(base + AI_IOCTRL_OFFSET, AI_IOCTRL_BIT_FGC | AI_IOCTRL_BIT_CLOCK_EN) + .await; + let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await; + + self.bus.bp_write8(base + AI_RESETCTRL_OFFSET, 0).await; + + Timer::after(Duration::from_millis(1)).await; + + self.bus + .bp_write8(base + AI_IOCTRL_OFFSET, AI_IOCTRL_BIT_CLOCK_EN) + .await; + let _ = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await; + + Timer::after(Duration::from_millis(1)).await; + } + + async fn core_is_up(&mut self, core: Core) -> bool { + let base = core.base_addr(); + + let io = self.bus.bp_read8(base + AI_IOCTRL_OFFSET).await; + if io & (AI_IOCTRL_BIT_FGC | AI_IOCTRL_BIT_CLOCK_EN) != AI_IOCTRL_BIT_CLOCK_EN { + debug!("core_is_up: returning false due to bad ioctrl {:02x}", io); + return false; + } + + let r = self.bus.bp_read8(base + AI_RESETCTRL_OFFSET).await; + if r & (AI_RESETCTRL_BIT_RESET) != 0 { + debug!("core_is_up: returning false due to bad resetctrl {:02x}", r); + return false; + } + + true + } +} diff --git a/cyw43/src/structs.rs b/cyw43/src/structs.rs new file mode 100644 index 000000000..5ba633c74 --- /dev/null +++ b/cyw43/src/structs.rs @@ -0,0 +1,496 @@ +use crate::events::Event; +use crate::fmt::Bytes; + +macro_rules! impl_bytes { + ($t:ident) => { + impl $t { + pub const SIZE: usize = core::mem::size_of::(); + + #[allow(unused)] + pub fn to_bytes(&self) -> [u8; Self::SIZE] { + unsafe { core::mem::transmute(*self) } + } + + #[allow(unused)] + pub fn from_bytes(bytes: &[u8; Self::SIZE]) -> &Self { + let alignment = core::mem::align_of::(); + assert_eq!( + bytes.as_ptr().align_offset(alignment), + 0, + "{} is not aligned", + core::any::type_name::() + ); + unsafe { core::mem::transmute(bytes) } + } + + #[allow(unused)] + pub fn from_bytes_mut(bytes: &mut [u8; Self::SIZE]) -> &mut Self { + let alignment = core::mem::align_of::(); + assert_eq!( + bytes.as_ptr().align_offset(alignment), + 0, + "{} is not aligned", + core::any::type_name::() + ); + + unsafe { core::mem::transmute(bytes) } + } + } + }; +} + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct SharedMemData { + pub flags: u32, + pub trap_addr: u32, + pub assert_exp_addr: u32, + pub assert_file_addr: u32, + pub assert_line: u32, + pub console_addr: u32, + pub msgtrace_addr: u32, + pub fwid: u32, +} +impl_bytes!(SharedMemData); + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct SharedMemLog { + pub buf: u32, + pub buf_size: u32, + pub idx: u32, + pub out_idx: u32, +} +impl_bytes!(SharedMemLog); + +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct SdpcmHeader { + pub len: u16, + pub len_inv: u16, + /// Rx/Tx sequence number + pub sequence: u8, + /// 4 MSB Channel number, 4 LSB arbitrary flag + pub channel_and_flags: u8, + /// Length of next data frame, reserved for Tx + pub next_length: u8, + /// Data offset + pub header_length: u8, + /// Flow control bits, reserved for Tx + pub wireless_flow_control: u8, + /// Maximum Sequence number allowed by firmware for Tx + pub bus_data_credit: u8, + /// Reserved + pub reserved: [u8; 2], +} +impl_bytes!(SdpcmHeader); + +impl SdpcmHeader { + pub fn parse(packet: &mut [u8]) -> Option<(&mut Self, &mut [u8])> { + let packet_len = packet.len(); + if packet_len < Self::SIZE { + warn!("packet too short, len={}", packet.len()); + return None; + } + let (sdpcm_header, sdpcm_packet) = packet.split_at_mut(Self::SIZE); + let sdpcm_header = Self::from_bytes_mut(sdpcm_header.try_into().unwrap()); + trace!("rx {:?}", sdpcm_header); + + if sdpcm_header.len != !sdpcm_header.len_inv { + warn!("len inv mismatch"); + return None; + } + + if sdpcm_header.len as usize != packet_len { + warn!("len from header doesn't match len from spi"); + return None; + } + + let sdpcm_packet = &mut sdpcm_packet[(sdpcm_header.header_length as usize - Self::SIZE)..]; + Some((sdpcm_header, sdpcm_packet)) + } +} + +#[derive(Debug, Clone, Copy)] +#[repr(C, packed(2))] +pub struct CdcHeader { + pub cmd: u32, + pub len: u32, + pub flags: u16, + pub id: u16, + pub status: u32, +} +impl_bytes!(CdcHeader); + +#[cfg(feature = "defmt")] +impl defmt::Format for CdcHeader { + fn format(&self, fmt: defmt::Formatter) { + fn copy(t: T) -> T { + t + } + + defmt::write!( + fmt, + "CdcHeader{{cmd: {=u32:08x}, len: {=u32:08x}, flags: {=u16:04x}, id: {=u16:04x}, status: {=u32:08x}}}", + copy(self.cmd), + copy(self.len), + copy(self.flags), + copy(self.id), + copy(self.status), + ) + } +} + +impl CdcHeader { + pub fn parse(packet: &mut [u8]) -> Option<(&mut Self, &mut [u8])> { + if packet.len() < Self::SIZE { + warn!("payload too short, len={}", packet.len()); + return None; + } + + let (cdc_header, payload) = packet.split_at_mut(Self::SIZE); + let cdc_header = Self::from_bytes_mut(cdc_header.try_into().unwrap()); + + let payload = &mut payload[..cdc_header.len as usize]; + Some((cdc_header, payload)) + } +} + +pub const BDC_VERSION: u8 = 2; +pub const BDC_VERSION_SHIFT: u8 = 4; + +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct BdcHeader { + pub flags: u8, + /// 802.1d Priority (low 3 bits) + pub priority: u8, + pub flags2: u8, + /// Offset from end of BDC header to packet data, in 4-uint8_t words. Leaves room for optional headers. + pub data_offset: u8, +} +impl_bytes!(BdcHeader); + +impl BdcHeader { + pub fn parse(packet: &mut [u8]) -> Option<(&mut Self, &mut [u8])> { + if packet.len() < Self::SIZE { + return None; + } + + let (bdc_header, bdc_packet) = packet.split_at_mut(Self::SIZE); + let bdc_header = Self::from_bytes_mut(bdc_header.try_into().unwrap()); + trace!(" {:?}", bdc_header); + + let packet_start = 4 * bdc_header.data_offset as usize; + + let bdc_packet = bdc_packet.get_mut(packet_start..)?; + trace!(" {:02x}", Bytes(&bdc_packet[..bdc_packet.len().min(36)])); + + Some((bdc_header, bdc_packet)) + } +} + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct EthernetHeader { + pub destination_mac: [u8; 6], + pub source_mac: [u8; 6], + pub ether_type: u16, +} + +impl EthernetHeader { + pub fn byteswap(&mut self) { + self.ether_type = self.ether_type.to_be(); + } +} + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct EventHeader { + pub subtype: u16, + pub length: u16, + pub version: u8, + pub oui: [u8; 3], + pub user_subtype: u16, +} + +impl EventHeader { + pub fn byteswap(&mut self) { + self.subtype = self.subtype.to_be(); + self.length = self.length.to_be(); + self.user_subtype = self.user_subtype.to_be(); + } +} + +#[derive(Debug, Clone, Copy)] +// #[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C, packed(2))] +pub struct EventMessage { + /// version + pub version: u16, + /// see flags below + pub flags: u16, + /// Message (see below) + pub event_type: u32, + /// Status code (see below) + pub status: u32, + /// Reason code (if applicable) + pub reason: u32, + /// WLC_E_AUTH + pub auth_type: u32, + /// data buf + pub datalen: u32, + /// Station address (if applicable) + pub addr: [u8; 6], + /// name of the incoming packet interface + pub ifname: [u8; 16], + /// destination OS i/f index + pub ifidx: u8, + /// source bsscfg index + pub bsscfgidx: u8, +} +impl_bytes!(EventMessage); + +#[cfg(feature = "defmt")] +impl defmt::Format for EventMessage { + fn format(&self, fmt: defmt::Formatter) { + let event_type = self.event_type; + let status = self.status; + let reason = self.reason; + let auth_type = self.auth_type; + let datalen = self.datalen; + + defmt::write!( + fmt, + "EventMessage {{ \ + version: {=u16}, \ + flags: {=u16}, \ + event_type: {=u32}, \ + status: {=u32}, \ + reason: {=u32}, \ + auth_type: {=u32}, \ + datalen: {=u32}, \ + addr: {=[u8; 6]:x}, \ + ifname: {=[u8; 16]:x}, \ + ifidx: {=u8}, \ + bsscfgidx: {=u8}, \ + }} ", + self.version, + self.flags, + event_type, + status, + reason, + auth_type, + datalen, + self.addr, + self.ifname, + self.ifidx, + self.bsscfgidx + ); + } +} + +impl EventMessage { + pub fn byteswap(&mut self) { + self.version = self.version.to_be(); + self.flags = self.flags.to_be(); + self.event_type = self.event_type.to_be(); + self.status = self.status.to_be(); + self.reason = self.reason.to_be(); + self.auth_type = self.auth_type.to_be(); + self.datalen = self.datalen.to_be(); + } +} + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C, packed(2))] +pub struct EventPacket { + pub eth: EthernetHeader, + pub hdr: EventHeader, + pub msg: EventMessage, +} +impl_bytes!(EventPacket); + +impl EventPacket { + pub fn parse(packet: &mut [u8]) -> Option<(&mut Self, &mut [u8])> { + if packet.len() < Self::SIZE { + return None; + } + + let (event_header, event_packet) = packet.split_at_mut(Self::SIZE); + let event_header = Self::from_bytes_mut(event_header.try_into().unwrap()); + // warn!("event_header {:x}", event_header as *const _); + event_header.byteswap(); + + let event_packet = event_packet.get_mut(..event_header.msg.datalen as usize)?; + + Some((event_header, event_packet)) + } + + pub fn byteswap(&mut self) { + self.eth.byteswap(); + self.hdr.byteswap(); + self.msg.byteswap(); + } +} + +#[derive(Clone, Copy)] +#[repr(C)] +pub struct DownloadHeader { + pub flag: u16, // + pub dload_type: u16, + pub len: u32, + pub crc: u32, +} +impl_bytes!(DownloadHeader); + +#[allow(unused)] +pub const DOWNLOAD_FLAG_NO_CRC: u16 = 0x0001; +pub const DOWNLOAD_FLAG_BEGIN: u16 = 0x0002; +pub const DOWNLOAD_FLAG_END: u16 = 0x0004; +pub const DOWNLOAD_FLAG_HANDLER_VER: u16 = 0x1000; + +// Country Locale Matrix (CLM) +pub const DOWNLOAD_TYPE_CLM: u16 = 2; + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct CountryInfo { + pub country_abbrev: [u8; 4], + pub rev: i32, + pub country_code: [u8; 4], +} +impl_bytes!(CountryInfo); + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct SsidInfo { + pub len: u32, + pub ssid: [u8; 32], +} +impl_bytes!(SsidInfo); + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct PassphraseInfo { + pub len: u16, + pub flags: u16, + pub passphrase: [u8; 64], +} +impl_bytes!(PassphraseInfo); + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct SsidInfoWithIndex { + pub index: u32, + pub ssid_info: SsidInfo, +} +impl_bytes!(SsidInfoWithIndex); + +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct EventMask { + pub iface: u32, + pub events: [u8; 24], +} +impl_bytes!(EventMask); + +impl EventMask { + pub fn unset(&mut self, evt: Event) { + let evt = evt as u8 as usize; + self.events[evt / 8] &= !(1 << (evt % 8)); + } +} + +/// Parameters for a wifi scan +#[derive(Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C)] +pub struct ScanParams { + pub version: u32, + pub action: u16, + pub sync_id: u16, + pub ssid_len: u32, + pub ssid: [u8; 32], + pub bssid: [u8; 6], + pub bss_type: u8, + pub scan_type: u8, + pub nprobes: u32, + pub active_time: u32, + pub passive_time: u32, + pub home_time: u32, + pub channel_num: u32, + pub channel_list: [u16; 1], +} +impl_bytes!(ScanParams); + +/// Wifi Scan Results Header, followed by `bss_count` `BssInfo` +#[derive(Clone, Copy)] +// #[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C, packed(2))] +pub struct ScanResults { + pub buflen: u32, + pub version: u32, + pub sync_id: u16, + pub bss_count: u16, +} +impl_bytes!(ScanResults); + +impl ScanResults { + pub fn parse(packet: &mut [u8]) -> Option<(&mut ScanResults, &mut [u8])> { + if packet.len() < ScanResults::SIZE { + return None; + } + + let (scan_results, bssinfo) = packet.split_at_mut(ScanResults::SIZE); + let scan_results = ScanResults::from_bytes_mut(scan_results.try_into().unwrap()); + + if scan_results.bss_count > 0 && bssinfo.len() < BssInfo::SIZE { + warn!("Scan result, incomplete BssInfo"); + return None; + } + + Some((scan_results, bssinfo)) + } +} + +/// Wifi Scan Result +#[derive(Clone, Copy)] +// #[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(C, packed(2))] +#[non_exhaustive] +pub struct BssInfo { + pub version: u32, + pub length: u32, + pub bssid: [u8; 6], + pub beacon_period: u16, + pub capability: u16, + pub ssid_len: u8, + pub ssid: [u8; 32], + // there will be more stuff here +} +impl_bytes!(BssInfo); + +impl BssInfo { + pub fn parse(packet: &mut [u8]) -> Option<&mut Self> { + if packet.len() < BssInfo::SIZE { + return None; + } + + Some(BssInfo::from_bytes_mut( + packet[..BssInfo::SIZE].as_mut().try_into().unwrap(), + )) + } +} diff --git a/docs/modules/ROOT/examples/basic/Cargo.toml b/docs/modules/ROOT/examples/basic/Cargo.toml index ae124a871..237ae0ac2 100644 --- a/docs/modules/ROOT/examples/basic/Cargo.toml +++ b/docs/modules/ROOT/examples/basic/Cargo.toml @@ -3,16 +3,16 @@ authors = ["Dario Nieuwenhuis "] edition = "2018" name = "embassy-basic-example" version = "0.1.0" +license = "MIT OR Apache-2.0" [dependencies] -embassy-executor = { version = "0.1.0", path = "../../../../../embassy-executor", features = ["defmt", "nightly"] } +embassy-executor = { version = "0.2.0", path = "../../../../../embassy-executor", features = ["defmt", "nightly", "integrated-timers", "arch-cortex-m", "executor-thread"] } embassy-time = { version = "0.1.0", path = "../../../../../embassy-time", features = ["defmt", "nightly"] } embassy-nrf = { version = "0.1.0", path = "../../../../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "nightly"] } defmt = "0.3" defmt-rtt = "0.3" -cortex-m = "0.7.3" +cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rt = "0.7.0" -embedded-hal = "0.2.6" panic-probe = { version = "0.3", features = ["print-defmt"] } diff --git a/examples/nrf/build.rs b/docs/modules/ROOT/examples/basic/build.rs similarity index 100% rename from examples/nrf/build.rs rename to docs/modules/ROOT/examples/basic/build.rs diff --git a/examples/nrf/memory.x b/docs/modules/ROOT/examples/basic/memory.x similarity index 100% rename from examples/nrf/memory.x rename to docs/modules/ROOT/examples/basic/memory.x diff --git a/docs/modules/ROOT/examples/layer-by-layer/Cargo.toml b/docs/modules/ROOT/examples/layer-by-layer/Cargo.toml index 9048d9302..943249a17 100644 --- a/docs/modules/ROOT/examples/layer-by-layer/Cargo.toml +++ b/docs/modules/ROOT/examples/layer-by-layer/Cargo.toml @@ -10,7 +10,6 @@ members = [ [patch.crates-io] embassy-executor = { path = "../../../../../embassy-executor" } embassy-stm32 = { path = "../../../../../embassy-stm32" } -stm32-metapac = { path = "../../../../../stm32-metapac" } [profile.release] codegen-units = 1 diff --git a/docs/modules/ROOT/examples/layer-by-layer/blinky-async/Cargo.toml b/docs/modules/ROOT/examples/layer-by-layer/blinky-async/Cargo.toml index e2933076f..a7236ed5e 100644 --- a/docs/modules/ROOT/examples/layer-by-layer/blinky-async/Cargo.toml +++ b/docs/modules/ROOT/examples/layer-by-layer/blinky-async/Cargo.toml @@ -2,12 +2,13 @@ name = "blinky-async" version = "0.1.0" edition = "2021" +license = "MIT OR Apache-2.0" [dependencies] cortex-m = "0.7" cortex-m-rt = "0.7" -embassy-stm32 = { version = "0.1.0", features = ["stm32l475vg", "memory-x", "exti"], default-features = false } -embassy-executor = { version = "0.1.0", default-features = false, features = ["nightly"] } +embassy-stm32 = { version = "0.1.0", features = ["stm32l475vg", "memory-x", "exti"] } +embassy-executor = { version = "0.2.0", features = ["nightly", "arch-cortex-m", "executor-thread"] } defmt = "0.3.0" defmt-rtt = "0.3.0" diff --git a/docs/modules/ROOT/examples/layer-by-layer/blinky-hal/Cargo.toml b/docs/modules/ROOT/examples/layer-by-layer/blinky-hal/Cargo.toml index dbd3aba8b..c15de2db2 100644 --- a/docs/modules/ROOT/examples/layer-by-layer/blinky-hal/Cargo.toml +++ b/docs/modules/ROOT/examples/layer-by-layer/blinky-hal/Cargo.toml @@ -2,11 +2,12 @@ name = "blinky-hal" version = "0.1.0" edition = "2021" +license = "MIT OR Apache-2.0" [dependencies] cortex-m = "0.7" cortex-m-rt = "0.7" -embassy-stm32 = { version = "0.1.0", features = ["stm32l475vg", "memory-x"], default-features = false } +embassy-stm32 = { version = "0.1.0", features = ["stm32l475vg", "memory-x"] } defmt = "0.3.0" defmt-rtt = "0.3.0" diff --git a/docs/modules/ROOT/examples/layer-by-layer/blinky-irq/Cargo.toml b/docs/modules/ROOT/examples/layer-by-layer/blinky-irq/Cargo.toml index 0dd326015..9733658b6 100644 --- a/docs/modules/ROOT/examples/layer-by-layer/blinky-irq/Cargo.toml +++ b/docs/modules/ROOT/examples/layer-by-layer/blinky-irq/Cargo.toml @@ -2,6 +2,7 @@ name = "blinky-irq" version = "0.1.0" edition = "2021" +license = "MIT OR Apache-2.0" [dependencies] cortex-m = "0.7" diff --git a/docs/modules/ROOT/examples/layer-by-layer/blinky-irq/src/main.rs b/docs/modules/ROOT/examples/layer-by-layer/blinky-irq/src/main.rs index 743d0c342..aecba0755 100644 --- a/docs/modules/ROOT/examples/layer-by-layer/blinky-irq/src/main.rs +++ b/docs/modules/ROOT/examples/layer-by-layer/blinky-irq/src/main.rs @@ -20,13 +20,13 @@ fn main() -> ! { let led = Output::new(p.PB14, Level::Low, Speed::Low); let mut button = Input::new(p.PC13, Pull::Up); - cortex_m::interrupt::free(|cs| unsafe { + cortex_m::interrupt::free(|cs| { enable_interrupt(&mut button); LED.borrow(cs).borrow_mut().replace(led); BUTTON.borrow(cs).borrow_mut().replace(button); - NVIC::unmask(pac::Interrupt::EXTI15_10); + unsafe { NVIC::unmask(pac::Interrupt::EXTI15_10) }; }); loop { @@ -64,25 +64,21 @@ const PORT: u8 = 2; const PIN: usize = 13; fn check_interrupt(_pin: &mut Input<'static, P>) -> bool { let exti = pac::EXTI; - unsafe { - let pin = PIN; - let lines = exti.pr(0).read(); - lines.line(pin) - } + let pin = PIN; + let lines = exti.pr(0).read(); + lines.line(pin) } fn clear_interrupt(_pin: &mut Input<'static, P>) { let exti = pac::EXTI; - unsafe { - let pin = PIN; - let mut lines = exti.pr(0).read(); - lines.set_line(pin, true); - exti.pr(0).write_value(lines); - } + let pin = PIN; + let mut lines = exti.pr(0).read(); + lines.set_line(pin, true); + exti.pr(0).write_value(lines); } fn enable_interrupt(_pin: &mut Input<'static, P>) { - cortex_m::interrupt::free(|_| unsafe { + cortex_m::interrupt::free(|_| { let rcc = pac::RCC; rcc.apb2enr().modify(|w| w.set_syscfgen(true)); diff --git a/docs/modules/ROOT/examples/layer-by-layer/blinky-pac/Cargo.toml b/docs/modules/ROOT/examples/layer-by-layer/blinky-pac/Cargo.toml index e7f4f5d1f..f872b94cb 100644 --- a/docs/modules/ROOT/examples/layer-by-layer/blinky-pac/Cargo.toml +++ b/docs/modules/ROOT/examples/layer-by-layer/blinky-pac/Cargo.toml @@ -2,11 +2,12 @@ name = "blinky-pac" version = "0.1.0" edition = "2021" +license = "MIT OR Apache-2.0" [dependencies] cortex-m = "0.7" cortex-m-rt = "0.7" -stm32-metapac = { version = "0.1.0", features = ["stm32l475vg", "memory-x"] } +stm32-metapac = { version = "1", features = ["stm32l475vg", "memory-x"] } defmt = "0.3.0" defmt-rtt = "0.3.0" diff --git a/docs/modules/ROOT/pages/basic_application.adoc b/docs/modules/ROOT/pages/basic_application.adoc index 4dc4a6359..3f4f16e28 100644 --- a/docs/modules/ROOT/pages/basic_application.adoc +++ b/docs/modules/ROOT/pages/basic_application.adoc @@ -21,7 +21,7 @@ Then, what follows are some declarations on how to deal with panics and faults. [source,rust] ---- -include::example$basic/src/main.rs[lines="11..12"] +include::example$basic/src/main.rs[lines="10"] ---- === Task declaration @@ -30,7 +30,7 @@ After a bit of import declaration, the tasks run by the application should be de [source,rust] ---- -include::example$basic/src/main.rs[lines="13..22"] +include::example$basic/src/main.rs[lines="12..20"] ---- An embassy task must be declared `async`, and may NOT take generic arguments. In this case, we are handed the LED that should be blinked and the interval of the blinking. @@ -45,23 +45,10 @@ The `Spawner` is the way the main application spawns other tasks. The `Periphera [source,rust] ---- -include::example$basic/src/main.rs[lines="23..-1"] +include::example$basic/src/main.rs[lines="22..-1"] ---- -`#[embassy_executor::main]` takes an optional `config` parameter specifying a function that returns an instance of HAL's `Config` struct. For example: - -```rust -fn embassy_config() -> embassy_nrf::config::Config { - embassy_nrf::config::Config::default() -} - -#[embassy_executor::main(config = "embassy_config()")] -async fn main(_spawner: Spawner, p: embassy_nrf::Peripherals) { - // ... -} -``` - -What happens when the `blinker` task have been spawned and main returns? Well, the main entry point is actually just like any other task, except that you can only have one and it takes some specific type arguments. The magic lies within the `#[embassy::main]` macro. The macro does the following: +What happens when the `blinker` task has been spawned and main returns? Well, the main entry point is actually just like any other task, except that you can only have one and it takes some specific type arguments. The magic lies within the `#[embassy::main]` macro. The macro does the following: . Creates an Embassy Executor . Initializes the microcontroller HAL to get the `Peripherals` @@ -76,7 +63,7 @@ The project definition needs to contain the embassy dependencies: [source,toml] ---- -include::example$basic/Cargo.toml[lines="8..9"] +include::example$basic/Cargo.toml[lines="9..11"] ---- Depending on your microcontroller, you may need to replace `embassy-nrf` with something else (`embassy-stm32` for STM32. Remember to update feature flags as well). diff --git a/docs/modules/ROOT/pages/bootloader.adoc b/docs/modules/ROOT/pages/bootloader.adoc index ae92e9d5d..b7215e52a 100644 --- a/docs/modules/ROOT/pages/bootloader.adoc +++ b/docs/modules/ROOT/pages/bootloader.adoc @@ -6,7 +6,7 @@ The bootloader can be used either as a library or be flashed directly if you are By design, the bootloader does not provide any network capabilities. Networking capabilities for fetching new firmware can be provided by the user application, using the bootloader as a library for updating the firmware, or by using the bootloader as a library and adding this capability yourself. -The bootloader supports both internal and external flash by relying on the `embedded-storage` traits. +The bootloader supports both internal and external flash by relying on the `embedded-storage` traits. The bootloader optionally supports the verification of firmware that has been digitally signed (recommended). == Hardware support @@ -15,6 +15,7 @@ The bootloader supports * nRF52 with and without softdevice * STM32 L4, WB, WL, L1, L0, F3, F7 and H7 +* Raspberry Pi: RP2040 In general, the bootloader works on any platform that implements the `embedded-storage` traits for its internal flash, but may require custom initialization code to work. @@ -25,12 +26,69 @@ image::bootloader_flash.png[Bootloader flash layout] The bootloader divides the storage into 4 main partitions, configurable when creating the bootloader instance or via linker scripts: -* BOOTLOADER - Where the bootloader is placed. The bootloader itself consumes about 8kB of flash. -* ACTIVE - Where the main application is placed. The bootloader will attempt to load the application at the start of this partition. This partition is only written to by the bootloader. -* DFU - Where the application-to-be-swapped is placed. This partition is written to by the application. -* BOOTLOADER STATE - Where the bootloader stores the current state describing if the active and dfu partitions need to be swapped. When the new firmware has been written to the DFU partition, a flag is set to instruct the bootloader that the partitions should be swapped. +* BOOTLOADER - Where the bootloader is placed. The bootloader itself consumes about 8kB of flash, but if you need to debug it and have space available, increasing this to 24kB will allow you to run the bootloader with probe-rs. +* ACTIVE - Where the main application is placed. The bootloader will attempt to load the application at the start of this partition. This partition is only written to by the bootloader. The size required for this partition depends on the size of your application. +* DFU - Where the application-to-be-swapped is placed. This partition is written to by the application. This partition must be at least 1 page bigger than the ACTIVE partition, since the swap algorithm uses the extra space to ensure power safe copy of data: ++ +Partition Size~dfu~= Partition Size~active~+ Page Size~active~ ++ +All values are specified in bytes. + +* BOOTLOADER STATE - Where the bootloader stores the current state describing if the active and dfu partitions need to be swapped. When the new firmware has been written to the DFU partition, a magic field is written to instruct the bootloader that the partitions should be swapped. This partition must be able to store a magic field as well as the partition swap progress. The partition size given by: ++ +Partition Size~state~ = Write Size~state~ + (2 × Partition Size~active~ / Page Size~active~) ++ +All values are specified in bytes. The partitions for ACTIVE (+BOOTLOADER), DFU and BOOTLOADER_STATE may be placed in separate flash. The page size used by the bootloader is determined by the lowest common multiple of the ACTIVE and DFU page sizes. The BOOTLOADER_STATE partition must be big enough to store one word per page in the ACTIVE and DFU partitions combined. The bootloader has a platform-agnostic part, which implements the power fail safe swapping algorithm given the boundaries set by the partitions. The platform-specific part is a minimal shim that provides additional functionality such as watchdogs or supporting the nRF52 softdevice. + +=== FirmwareUpdater + +The `FirmwareUpdater` is an object for conveniently flashing firmware to the DFU partition and subsequently marking it as being ready for swapping with the active partition on the next reset. Its principle methods are `write_firmware`, which is called once per the size of the flash "write block" (typically 4KiB), and `mark_updated`, which is the final call. + +=== Verification + +The bootloader supports the verification of firmware that has been flashed to the DFU partition. Verification requires that firmware has been signed digitally using link:https://ed25519.cr.yp.to/[`ed25519`] signatures. With verification enabled, the `FirmwareUpdater::verify_and_mark_updated` method is called in place of `mark_updated`. A public key and signature are required, along with the actual length of the firmware that has been flashed. If verification fails then the firmware will not be marked as updated and therefore be rejected. + +Signatures are normally conveyed with the firmware to be updated and not written to flash. How signatures are provided is a firmware responsibility. + +To enable verification use either the `ed25519-dalek` or `ed25519-salty` features when depending on the `embassy-boot` crate. We recommend `ed25519-salty` at this time due to its small size. + +==== Tips on keys and signing with ed25519 + +Ed25519 is a public key signature system where you are responsible for keeping the private key secure. We recommend embedding the *public* key in your program so that it can be easily passed to `verify_and_mark_updated`. An example declaration of the public key in your firmware: + +[source, rust] +---- +static PUBLIC_SIGNING_KEY: &[u8] = include_bytes!("key.pub"); +---- + +Signatures are often conveyed along with firmware by appending them. + +Ed25519 keys can be generated by a variety of tools. We recommend link:https://man.openbsd.org/signify[`signify`] as it is in wide use to sign and verify OpenBSD distributions, and is straightforward to use. + +The following set of Bash commands can be used to generate public and private keys on Unix platforms, and also generate a local `key.pub` file with the `signify` file headers removed. Declare a `SECRETS_DIR` environment variable in a secure location. + +[source, bash] +---- +signify -G -n -p $SECRETS_DIR/key.pub -s $SECRETS_DIR/key.sec +tail -n1 $SECRETS_DIR/key.pub | base64 -d -i - | dd ibs=10 skip=1 > key.pub +chmod 700 $SECRETS_DIR/key.sec +export SECRET_SIGNING_KEY=$(tail -n1 $SECRETS_DIR/key.sec) +---- + +Then, to sign your firmware given a declaration of `FIRMWARE_DIR` and a firmware filename of `myfirmware`: + +[source, bash] +---- +shasum -a 512 -b $FIRMWARE_DIR/myfirmware > $SECRETS_DIR/message.txt +cat $SECRETS_DIR/message.txt | dd ibs=128 count=1 | xxd -p -r > $SECRETS_DIR/message.txt +signify -S -s $SECRETS_DIR/key.sec -m $SECRETS_DIR/message.txt -x $SECRETS_DIR/message.txt.sig +cp $FIRMWARE_DIR/myfirmware $FIRMWARE_DIR/myfirmware+signed +tail -n1 $SECRETS_DIR/message.txt.sig | base64 -d -i - | dd ibs=10 skip=1 >> $FIRMWARE_DIR/myfirmware+signed +---- + +Remember, guard the `$SECRETS_DIR/key.sec` key as compromising it means that another party can sign your firmware. \ No newline at end of file diff --git a/docs/modules/ROOT/pages/getting_started.adoc b/docs/modules/ROOT/pages/getting_started.adoc index f3492a3d0..881e449b6 100644 --- a/docs/modules/ROOT/pages/getting_started.adoc +++ b/docs/modules/ROOT/pages/getting_started.adoc @@ -45,11 +45,11 @@ You can run an example by opening a terminal and entering the following commands [source, bash] ---- -cd examples/nrf +cd examples/nrf52840 cargo run --bin blinky --release ---- -== Whats next? +== What's next? Congratulations, you have your first Embassy application running! Here are some alternatives on where to go from here: diff --git a/docs/modules/ROOT/pages/layer_by_layer.adoc b/docs/modules/ROOT/pages/layer_by_layer.adoc index a96dd9fe2..a78a64a97 100644 --- a/docs/modules/ROOT/pages/layer_by_layer.adoc +++ b/docs/modules/ROOT/pages/layer_by_layer.adoc @@ -8,7 +8,7 @@ The application we'll write is a simple 'push button, blink led' application, wh == PAC version -The PAC is the lowest API for accessing peripherals and registers, if you don't count reading/writing directly to memory addresses. It provide distinct types +The PAC is the lowest API for accessing peripherals and registers, if you don't count reading/writing directly to memory addresses. It provides distinct types to make accessing peripheral registers easier, but it does not prevent you from writing unsafe code. Writing an application using the PAC directly is therefore not recommended, but if the functionality you want to use is not exposed in the upper layers, that's what you need to use. @@ -20,13 +20,13 @@ The blinky app using PAC is shown below: include::example$layer-by-layer/blinky-pac/src/main.rs[] ---- -As you can see, there are a lot of code needed to enable the peripheral clocks, configuring the input pins and the output pins of the application. +As you can see, a lot of code is needed to enable the peripheral clocks and to configure the input pins and the output pins of the application. Another downside of this application is that it is busy-looping while polling the button state. This prevents the microcontroller from utilizing any sleep mode to save power. == HAL version -To simplify our application, we can use the HAL instead. The HAL exposes higher level APIs that handle details such +To simplify our application, we can use the HAL instead. The HAL exposes higher level APIs that handle details such as: * Automatically enabling the peripheral clock when you're using the peripheral * Deriving and applying register configuration from higher level types @@ -39,7 +39,7 @@ The HAL example is shown below: include::example$layer-by-layer/blinky-hal/src/main.rs[] ---- -As you can see, the application becomes a lot simpler, even without using any async code. The `Input` and `Output` hides all the details accessing the GPIO registers, and allow you to use a much simpler API to query the state of the button and toggle the LED output accordingly. +As you can see, the application becomes a lot simpler, even without using any async code. The `Input` and `Output` types hide all the details of accessing the GPIO registers and allow you to use a much simpler API for querying the state of the button and toggling the LED output. The same downside from the PAC example still applies though: the application is busy looping and consuming more power than necessary. diff --git a/docs/modules/ROOT/pages/runtime.adoc b/docs/modules/ROOT/pages/runtime.adoc index a7d6a8d0c..5096f5a43 100644 --- a/docs/modules/ROOT/pages/runtime.adoc +++ b/docs/modules/ROOT/pages/runtime.adoc @@ -20,7 +20,7 @@ IMPORTANT: The executor relies on tasks not blocking indefinitely, as this preve image::embassy_executor.png[Executor model] -If you use the `#[embassy::main]` macro in your application, it creates the `Executor` for you and spawns the main entry point as the first task. You can also create the Executor manually, and you can in fact create multiple Executors. +If you use the `#[embassy_executor::main]` macro in your application, it creates the `Executor` for you and spawns the main entry point as the first task. You can also create the Executor manually, and you can in fact create multiple Executors. == Interrupts diff --git a/docs/modules/ROOT/pages/stm32.adoc b/docs/modules/ROOT/pages/stm32.adoc index 8ed9ab04b..7bfc0592b 100644 --- a/docs/modules/ROOT/pages/stm32.adoc +++ b/docs/modules/ROOT/pages/stm32.adoc @@ -4,9 +4,9 @@ The link:https://github.com/embassy-rs/embassy/tree/master/embassy-stm32[Embassy == The infinite variant problem -STM32 microcontrollers comes in many families and flavors, and supporting all of them is a big undertaking. Embassy has taken advantage of the fact +STM32 microcontrollers come in many families, and flavors and supporting all of them is a big undertaking. Embassy has taken advantage of the fact that the STM32 peripheral versions are shared across chip families. Instead of re-implementing the SPI -peripheral for every STM32 chip family, embassy have a single SPI implementation that depends on +peripheral for every STM32 chip family, embassy has a single SPI implementation that depends on code-generated register types that are identical for STM32 families with the same version of a given peripheral. === The metapac diff --git a/embassy-boot/boot/Cargo.toml b/embassy-boot/boot/Cargo.toml index a42f88688..415d7960f 100644 --- a/embassy-boot/boot/Cargo.toml +++ b/embassy-boot/boot/Cargo.toml @@ -1,25 +1,56 @@ [package] edition = "2021" name = "embassy-boot" -version = "0.1.0" -description = "Bootloader using Embassy" +version = "0.1.1" +description = "A lightweight bootloader supporting firmware updates in a power-fail-safe way, with trial boots and rollbacks." +license = "MIT OR Apache-2.0" +repository = "https://github.com/embassy-rs/embassy" +categories = [ + "embedded", + "no-std", + "asynchronous", +] [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-v$VERSION/embassy-boot/boot/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot/boot/src/" target = "thumbv7em-none-eabi" +features = ["defmt"] + +[package.metadata.docs.rs] +features = ["defmt"] [lib] [dependencies] defmt = { version = "0.3", optional = true } +digest = "0.10" log = { version = "0.4", optional = true } -embassy-sync = { version = "0.1.0", path = "../../embassy-sync" } +ed25519-dalek = { version = "1.0.1", default_features = false, features = ["u32_backend"], optional = true } +embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" } +embassy-sync = { version = "0.2.0", path = "../../embassy-sync" } embedded-storage = "0.3.0" -embedded-storage-async = "0.3.0" +embedded-storage-async = { version = "0.4.0", optional = true } +salty = { git = "https://github.com/ycrypto/salty.git", rev = "a9f17911a5024698406b75c0fac56ab5ccf6a8c7", optional = true } +signature = { version = "1.6.4", default-features = false } [dev-dependencies] log = "0.4" env_logger = "0.9" -rand = "0.8" +rand = "0.7" # ed25519-dalek v1.0.1 depends on this exact version futures = { version = "0.3", features = ["executor"] } +sha1 = "0.10.5" +critical-section = { version = "1.1.1", features = ["std"] } + +[dev-dependencies.ed25519-dalek] +default_features = false +features = ["rand", "std", "u32_backend"] + +[features] +ed25519-dalek = ["dep:ed25519-dalek", "_verify"] +ed25519-salty = ["dep:salty", "_verify"] + +nightly = ["dep:embedded-storage-async", "embassy-embedded-hal/nightly"] + +#Internal features +_verify = [] diff --git a/embassy-boot/boot/README.md b/embassy-boot/boot/README.md new file mode 100644 index 000000000..07755bc6c --- /dev/null +++ b/embassy-boot/boot/README.md @@ -0,0 +1,31 @@ +# embassy-boot + +An [Embassy](https://embassy.dev) project. + +A lightweight bootloader supporting firmware updates in a power-fail-safe way, with trial boots and rollbacks. + +The bootloader can be used either as a library or be flashed directly with the default configuration derived from linker scripts. + +By design, the bootloader does not provide any network capabilities. Networking capabilities for fetching new firmware can be provided by the user application, using the bootloader as a library for updating the firmware, or by using the bootloader as a library and adding this capability yourself. + +## Hardware support + +The bootloader supports different hardware in separate crates: + +* `embassy-boot-nrf` - for the nRF microcontrollers. +* `embassy-boot-rp` - for the RP2040 microcontrollers. +* `embassy-boot-stm32` - for the STM32 microcontrollers. + +## Minimum supported Rust version (MSRV) + +`embassy-boot` is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release. + +## License + +This work is licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. diff --git a/embassy-boot/boot/src/boot_loader.rs b/embassy-boot/boot/src/boot_loader.rs new file mode 100644 index 000000000..a8c19197b --- /dev/null +++ b/embassy-boot/boot/src/boot_loader.rs @@ -0,0 +1,421 @@ +use core::cell::RefCell; + +use embassy_embedded_hal::flash::partition::BlockingPartition; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind}; + +use crate::{State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; + +/// Errors returned by bootloader +#[derive(PartialEq, Eq, Debug)] +pub enum BootError { + /// Error from flash. + Flash(NorFlashErrorKind), + /// Invalid bootloader magic + BadMagic, +} + +#[cfg(feature = "defmt")] +impl defmt::Format for BootError { + fn format(&self, fmt: defmt::Formatter) { + match self { + BootError::Flash(_) => defmt::write!(fmt, "BootError::Flash(_)"), + BootError::BadMagic => defmt::write!(fmt, "BootError::BadMagic"), + } + } +} + +impl From for BootError +where + E: NorFlashError, +{ + fn from(error: E) -> Self { + BootError::Flash(error.kind()) + } +} + +/// Bootloader flash configuration holding the three flashes used by the bootloader +/// +/// If only a single flash is actually used, then that flash should be partitioned into three partitions before use. +/// The easiest way to do this is to use [`BootLoaderConfig::from_linkerfile_blocking`] which will partition +/// the provided flash according to symbols defined in the linkerfile. +pub struct BootLoaderConfig { + /// Flash type used for the active partition - the partition which will be booted from. + pub active: ACTIVE, + /// Flash type used for the dfu partition - the partition which will be swapped in when requested. + pub dfu: DFU, + /// Flash type used for the state partition. + pub state: STATE, +} + +impl<'a, FLASH: NorFlash> + BootLoaderConfig< + BlockingPartition<'a, NoopRawMutex, FLASH>, + BlockingPartition<'a, NoopRawMutex, FLASH>, + BlockingPartition<'a, NoopRawMutex, FLASH>, + > +{ + /// Create a bootloader config from the flash and address symbols defined in the linkerfile + // #[cfg(target_os = "none")] + pub fn from_linkerfile_blocking(flash: &'a Mutex>) -> Self { + extern "C" { + static __bootloader_state_start: u32; + static __bootloader_state_end: u32; + static __bootloader_active_start: u32; + static __bootloader_active_end: u32; + static __bootloader_dfu_start: u32; + static __bootloader_dfu_end: u32; + } + + let active = unsafe { + let start = &__bootloader_active_start as *const u32 as u32; + let end = &__bootloader_active_end as *const u32 as u32; + trace!("ACTIVE: 0x{:x} - 0x{:x}", start, end); + + BlockingPartition::new(flash, start, end - start) + }; + let dfu = unsafe { + let start = &__bootloader_dfu_start as *const u32 as u32; + let end = &__bootloader_dfu_end as *const u32 as u32; + trace!("DFU: 0x{:x} - 0x{:x}", start, end); + + BlockingPartition::new(flash, start, end - start) + }; + let state = unsafe { + let start = &__bootloader_state_start as *const u32 as u32; + let end = &__bootloader_state_end as *const u32 as u32; + trace!("STATE: 0x{:x} - 0x{:x}", start, end); + + BlockingPartition::new(flash, start, end - start) + }; + + Self { active, dfu, state } + } +} + +/// BootLoader works with any flash implementing embedded_storage. +pub struct BootLoader { + active: ACTIVE, + dfu: DFU, + /// The state partition has the following format: + /// All ranges are in multiples of WRITE_SIZE bytes. + /// | Range | Description | + /// | 0..1 | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. | + /// | 1..2 | Progress validity. ERASE_VALUE means valid, !ERASE_VALUE means invalid. | + /// | 2..2 + N | Progress index used while swapping or reverting + state: STATE, +} + +impl BootLoader { + /// Get the page size which is the "unit of operation" within the bootloader. + const PAGE_SIZE: u32 = if ACTIVE::ERASE_SIZE > DFU::ERASE_SIZE { + ACTIVE::ERASE_SIZE as u32 + } else { + DFU::ERASE_SIZE as u32 + }; + + /// Create a new instance of a bootloader with the flash partitions. + /// + /// - All partitions must be aligned with the PAGE_SIZE const generic parameter. + /// - The dfu partition must be at least PAGE_SIZE bigger than the active partition. + pub fn new(config: BootLoaderConfig) -> Self { + Self { + active: config.active, + dfu: config.dfu, + state: config.state, + } + } + + /// Perform necessary boot preparations like swapping images. + /// + /// The DFU partition is assumed to be 1 page bigger than the active partition for the swap + /// algorithm to work correctly. + /// + /// The provided aligned_buf argument must satisfy any alignment requirements + /// given by the partition flashes. All flash operations will use this buffer. + /// + /// SWAPPING + /// + /// Assume a flash size of 3 pages for the active partition, and 4 pages for the DFU partition. + /// The swap index contains the copy progress, as to allow continuation of the copy process on + /// power failure. The index counter is represented within 1 or more pages (depending on total + /// flash size), where a page X is considered swapped if index at location (X + WRITE_SIZE) + /// contains a zero value. This ensures that index updates can be performed atomically and + /// avoid a situation where the wrong index value is set (page write size is "atomic"). + /// + /// +-----------+------------+--------+--------+--------+--------+ + /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | + /// +-----------+------------+--------+--------+--------+--------+ + /// | Active | 0 | 1 | 2 | 3 | - | + /// | DFU | 0 | 3 | 2 | 1 | X | + /// +-----------+------------+--------+--------+--------+--------+ + /// + /// The algorithm starts by copying 'backwards', and after the first step, the layout is + /// as follows: + /// + /// +-----------+------------+--------+--------+--------+--------+ + /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | + /// +-----------+------------+--------+--------+--------+--------+ + /// | Active | 1 | 1 | 2 | 1 | - | + /// | DFU | 1 | 3 | 2 | 1 | 3 | + /// +-----------+------------+--------+--------+--------+--------+ + /// + /// The next iteration performs the same steps + /// + /// +-----------+------------+--------+--------+--------+--------+ + /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | + /// +-----------+------------+--------+--------+--------+--------+ + /// | Active | 2 | 1 | 2 | 1 | - | + /// | DFU | 2 | 3 | 2 | 2 | 3 | + /// +-----------+------------+--------+--------+--------+--------+ + /// + /// And again until we're done + /// + /// +-----------+------------+--------+--------+--------+--------+ + /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | + /// +-----------+------------+--------+--------+--------+--------+ + /// | Active | 3 | 3 | 2 | 1 | - | + /// | DFU | 3 | 3 | 1 | 2 | 3 | + /// +-----------+------------+--------+--------+--------+--------+ + /// + /// REVERTING + /// + /// The reverting algorithm uses the swap index to discover that images were swapped, but that + /// the application failed to mark the boot successful. In this case, the revert algorithm will + /// run. + /// + /// The revert index is located separately from the swap index, to ensure that revert can continue + /// on power failure. + /// + /// The revert algorithm works forwards, by starting copying into the 'unused' DFU page at the start. + /// + /// +-----------+--------------+--------+--------+--------+--------+ + /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | + //*/ + /// +-----------+--------------+--------+--------+--------+--------+ + /// | Active | 3 | 1 | 2 | 1 | - | + /// | DFU | 3 | 3 | 1 | 2 | 3 | + /// +-----------+--------------+--------+--------+--------+--------+ + /// + /// + /// +-----------+--------------+--------+--------+--------+--------+ + /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | + /// +-----------+--------------+--------+--------+--------+--------+ + /// | Active | 3 | 1 | 2 | 1 | - | + /// | DFU | 3 | 3 | 2 | 2 | 3 | + /// +-----------+--------------+--------+--------+--------+--------+ + /// + /// +-----------+--------------+--------+--------+--------+--------+ + /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | + /// +-----------+--------------+--------+--------+--------+--------+ + /// | Active | 3 | 1 | 2 | 3 | - | + /// | DFU | 3 | 3 | 2 | 1 | 3 | + /// +-----------+--------------+--------+--------+--------+--------+ + /// + pub fn prepare_boot(&mut self, aligned_buf: &mut [u8]) -> Result { + // Ensure we have enough progress pages to store copy progress + assert_eq!(0, Self::PAGE_SIZE % aligned_buf.len() as u32); + assert_eq!(0, Self::PAGE_SIZE % ACTIVE::WRITE_SIZE as u32); + assert_eq!(0, Self::PAGE_SIZE % ACTIVE::ERASE_SIZE as u32); + assert_eq!(0, Self::PAGE_SIZE % DFU::WRITE_SIZE as u32); + assert_eq!(0, Self::PAGE_SIZE % DFU::ERASE_SIZE as u32); + assert!(aligned_buf.len() >= STATE::WRITE_SIZE); + assert_eq!(0, aligned_buf.len() % ACTIVE::WRITE_SIZE); + assert_eq!(0, aligned_buf.len() % DFU::WRITE_SIZE); + + assert_partitions(&self.active, &self.dfu, &self.state, Self::PAGE_SIZE); + + // Copy contents from partition N to active + let state = self.read_state(aligned_buf)?; + if state == State::Swap { + // + // Check if we already swapped. If we're in the swap state, this means we should revert + // since the app has failed to mark boot as successful + // + if !self.is_swapped(aligned_buf)? { + trace!("Swapping"); + self.swap(aligned_buf)?; + trace!("Swapping done"); + } else { + trace!("Reverting"); + self.revert(aligned_buf)?; + + let state_word = &mut aligned_buf[..STATE::WRITE_SIZE]; + + // Invalidate progress + state_word.fill(!STATE_ERASE_VALUE); + self.state.write(STATE::WRITE_SIZE as u32, state_word)?; + + // Clear magic and progress + self.state.erase(0, self.state.capacity() as u32)?; + + // Set magic + state_word.fill(BOOT_MAGIC); + self.state.write(0, state_word)?; + } + } + Ok(state) + } + + fn is_swapped(&mut self, aligned_buf: &mut [u8]) -> Result { + let page_count = self.active.capacity() / Self::PAGE_SIZE as usize; + let progress = self.current_progress(aligned_buf)?; + + Ok(progress >= page_count * 2) + } + + fn current_progress(&mut self, aligned_buf: &mut [u8]) -> Result { + let write_size = STATE::WRITE_SIZE as u32; + let max_index = ((self.state.capacity() - STATE::WRITE_SIZE) / STATE::WRITE_SIZE) - 2; + let state_word = &mut aligned_buf[..write_size as usize]; + + self.state.read(write_size, state_word)?; + if state_word.iter().any(|&b| b != STATE_ERASE_VALUE) { + // Progress is invalid + return Ok(max_index); + } + + for index in 0..max_index { + self.state.read((2 + index) as u32 * write_size, state_word)?; + + if state_word.iter().any(|&b| b == STATE_ERASE_VALUE) { + return Ok(index); + } + } + Ok(max_index) + } + + fn update_progress(&mut self, progress_index: usize, aligned_buf: &mut [u8]) -> Result<(), BootError> { + let state_word = &mut aligned_buf[..STATE::WRITE_SIZE]; + state_word.fill(!STATE_ERASE_VALUE); + self.state + .write((2 + progress_index) as u32 * STATE::WRITE_SIZE as u32, state_word)?; + Ok(()) + } + + fn copy_page_once_to_active( + &mut self, + progress_index: usize, + from_offset: u32, + to_offset: u32, + aligned_buf: &mut [u8], + ) -> Result<(), BootError> { + if self.current_progress(aligned_buf)? <= progress_index { + let page_size = Self::PAGE_SIZE as u32; + + self.active.erase(to_offset, to_offset + page_size)?; + + for offset_in_page in (0..page_size).step_by(aligned_buf.len()) { + self.dfu.read(from_offset + offset_in_page as u32, aligned_buf)?; + self.active.write(to_offset + offset_in_page as u32, aligned_buf)?; + } + + self.update_progress(progress_index, aligned_buf)?; + } + Ok(()) + } + + fn copy_page_once_to_dfu( + &mut self, + progress_index: usize, + from_offset: u32, + to_offset: u32, + aligned_buf: &mut [u8], + ) -> Result<(), BootError> { + if self.current_progress(aligned_buf)? <= progress_index { + let page_size = Self::PAGE_SIZE as u32; + + self.dfu.erase(to_offset as u32, to_offset + page_size)?; + + for offset_in_page in (0..page_size).step_by(aligned_buf.len()) { + self.active.read(from_offset + offset_in_page as u32, aligned_buf)?; + self.dfu.write(to_offset + offset_in_page as u32, aligned_buf)?; + } + + self.update_progress(progress_index, aligned_buf)?; + } + Ok(()) + } + + fn swap(&mut self, aligned_buf: &mut [u8]) -> Result<(), BootError> { + let page_count = self.active.capacity() as u32 / Self::PAGE_SIZE; + for page_num in 0..page_count { + let progress_index = (page_num * 2) as usize; + + // Copy active page to the 'next' DFU page. + let active_from_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE; + let dfu_to_offset = (page_count - page_num) * Self::PAGE_SIZE; + //trace!("Copy active {} to dfu {}", active_from_offset, dfu_to_offset); + self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, aligned_buf)?; + + // Copy DFU page to the active page + let active_to_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE; + let dfu_from_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE; + //trace!("Copy dfy {} to active {}", dfu_from_offset, active_to_offset); + self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, aligned_buf)?; + } + + Ok(()) + } + + fn revert(&mut self, aligned_buf: &mut [u8]) -> Result<(), BootError> { + let page_count = self.active.capacity() as u32 / Self::PAGE_SIZE; + for page_num in 0..page_count { + let progress_index = (page_count * 2 + page_num * 2) as usize; + + // Copy the bad active page to the DFU page + let active_from_offset = page_num * Self::PAGE_SIZE; + let dfu_to_offset = page_num * Self::PAGE_SIZE; + self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, aligned_buf)?; + + // Copy the DFU page back to the active page + let active_to_offset = page_num * Self::PAGE_SIZE; + let dfu_from_offset = (page_num + 1) * Self::PAGE_SIZE; + self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, aligned_buf)?; + } + + Ok(()) + } + + fn read_state(&mut self, aligned_buf: &mut [u8]) -> Result { + let state_word = &mut aligned_buf[..STATE::WRITE_SIZE]; + self.state.read(0, state_word)?; + + if !state_word.iter().any(|&b| b != SWAP_MAGIC) { + Ok(State::Swap) + } else { + Ok(State::Boot) + } + } +} + +fn assert_partitions( + active: &ACTIVE, + dfu: &DFU, + state: &STATE, + page_size: u32, +) { + assert_eq!(active.capacity() as u32 % page_size, 0); + assert_eq!(dfu.capacity() as u32 % page_size, 0); + assert!(dfu.capacity() as u32 - active.capacity() as u32 >= page_size); + assert!(2 + 2 * (active.capacity() as u32 / page_size) <= state.capacity() as u32 / STATE::WRITE_SIZE as u32); +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mem_flash::MemFlash; + + #[test] + #[should_panic] + fn test_range_asserts() { + const ACTIVE_SIZE: usize = 4194304 - 4096; + const DFU_SIZE: usize = 4194304; + const STATE_SIZE: usize = 4096; + static ACTIVE: MemFlash = MemFlash::new(0xFF); + static DFU: MemFlash = MemFlash::new(0xFF); + static STATE: MemFlash = MemFlash::new(0xFF); + assert_partitions(&ACTIVE, &DFU, &STATE, 4096); + } +} diff --git a/embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs b/embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs new file mode 100644 index 000000000..a184d1c51 --- /dev/null +++ b/embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs @@ -0,0 +1,30 @@ +use digest::typenum::U64; +use digest::{FixedOutput, HashMarker, OutputSizeUser, Update}; +use ed25519_dalek::Digest as _; + +pub struct Sha512(ed25519_dalek::Sha512); + +impl Default for Sha512 { + fn default() -> Self { + Self(ed25519_dalek::Sha512::new()) + } +} + +impl Update for Sha512 { + fn update(&mut self, data: &[u8]) { + self.0.update(data) + } +} + +impl FixedOutput for Sha512 { + fn finalize_into(self, out: &mut digest::Output) { + let result = self.0.finalize(); + out.as_mut_slice().copy_from_slice(result.as_slice()) + } +} + +impl OutputSizeUser for Sha512 { + type OutputSize = U64; +} + +impl HashMarker for Sha512 {} diff --git a/embassy-boot/boot/src/digest_adapters/mod.rs b/embassy-boot/boot/src/digest_adapters/mod.rs new file mode 100644 index 000000000..9b4b4b60c --- /dev/null +++ b/embassy-boot/boot/src/digest_adapters/mod.rs @@ -0,0 +1,5 @@ +#[cfg(feature = "ed25519-dalek")] +pub(crate) mod ed25519_dalek; + +#[cfg(feature = "ed25519-salty")] +pub(crate) mod salty; diff --git a/embassy-boot/boot/src/digest_adapters/salty.rs b/embassy-boot/boot/src/digest_adapters/salty.rs new file mode 100644 index 000000000..2b5dcf3af --- /dev/null +++ b/embassy-boot/boot/src/digest_adapters/salty.rs @@ -0,0 +1,29 @@ +use digest::typenum::U64; +use digest::{FixedOutput, HashMarker, OutputSizeUser, Update}; + +pub struct Sha512(salty::Sha512); + +impl Default for Sha512 { + fn default() -> Self { + Self(salty::Sha512::new()) + } +} + +impl Update for Sha512 { + fn update(&mut self, data: &[u8]) { + self.0.update(data) + } +} + +impl FixedOutput for Sha512 { + fn finalize_into(self, out: &mut digest::Output) { + let result = self.0.finalize(); + out.as_mut_slice().copy_from_slice(result.as_slice()) + } +} + +impl OutputSizeUser for Sha512 { + type OutputSize = U64; +} + +impl HashMarker for Sha512 {} diff --git a/embassy-boot/boot/src/firmware_updater/asynch.rs b/embassy-boot/boot/src/firmware_updater/asynch.rs new file mode 100644 index 000000000..20731ee0a --- /dev/null +++ b/embassy-boot/boot/src/firmware_updater/asynch.rs @@ -0,0 +1,299 @@ +use digest::Digest; +#[cfg(target_os = "none")] +use embassy_embedded_hal::flash::partition::Partition; +#[cfg(target_os = "none")] +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embedded_storage_async::nor_flash::NorFlash; + +use super::FirmwareUpdaterConfig; +use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; + +/// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to +/// 'mess up' the internal bootloader state +pub struct FirmwareUpdater { + dfu: DFU, + state: STATE, +} + +#[cfg(target_os = "none")] +impl<'a, FLASH: NorFlash> + FirmwareUpdaterConfig, Partition<'a, NoopRawMutex, FLASH>> +{ + /// Create a firmware updater config from the flash and address symbols defined in the linkerfile + pub fn from_linkerfile(flash: &'a embassy_sync::mutex::Mutex) -> Self { + extern "C" { + static __bootloader_state_start: u32; + static __bootloader_state_end: u32; + static __bootloader_dfu_start: u32; + static __bootloader_dfu_end: u32; + } + + let dfu = unsafe { + let start = &__bootloader_dfu_start as *const u32 as u32; + let end = &__bootloader_dfu_end as *const u32 as u32; + trace!("DFU: 0x{:x} - 0x{:x}", start, end); + + Partition::new(flash, start, end - start) + }; + let state = unsafe { + let start = &__bootloader_state_start as *const u32 as u32; + let end = &__bootloader_state_end as *const u32 as u32; + trace!("STATE: 0x{:x} - 0x{:x}", start, end); + + Partition::new(flash, start, end - start) + }; + + Self { dfu, state } + } +} + +impl FirmwareUpdater { + /// Create a firmware updater instance with partition ranges for the update and state partitions. + pub fn new(config: FirmwareUpdaterConfig) -> Self { + Self { + dfu: config.dfu, + state: config.state, + } + } + + // Make sure we are running a booted firmware to avoid reverting to a bad state. + async fn verify_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> { + assert_eq!(aligned.len(), STATE::WRITE_SIZE); + if self.get_state(aligned).await? == State::Boot { + Ok(()) + } else { + Err(FirmwareUpdaterError::BadState) + } + } + + /// Obtain the current state. + /// + /// This is useful to check if the bootloader has just done a swap, in order + /// to do verifications and self-tests of the new image before calling + /// `mark_booted`. + pub async fn get_state(&mut self, aligned: &mut [u8]) -> Result { + self.state.read(0, aligned).await?; + + if !aligned.iter().any(|&b| b != SWAP_MAGIC) { + Ok(State::Swap) + } else { + Ok(State::Boot) + } + } + + /// Verify the DFU given a public key. If there is an error then DO NOT + /// proceed with updating the firmware as it must be signed with a + /// corresponding private key (otherwise it could be malicious firmware). + /// + /// Mark to trigger firmware swap on next boot if verify suceeds. + /// + /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have + /// been generated from a SHA-512 digest of the firmware bytes. + /// + /// If no signature feature is set then this method will always return a + /// signature error. + /// + /// # Safety + /// + /// The `_aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from + /// and written to. + #[cfg(feature = "_verify")] + pub async fn verify_and_mark_updated( + &mut self, + _public_key: &[u8], + _signature: &[u8], + _update_len: u32, + _aligned: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { + assert_eq!(_aligned.len(), STATE::WRITE_SIZE); + assert!(_update_len <= self.dfu.capacity() as u32); + + self.verify_booted(_aligned).await?; + + #[cfg(feature = "ed25519-dalek")] + { + use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier}; + + use crate::digest_adapters::ed25519_dalek::Sha512; + + let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); + + let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; + let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; + + let mut message = [0; 64]; + self.hash::(_update_len, _aligned, &mut message).await?; + + public_key.verify(&message, &signature).map_err(into_signature_error)? + } + #[cfg(feature = "ed25519-salty")] + { + use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH}; + use salty::{PublicKey, Signature}; + + use crate::digest_adapters::salty::Sha512; + + fn into_signature_error(_: E) -> FirmwareUpdaterError { + FirmwareUpdaterError::Signature(signature::Error::default()) + } + + let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?; + let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?; + let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?; + let signature = Signature::try_from(&signature).map_err(into_signature_error)?; + + let mut message = [0; 64]; + self.hash::(_update_len, _aligned, &mut message).await?; + + let r = public_key.verify(&message, &signature); + trace!( + "Verifying with public key {}, signature {} and message {} yields ok: {}", + public_key.to_bytes(), + signature.to_bytes(), + message, + r.is_ok() + ); + r.map_err(into_signature_error)? + } + + self.set_magic(_aligned, SWAP_MAGIC).await + } + + /// Verify the update in DFU with any digest. + pub async fn hash( + &mut self, + update_len: u32, + chunk_buf: &mut [u8], + output: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { + let mut digest = D::new(); + for offset in (0..update_len).step_by(chunk_buf.len()) { + self.dfu.read(offset, chunk_buf).await?; + let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); + digest.update(&chunk_buf[..len]); + } + output.copy_from_slice(digest.finalize().as_slice()); + Ok(()) + } + + /// Mark to trigger firmware swap on next boot. + /// + /// # Safety + /// + /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to. + #[cfg(not(feature = "_verify"))] + pub async fn mark_updated(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> { + assert_eq!(aligned.len(), STATE::WRITE_SIZE); + self.set_magic(aligned, SWAP_MAGIC).await + } + + /// Mark firmware boot successful and stop rollback on reset. + /// + /// # Safety + /// + /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to. + pub async fn mark_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> { + assert_eq!(aligned.len(), STATE::WRITE_SIZE); + self.set_magic(aligned, BOOT_MAGIC).await + } + + async fn set_magic(&mut self, aligned: &mut [u8], magic: u8) -> Result<(), FirmwareUpdaterError> { + self.state.read(0, aligned).await?; + + if aligned.iter().any(|&b| b != magic) { + // Read progress validity + self.state.read(STATE::WRITE_SIZE as u32, aligned).await?; + + if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) { + // The current progress validity marker is invalid + } else { + // Invalidate progress + aligned.fill(!STATE_ERASE_VALUE); + self.state.write(STATE::WRITE_SIZE as u32, aligned).await?; + } + + // Clear magic and progress + self.state.erase(0, self.state.capacity() as u32).await?; + + // Set magic + aligned.fill(magic); + self.state.write(0, aligned).await?; + } + Ok(()) + } + + /// Write data to a flash page. + /// + /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. + /// + /// # Safety + /// + /// Failing to meet alignment and size requirements may result in a panic. + pub async fn write_firmware( + &mut self, + aligned: &mut [u8], + offset: usize, + data: &[u8], + ) -> Result<(), FirmwareUpdaterError> { + assert!(data.len() >= DFU::ERASE_SIZE); + assert_eq!(aligned.len(), STATE::WRITE_SIZE); + + self.verify_booted(aligned).await?; + + self.dfu.erase(offset as u32, (offset + data.len()) as u32).await?; + + self.dfu.write(offset as u32, data).await?; + + Ok(()) + } + + /// Prepare for an incoming DFU update by erasing the entire DFU area and + /// returning its `Partition`. + /// + /// Using this instead of `write_firmware` allows for an optimized API in + /// exchange for added complexity. + /// + /// # Safety + /// + /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to. + pub async fn prepare_update(&mut self, aligned: &mut [u8]) -> Result<&mut DFU, FirmwareUpdaterError> { + assert_eq!(aligned.len(), STATE::WRITE_SIZE); + self.verify_booted(aligned).await?; + + self.dfu.erase(0, self.dfu.capacity() as u32).await?; + + Ok(&mut self.dfu) + } +} + +#[cfg(test)] +mod tests { + use embassy_embedded_hal::flash::partition::Partition; + use embassy_sync::blocking_mutex::raw::NoopRawMutex; + use embassy_sync::mutex::Mutex; + use futures::executor::block_on; + use sha1::{Digest, Sha1}; + + use super::*; + use crate::mem_flash::MemFlash; + + #[test] + fn can_verify_sha1() { + let flash = Mutex::::new(MemFlash::<131072, 4096, 8>::default()); + let state = Partition::new(&flash, 0, 4096); + let dfu = Partition::new(&flash, 65536, 65536); + let mut aligned = [0; 8]; + + let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; + let mut to_write = [0; 4096]; + to_write[..7].copy_from_slice(update.as_slice()); + + let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }); + block_on(updater.write_firmware(&mut aligned, 0, to_write.as_slice())).unwrap(); + let mut chunk_buf = [0; 2]; + let mut hash = [0; 20]; + block_on(updater.hash::(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap(); + + assert_eq!(Sha1::digest(update).as_slice(), hash); + } +} diff --git a/embassy-boot/boot/src/firmware_updater/blocking.rs b/embassy-boot/boot/src/firmware_updater/blocking.rs new file mode 100644 index 000000000..f03f53e4d --- /dev/null +++ b/embassy-boot/boot/src/firmware_updater/blocking.rs @@ -0,0 +1,302 @@ +use digest::Digest; +#[cfg(target_os = "none")] +use embassy_embedded_hal::flash::partition::BlockingPartition; +#[cfg(target_os = "none")] +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embedded_storage::nor_flash::NorFlash; + +use super::FirmwareUpdaterConfig; +use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; + +/// Blocking FirmwareUpdater is an application API for interacting with the BootLoader without the ability to +/// 'mess up' the internal bootloader state +pub struct BlockingFirmwareUpdater { + dfu: DFU, + state: STATE, +} + +#[cfg(target_os = "none")] +impl<'a, FLASH: NorFlash> + FirmwareUpdaterConfig, BlockingPartition<'a, NoopRawMutex, FLASH>> +{ + /// Create a firmware updater config from the flash and address symbols defined in the linkerfile + pub fn from_linkerfile_blocking( + flash: &'a embassy_sync::blocking_mutex::Mutex>, + ) -> Self { + extern "C" { + static __bootloader_state_start: u32; + static __bootloader_state_end: u32; + static __bootloader_dfu_start: u32; + static __bootloader_dfu_end: u32; + } + + let dfu = unsafe { + let start = &__bootloader_dfu_start as *const u32 as u32; + let end = &__bootloader_dfu_end as *const u32 as u32; + trace!("DFU: 0x{:x} - 0x{:x}", start, end); + + BlockingPartition::new(flash, start, end - start) + }; + let state = unsafe { + let start = &__bootloader_state_start as *const u32 as u32; + let end = &__bootloader_state_end as *const u32 as u32; + trace!("STATE: 0x{:x} - 0x{:x}", start, end); + + BlockingPartition::new(flash, start, end - start) + }; + + Self { dfu, state } + } +} + +impl BlockingFirmwareUpdater { + /// Create a firmware updater instance with partition ranges for the update and state partitions. + pub fn new(config: FirmwareUpdaterConfig) -> Self { + Self { + dfu: config.dfu, + state: config.state, + } + } + + // Make sure we are running a booted firmware to avoid reverting to a bad state. + fn verify_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> { + assert_eq!(aligned.len(), STATE::WRITE_SIZE); + if self.get_state(aligned)? == State::Boot { + Ok(()) + } else { + Err(FirmwareUpdaterError::BadState) + } + } + + /// Obtain the current state. + /// + /// This is useful to check if the bootloader has just done a swap, in order + /// to do verifications and self-tests of the new image before calling + /// `mark_booted`. + pub fn get_state(&mut self, aligned: &mut [u8]) -> Result { + self.state.read(0, aligned)?; + + if !aligned.iter().any(|&b| b != SWAP_MAGIC) { + Ok(State::Swap) + } else { + Ok(State::Boot) + } + } + + /// Verify the DFU given a public key. If there is an error then DO NOT + /// proceed with updating the firmware as it must be signed with a + /// corresponding private key (otherwise it could be malicious firmware). + /// + /// Mark to trigger firmware swap on next boot if verify suceeds. + /// + /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have + /// been generated from a SHA-512 digest of the firmware bytes. + /// + /// If no signature feature is set then this method will always return a + /// signature error. + /// + /// # Safety + /// + /// The `_aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from + /// and written to. + #[cfg(feature = "_verify")] + pub fn verify_and_mark_updated( + &mut self, + _public_key: &[u8], + _signature: &[u8], + _update_len: u32, + _aligned: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { + assert_eq!(_aligned.len(), STATE::WRITE_SIZE); + assert!(_update_len <= self.dfu.capacity() as u32); + + self.verify_booted(_aligned)?; + + #[cfg(feature = "ed25519-dalek")] + { + use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier}; + + use crate::digest_adapters::ed25519_dalek::Sha512; + + let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); + + let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; + let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; + + let mut message = [0; 64]; + self.hash::(_update_len, _aligned, &mut message)?; + + public_key.verify(&message, &signature).map_err(into_signature_error)? + } + #[cfg(feature = "ed25519-salty")] + { + use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH}; + use salty::{PublicKey, Signature}; + + use crate::digest_adapters::salty::Sha512; + + fn into_signature_error(_: E) -> FirmwareUpdaterError { + FirmwareUpdaterError::Signature(signature::Error::default()) + } + + let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?; + let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?; + let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?; + let signature = Signature::try_from(&signature).map_err(into_signature_error)?; + + let mut message = [0; 64]; + self.hash::(_update_len, _aligned, &mut message)?; + + let r = public_key.verify(&message, &signature); + trace!( + "Verifying with public key {}, signature {} and message {} yields ok: {}", + public_key.to_bytes(), + signature.to_bytes(), + message, + r.is_ok() + ); + r.map_err(into_signature_error)? + } + + self.set_magic(_aligned, SWAP_MAGIC) + } + + /// Verify the update in DFU with any digest. + pub fn hash( + &mut self, + update_len: u32, + chunk_buf: &mut [u8], + output: &mut [u8], + ) -> Result<(), FirmwareUpdaterError> { + let mut digest = D::new(); + for offset in (0..update_len).step_by(chunk_buf.len()) { + self.dfu.read(offset, chunk_buf)?; + let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); + digest.update(&chunk_buf[..len]); + } + output.copy_from_slice(digest.finalize().as_slice()); + Ok(()) + } + + /// Mark to trigger firmware swap on next boot. + /// + /// # Safety + /// + /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to. + #[cfg(not(feature = "_verify"))] + pub fn mark_updated(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> { + assert_eq!(aligned.len(), STATE::WRITE_SIZE); + self.set_magic(aligned, SWAP_MAGIC) + } + + /// Mark firmware boot successful and stop rollback on reset. + /// + /// # Safety + /// + /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to. + pub fn mark_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> { + assert_eq!(aligned.len(), STATE::WRITE_SIZE); + self.set_magic(aligned, BOOT_MAGIC) + } + + fn set_magic(&mut self, aligned: &mut [u8], magic: u8) -> Result<(), FirmwareUpdaterError> { + self.state.read(0, aligned)?; + + if aligned.iter().any(|&b| b != magic) { + // Read progress validity + self.state.read(STATE::WRITE_SIZE as u32, aligned)?; + + if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) { + // The current progress validity marker is invalid + } else { + // Invalidate progress + aligned.fill(!STATE_ERASE_VALUE); + self.state.write(STATE::WRITE_SIZE as u32, aligned)?; + } + + // Clear magic and progress + self.state.erase(0, self.state.capacity() as u32)?; + + // Set magic + aligned.fill(magic); + self.state.write(0, aligned)?; + } + Ok(()) + } + + /// Write data to a flash page. + /// + /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. + /// + /// # Safety + /// + /// Failing to meet alignment and size requirements may result in a panic. + pub fn write_firmware( + &mut self, + aligned: &mut [u8], + offset: usize, + data: &[u8], + ) -> Result<(), FirmwareUpdaterError> { + assert!(data.len() >= DFU::ERASE_SIZE); + assert_eq!(aligned.len(), STATE::WRITE_SIZE); + self.verify_booted(aligned)?; + + self.dfu.erase(offset as u32, (offset + data.len()) as u32)?; + + self.dfu.write(offset as u32, data)?; + + Ok(()) + } + + /// Prepare for an incoming DFU update by erasing the entire DFU area and + /// returning its `Partition`. + /// + /// Using this instead of `write_firmware` allows for an optimized API in + /// exchange for added complexity. + /// + /// # Safety + /// + /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to. + pub fn prepare_update(&mut self, aligned: &mut [u8]) -> Result<&mut DFU, FirmwareUpdaterError> { + assert_eq!(aligned.len(), STATE::WRITE_SIZE); + self.verify_booted(aligned)?; + self.dfu.erase(0, self.dfu.capacity() as u32)?; + + Ok(&mut self.dfu) + } +} + +#[cfg(test)] +mod tests { + use core::cell::RefCell; + + use embassy_embedded_hal::flash::partition::BlockingPartition; + use embassy_sync::blocking_mutex::raw::NoopRawMutex; + use embassy_sync::blocking_mutex::Mutex; + use sha1::{Digest, Sha1}; + + use super::*; + use crate::mem_flash::MemFlash; + + #[test] + fn can_verify_sha1() { + let flash = Mutex::::new(RefCell::new(MemFlash::<131072, 4096, 8>::default())); + let state = BlockingPartition::new(&flash, 0, 4096); + let dfu = BlockingPartition::new(&flash, 65536, 65536); + + let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; + let mut to_write = [0; 4096]; + to_write[..7].copy_from_slice(update.as_slice()); + + let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }); + let mut aligned = [0; 8]; + updater.write_firmware(&mut aligned, 0, to_write.as_slice()).unwrap(); + let mut chunk_buf = [0; 2]; + let mut hash = [0; 20]; + updater + .hash::(update.len() as u32, &mut chunk_buf, &mut hash) + .unwrap(); + + assert_eq!(Sha1::digest(update).as_slice(), hash); + } +} diff --git a/embassy-boot/boot/src/firmware_updater/mod.rs b/embassy-boot/boot/src/firmware_updater/mod.rs new file mode 100644 index 000000000..55ce8f363 --- /dev/null +++ b/embassy-boot/boot/src/firmware_updater/mod.rs @@ -0,0 +1,51 @@ +#[cfg(feature = "nightly")] +mod asynch; +mod blocking; + +#[cfg(feature = "nightly")] +pub use asynch::FirmwareUpdater; +pub use blocking::BlockingFirmwareUpdater; +use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind}; + +/// Firmware updater flash configuration holding the two flashes used by the updater +/// +/// If only a single flash is actually used, then that flash should be partitioned into two partitions before use. +/// The easiest way to do this is to use [`FirmwareUpdaterConfig::from_linkerfile`] or [`FirmwareUpdaterConfig::from_linkerfile_blocking`] which will partition +/// the provided flash according to symbols defined in the linkerfile. +pub struct FirmwareUpdaterConfig { + /// The dfu flash partition + pub dfu: DFU, + /// The state flash partition + pub state: STATE, +} + +/// Errors returned by FirmwareUpdater +#[derive(Debug)] +pub enum FirmwareUpdaterError { + /// Error from flash. + Flash(NorFlashErrorKind), + /// Signature errors. + Signature(signature::Error), + /// Bad state. + BadState, +} + +#[cfg(feature = "defmt")] +impl defmt::Format for FirmwareUpdaterError { + fn format(&self, fmt: defmt::Formatter) { + match self { + FirmwareUpdaterError::Flash(_) => defmt::write!(fmt, "FirmwareUpdaterError::Flash(_)"), + FirmwareUpdaterError::Signature(_) => defmt::write!(fmt, "FirmwareUpdaterError::Signature(_)"), + FirmwareUpdaterError::BadState => defmt::write!(fmt, "FirmwareUpdaterError::BadState"), + } + } +} + +impl From for FirmwareUpdaterError +where + E: NorFlashError, +{ + fn from(error: E) -> Self { + FirmwareUpdaterError::Flash(error.kind()) + } +} diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index 51e1056cf..016362b86 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -1,674 +1,76 @@ -#![feature(type_alias_impl_trait)] -#![feature(generic_associated_types)] -#![feature(generic_const_exprs)] -#![allow(incomplete_features)] +#![cfg_attr(feature = "nightly", feature(async_fn_in_trait))] #![no_std] -///! embassy-boot is a bootloader and firmware updater for embedded devices with flash -///! storage implemented using embedded-storage -///! -///! The bootloader works in conjunction with the firmware application, and only has the -///! ability to manage two flash banks with an active and a updatable part. It implements -///! a swap algorithm that is power-failure safe, and allows reverting to the previous -///! version of the firmware, should the application crash and fail to mark itself as booted. -///! -///! This library is intended to be used by platform-specific bootloaders, such as embassy-boot-nrf, -///! which defines the limits and flash type for that particular platform. -///! +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] mod fmt; -use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; -use embedded_storage_async::nor_flash::AsyncNorFlash; +mod boot_loader; +mod digest_adapters; +mod firmware_updater; +#[cfg(test)] +mod mem_flash; +#[cfg(test)] +mod test_flash; -const BOOT_MAGIC: u8 = 0xD0; -const SWAP_MAGIC: u8 = 0xF0; +// The expected value of the flash after an erase +// TODO: Use the value provided by NorFlash when available +pub(crate) const STATE_ERASE_VALUE: u8 = 0xFF; +pub use boot_loader::{BootError, BootLoader, BootLoaderConfig}; +#[cfg(feature = "nightly")] +pub use firmware_updater::FirmwareUpdater; +pub use firmware_updater::{BlockingFirmwareUpdater, FirmwareUpdaterConfig, FirmwareUpdaterError}; -#[derive(Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct Partition { - pub from: usize, - pub to: usize, -} +pub(crate) const BOOT_MAGIC: u8 = 0xD0; +pub(crate) const SWAP_MAGIC: u8 = 0xF0; -impl Partition { - pub const fn new(from: usize, to: usize) -> Self { - Self { from, to } - } - pub const fn len(&self) -> usize { - self.to - self.from - } -} - -#[derive(PartialEq, Debug)] +/// The state of the bootloader after running prepare. +#[derive(PartialEq, Eq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum State { + /// Bootloader is ready to boot the active partition. Boot, + /// Bootloader has swapped the active partition with the dfu partition and will attempt boot. Swap, } -#[derive(PartialEq, Debug)] -pub enum BootError { - Flash(NorFlashErrorKind), - BadMagic, -} - -impl From for BootError -where - E: NorFlashError, -{ - fn from(error: E) -> Self { - BootError::Flash(error.kind()) - } -} - -pub trait FlashConfig { - const BLOCK_SIZE: usize; - const ERASE_VALUE: u8; - type FLASH: NorFlash + ReadNorFlash; - - fn flash(&mut self) -> &mut Self::FLASH; -} - -/// Trait defining the flash handles used for active and DFU partition -pub trait FlashProvider { - type STATE: FlashConfig; - type ACTIVE: FlashConfig; - type DFU: FlashConfig; - - /// Return flash instance used to write/read to/from active partition. - fn active(&mut self) -> &mut Self::ACTIVE; - /// Return flash instance used to write/read to/from dfu partition. - fn dfu(&mut self) -> &mut Self::DFU; - /// Return flash instance used to write/read to/from bootloader state. - fn state(&mut self) -> &mut Self::STATE; -} - -/// BootLoader works with any flash implementing embedded_storage and can also work with -/// different page sizes and flash write sizes. -/// -/// The PAGE_SIZE const parameter must be a multiple of the ACTIVE and DFU page sizes. -pub struct BootLoader { - // Page with current state of bootloader. The state partition has the following format: - // | Range | Description | - // | 0 - WRITE_SIZE | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. | - // | WRITE_SIZE - N | Progress index used while swapping or reverting | - state: Partition, - // Location of the partition which will be booted from - active: Partition, - // Location of the partition which will be swapped in when requested - dfu: Partition, -} - -impl BootLoader { - pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self { - assert_eq!(active.len() % PAGE_SIZE, 0); - assert_eq!(dfu.len() % PAGE_SIZE, 0); - // DFU partition must have an extra page - assert!(dfu.len() - active.len() >= PAGE_SIZE); - Self { active, dfu, state } - } - - pub fn boot_address(&self) -> usize { - self.active.from - } - - /// Perform necessary boot preparations like swapping images. - /// - /// The DFU partition is assumed to be 1 page bigger than the active partition for the swap - /// algorithm to work correctly. - /// - /// SWAPPING - /// - /// Assume a flash size of 3 pages for the active partition, and 4 pages for the DFU partition. - /// The swap index contains the copy progress, as to allow continuation of the copy process on - /// power failure. The index counter is represented within 1 or more pages (depending on total - /// flash size), where a page X is considered swapped if index at location (X + WRITE_SIZE) - /// contains a zero value. This ensures that index updates can be performed atomically and - /// avoid a situation where the wrong index value is set (page write size is "atomic"). - /// - /// +-----------+------------+--------+--------+--------+--------+ - /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | - /// +-----------+------------+--------+--------+--------+--------+ - /// | Active | 0 | 1 | 2 | 3 | - | - /// | DFU | 0 | 3 | 2 | 1 | X | - /// +-----------+-------+--------+--------+--------+--------+ - /// - /// The algorithm starts by copying 'backwards', and after the first step, the layout is - /// as follows: - /// - /// +-----------+------------+--------+--------+--------+--------+ - /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | - /// +-----------+------------+--------+--------+--------+--------+ - /// | Active | 1 | 1 | 2 | 1 | - | - /// | DFU | 1 | 3 | 2 | 1 | 3 | - /// +-----------+------------+--------+--------+--------+--------+ - /// - /// The next iteration performs the same steps - /// - /// +-----------+------------+--------+--------+--------+--------+ - /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | - /// +-----------+------------+--------+--------+--------+--------+ - /// | Active | 2 | 1 | 2 | 1 | - | - /// | DFU | 2 | 3 | 2 | 2 | 3 | - /// +-----------+------------+--------+--------+--------+--------+ - /// - /// And again until we're done - /// - /// +-----------+------------+--------+--------+--------+--------+ - /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | - /// +-----------+------------+--------+--------+--------+--------+ - /// | Active | 3 | 3 | 2 | 1 | - | - /// | DFU | 3 | 3 | 1 | 2 | 3 | - /// +-----------+------------+--------+--------+--------+--------+ - /// - /// REVERTING - /// - /// The reverting algorithm uses the swap index to discover that images were swapped, but that - /// the application failed to mark the boot successful. In this case, the revert algorithm will - /// run. - /// - /// The revert index is located separately from the swap index, to ensure that revert can continue - /// on power failure. - /// - /// The revert algorithm works forwards, by starting copying into the 'unused' DFU page at the start. - /// - /// +-----------+--------------+--------+--------+--------+--------+ - /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | - //*/ - /// +-----------+--------------+--------+--------+--------+--------+ - /// | Active | 3 | 1 | 2 | 1 | - | - /// | DFU | 3 | 3 | 1 | 2 | 3 | - /// +-----------+--------------+--------+--------+--------+--------+ - /// - /// - /// +-----------+--------------+--------+--------+--------+--------+ - /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | - /// +-----------+--------------+--------+--------+--------+--------+ - /// | Active | 3 | 1 | 2 | 1 | - | - /// | DFU | 3 | 3 | 2 | 2 | 3 | - /// +-----------+--------------+--------+--------+--------+--------+ - /// - /// +-----------+--------------+--------+--------+--------+--------+ - /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | - /// +-----------+--------------+--------+--------+--------+--------+ - /// | Active | 3 | 1 | 2 | 3 | - | - /// | DFU | 3 | 3 | 2 | 1 | 3 | - /// +-----------+--------------+--------+--------+--------+--------+ - /// - pub fn prepare_boot(&mut self, p: &mut P) -> Result - where - [(); <

::STATE as FlashConfig>::FLASH::WRITE_SIZE]:, - [(); <

::ACTIVE as FlashConfig>::FLASH::ERASE_SIZE]:, - { - // Ensure we have enough progress pages to store copy progress - assert!( - self.active.len() / PAGE_SIZE - <= (self.state.len() - <

::STATE as FlashConfig>::FLASH::WRITE_SIZE) - / <

::STATE as FlashConfig>::FLASH::WRITE_SIZE - ); - - // Copy contents from partition N to active - let state = self.read_state(p.state())?; - match state { - State::Swap => { - // - // Check if we already swapped. If we're in the swap state, this means we should revert - // since the app has failed to mark boot as successful - // - if !self.is_swapped(p.state())? { - trace!("Swapping"); - self.swap(p)?; - trace!("Swapping done"); - } else { - trace!("Reverting"); - self.revert(p)?; - - // Overwrite magic and reset progress - let fstate = p.state().flash(); - let aligned = Aligned( - [!P::STATE::ERASE_VALUE; <

::STATE as FlashConfig>::FLASH::WRITE_SIZE], - ); - fstate.write(self.state.from as u32, &aligned.0)?; - fstate.erase(self.state.from as u32, self.state.to as u32)?; - let aligned = - Aligned([BOOT_MAGIC; <

::STATE as FlashConfig>::FLASH::WRITE_SIZE]); - fstate.write(self.state.from as u32, &aligned.0)?; - } - } - _ => {} - } - Ok(state) - } - - fn is_swapped(&mut self, p: &mut P) -> Result - where - [(); P::FLASH::WRITE_SIZE]:, - { - let page_count = self.active.len() / P::FLASH::ERASE_SIZE; - let progress = self.current_progress(p)?; - - Ok(progress >= page_count * 2) - } - - fn current_progress(&mut self, p: &mut P) -> Result - where - [(); P::FLASH::WRITE_SIZE]:, - { - let write_size = P::FLASH::WRITE_SIZE; - let max_index = ((self.state.len() - write_size) / write_size) - 1; - let flash = p.flash(); - let mut aligned = Aligned([!P::ERASE_VALUE; P::FLASH::WRITE_SIZE]); - for i in 0..max_index { - flash.read((self.state.from + write_size + i * write_size) as u32, &mut aligned.0)?; - if aligned.0 == [P::ERASE_VALUE; P::FLASH::WRITE_SIZE] { - return Ok(i); - } - } - Ok(max_index) - } - - fn update_progress(&mut self, idx: usize, p: &mut P) -> Result<(), BootError> - where - [(); P::FLASH::WRITE_SIZE]:, - { - let flash = p.flash(); - let write_size = P::FLASH::WRITE_SIZE; - let w = self.state.from + write_size + idx * write_size; - let aligned = Aligned([!P::ERASE_VALUE; P::FLASH::WRITE_SIZE]); - flash.write(w as u32, &aligned.0)?; - Ok(()) - } - - fn active_addr(&self, n: usize) -> usize { - self.active.from + n * PAGE_SIZE - } - - fn dfu_addr(&self, n: usize) -> usize { - self.dfu.from + n * PAGE_SIZE - } - - fn copy_page_once_to_active( - &mut self, - idx: usize, - from_page: usize, - to_page: usize, - p: &mut P, - ) -> Result<(), BootError> - where - [(); <

::STATE as FlashConfig>::FLASH::WRITE_SIZE]:, - { - let mut buf: [u8; PAGE_SIZE] = [0; PAGE_SIZE]; - if self.current_progress(p.state())? <= idx { - let mut offset = from_page; - for chunk in buf.chunks_mut(P::DFU::BLOCK_SIZE) { - p.dfu().flash().read(offset as u32, chunk)?; - offset += chunk.len(); - } - - p.active().flash().erase(to_page as u32, (to_page + PAGE_SIZE) as u32)?; - - let mut offset = to_page; - for chunk in buf.chunks(P::ACTIVE::BLOCK_SIZE) { - p.active().flash().write(offset as u32, &chunk)?; - offset += chunk.len(); - } - self.update_progress(idx, p.state())?; - } - Ok(()) - } - - fn copy_page_once_to_dfu( - &mut self, - idx: usize, - from_page: usize, - to_page: usize, - p: &mut P, - ) -> Result<(), BootError> - where - [(); <

::STATE as FlashConfig>::FLASH::WRITE_SIZE]:, - { - let mut buf: [u8; PAGE_SIZE] = [0; PAGE_SIZE]; - if self.current_progress(p.state())? <= idx { - let mut offset = from_page; - for chunk in buf.chunks_mut(P::ACTIVE::BLOCK_SIZE) { - p.active().flash().read(offset as u32, chunk)?; - offset += chunk.len(); - } - - p.dfu().flash().erase(to_page as u32, (to_page + PAGE_SIZE) as u32)?; - - let mut offset = to_page; - for chunk in buf.chunks(P::DFU::BLOCK_SIZE) { - p.dfu().flash().write(offset as u32, chunk)?; - offset += chunk.len(); - } - self.update_progress(idx, p.state())?; - } - Ok(()) - } - - fn swap(&mut self, p: &mut P) -> Result<(), BootError> - where - [(); <

::STATE as FlashConfig>::FLASH::WRITE_SIZE]:, - { - let page_count = self.active.len() / PAGE_SIZE; - trace!("Page count: {}", page_count); - for page in 0..page_count { - trace!("COPY PAGE {}", page); - // Copy active page to the 'next' DFU page. - let active_page = self.active_addr(page_count - 1 - page); - let dfu_page = self.dfu_addr(page_count - page); - //trace!("Copy active {} to dfu {}", active_page, dfu_page); - self.copy_page_once_to_dfu(page * 2, active_page, dfu_page, p)?; - - // Copy DFU page to the active page - let active_page = self.active_addr(page_count - 1 - page); - let dfu_page = self.dfu_addr(page_count - 1 - page); - //trace!("Copy dfy {} to active {}", dfu_page, active_page); - self.copy_page_once_to_active(page * 2 + 1, dfu_page, active_page, p)?; - } - - Ok(()) - } - - fn revert(&mut self, p: &mut P) -> Result<(), BootError> - where - [(); <

::STATE as FlashConfig>::FLASH::WRITE_SIZE]:, - { - let page_count = self.active.len() / PAGE_SIZE; - for page in 0..page_count { - // Copy the bad active page to the DFU page - let active_page = self.active_addr(page); - let dfu_page = self.dfu_addr(page); - self.copy_page_once_to_dfu(page_count * 2 + page * 2, active_page, dfu_page, p)?; - - // Copy the DFU page back to the active page - let active_page = self.active_addr(page); - let dfu_page = self.dfu_addr(page + 1); - self.copy_page_once_to_active(page_count * 2 + page * 2 + 1, dfu_page, active_page, p)?; - } - - Ok(()) - } - - fn read_state(&mut self, p: &mut P) -> Result - where - [(); P::FLASH::WRITE_SIZE]:, - { - let mut magic: [u8; P::FLASH::WRITE_SIZE] = [0; P::FLASH::WRITE_SIZE]; - let flash = p.flash(); - flash.read(self.state.from as u32, &mut magic)?; - - if magic == [SWAP_MAGIC; P::FLASH::WRITE_SIZE] { - Ok(State::Swap) - } else { - Ok(State::Boot) - } - } -} - -/// Convenience provider that uses a single flash for everything -pub struct SingleFlashProvider<'a, F, const ERASE_VALUE: u8 = 0xFF> -where - F: NorFlash + ReadNorFlash, -{ - config: SingleFlashConfig<'a, F, ERASE_VALUE>, -} - -impl<'a, F, const ERASE_VALUE: u8> SingleFlashProvider<'a, F, ERASE_VALUE> -where - F: NorFlash + ReadNorFlash, -{ - pub fn new(flash: &'a mut F) -> Self { - Self { - config: SingleFlashConfig { flash }, - } - } -} - -pub struct SingleFlashConfig<'a, F, const ERASE_VALUE: u8 = 0xFF> -where - F: NorFlash + ReadNorFlash, -{ - flash: &'a mut F, -} - -impl<'a, F> FlashProvider for SingleFlashProvider<'a, F> -where - F: NorFlash + ReadNorFlash, -{ - type STATE = SingleFlashConfig<'a, F>; - type ACTIVE = SingleFlashConfig<'a, F>; - type DFU = SingleFlashConfig<'a, F>; - - fn active(&mut self) -> &mut Self::STATE { - &mut self.config - } - fn dfu(&mut self) -> &mut Self::ACTIVE { - &mut self.config - } - fn state(&mut self) -> &mut Self::DFU { - &mut self.config - } -} - -impl<'a, F, const ERASE_VALUE: u8> FlashConfig for SingleFlashConfig<'a, F, ERASE_VALUE> -where - F: NorFlash + ReadNorFlash, -{ - const BLOCK_SIZE: usize = F::ERASE_SIZE; - const ERASE_VALUE: u8 = ERASE_VALUE; - type FLASH = F; - fn flash(&mut self) -> &mut F { - self.flash - } -} - -/// Convenience provider that uses a single flash for everything -pub struct MultiFlashProvider<'a, ACTIVE, STATE, DFU> -where - ACTIVE: NorFlash + ReadNorFlash, - STATE: NorFlash + ReadNorFlash, - DFU: NorFlash + ReadNorFlash, -{ - active: SingleFlashConfig<'a, ACTIVE>, - state: SingleFlashConfig<'a, STATE>, - dfu: SingleFlashConfig<'a, DFU>, -} - -impl<'a, ACTIVE, STATE, DFU> MultiFlashProvider<'a, ACTIVE, STATE, DFU> -where - ACTIVE: NorFlash + ReadNorFlash, - STATE: NorFlash + ReadNorFlash, - DFU: NorFlash + ReadNorFlash, -{ - pub fn new(active: &'a mut ACTIVE, state: &'a mut STATE, dfu: &'a mut DFU) -> Self { - Self { - active: SingleFlashConfig { flash: active }, - state: SingleFlashConfig { flash: state }, - dfu: SingleFlashConfig { flash: dfu }, - } - } -} - -impl<'a, ACTIVE, STATE, DFU> FlashProvider for MultiFlashProvider<'a, ACTIVE, STATE, DFU> -where - ACTIVE: NorFlash + ReadNorFlash, - STATE: NorFlash + ReadNorFlash, - DFU: NorFlash + ReadNorFlash, -{ - type STATE = SingleFlashConfig<'a, STATE>; - type ACTIVE = SingleFlashConfig<'a, ACTIVE>; - type DFU = SingleFlashConfig<'a, DFU>; - - fn active(&mut self) -> &mut Self::ACTIVE { - &mut self.active - } - fn dfu(&mut self) -> &mut Self::DFU { - &mut self.dfu - } - fn state(&mut self) -> &mut Self::STATE { - &mut self.state - } -} - -/// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to -/// 'mess up' the internal bootloader state -pub struct FirmwareUpdater { - state: Partition, - dfu: Partition, -} - -// NOTE: Aligned to the largest write size supported by flash +/// Buffer aligned to 32 byte boundary, largest known alignment requirement for embassy-boot. #[repr(align(32))] -pub struct Aligned([u8; N]); +pub struct AlignedBuffer(pub [u8; N]); -impl Default for FirmwareUpdater { - fn default() -> Self { - extern "C" { - static __bootloader_state_start: u32; - static __bootloader_state_end: u32; - static __bootloader_dfu_start: u32; - static __bootloader_dfu_end: u32; - } - - let dfu = unsafe { - Partition::new( - &__bootloader_dfu_start as *const u32 as usize, - &__bootloader_dfu_end as *const u32 as usize, - ) - }; - let state = unsafe { - Partition::new( - &__bootloader_state_start as *const u32 as usize, - &__bootloader_state_end as *const u32 as usize, - ) - }; - - trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to); - trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to); - FirmwareUpdater::new(dfu, state) +impl AsRef<[u8]> for AlignedBuffer { + fn as_ref(&self) -> &[u8] { + &self.0 } } -impl FirmwareUpdater { - pub const fn new(dfu: Partition, state: Partition) -> Self { - Self { dfu, state } - } - - /// Return the length of the DFU area - pub fn firmware_len(&self) -> usize { - self.dfu.len() - } - - /// Instruct bootloader that DFU should commence at next boot. - /// Must be provided with an aligned buffer to use for reading and writing magic; - pub async fn update(&mut self, flash: &mut F) -> Result<(), F::Error> - where - [(); F::WRITE_SIZE]:, - { - let mut aligned = Aligned([0; { F::WRITE_SIZE }]); - self.set_magic(&mut aligned.0, SWAP_MAGIC, flash).await - } - - /// Mark firmware boot successfully - pub async fn mark_booted(&mut self, flash: &mut F) -> Result<(), F::Error> - where - [(); F::WRITE_SIZE]:, - { - let mut aligned = Aligned([0; { F::WRITE_SIZE }]); - self.set_magic(&mut aligned.0, BOOT_MAGIC, flash).await - } - - async fn set_magic( - &mut self, - aligned: &mut [u8], - magic: u8, - flash: &mut F, - ) -> Result<(), F::Error> { - flash.read(self.state.from as u32, aligned).await?; - - if aligned.iter().find(|&&b| b != magic).is_some() { - aligned.fill(0); - - flash.write(self.state.from as u32, aligned).await?; - flash.erase(self.state.from as u32, self.state.to as u32).await?; - - aligned.fill(magic); - flash.write(self.state.from as u32, aligned).await?; - } - Ok(()) - } - - // Write to a region of the DFU page - pub async fn write_firmware( - &mut self, - offset: usize, - data: &[u8], - flash: &mut F, - block_size: usize, - ) -> Result<(), F::Error> { - assert!(data.len() >= F::ERASE_SIZE); - - trace!( - "Writing firmware at offset 0x{:x} len {}", - self.dfu.from + offset, - data.len() - ); - - flash - .erase( - (self.dfu.from + offset) as u32, - (self.dfu.from + offset + data.len()) as u32, - ) - .await?; - - trace!( - "Erased from {} to {}", - self.dfu.from + offset, - self.dfu.from + offset + data.len() - ); - - let mut write_offset = self.dfu.from + offset; - for chunk in data.chunks(block_size) { - trace!("Wrote chunk at {}: {:?}", write_offset, chunk); - flash.write(write_offset as u32, chunk).await?; - write_offset += chunk.len(); - } - /* - trace!("Wrote data, reading back for verification"); - - let mut buf: [u8; 4096] = [0; 4096]; - let mut data_offset = 0; - let mut read_offset = self.dfu.from + offset; - for chunk in buf.chunks_mut(block_size) { - flash.read(read_offset as u32, chunk).await?; - trace!("Read chunk at {}: {:?}", read_offset, chunk); - assert_eq!(&data[data_offset..data_offset + block_size], chunk); - read_offset += chunk.len(); - data_offset += chunk.len(); - } - */ - - Ok(()) +impl AsMut<[u8]> for AlignedBuffer { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0 } } #[cfg(test)] mod tests { - use core::convert::Infallible; - use core::future::Future; + #![allow(unused_imports)] - use embedded_storage::nor_flash::ErrorType; - use embedded_storage_async::nor_flash::AsyncReadNorFlash; + use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; + #[cfg(feature = "nightly")] + use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash; use futures::executor::block_on; use super::*; + use crate::boot_loader::BootLoaderConfig; + use crate::firmware_updater::FirmwareUpdaterConfig; + use crate::mem_flash::MemFlash; + #[cfg(feature = "nightly")] + use crate::test_flash::AsyncTestFlash; + use crate::test_flash::BlockingTestFlash; /* #[test] fn test_bad_magic() { let mut flash = MemFlash([0xff; 131072]); - let mut flash = SingleFlashProvider::new(&mut flash); + let mut flash = SingleFlashConfig::new(&mut flash); let mut bootloader = BootLoader::<4096>::new(ACTIVE, DFU, STATE); @@ -681,281 +83,229 @@ mod tests { #[test] fn test_boot_state() { - const STATE: Partition = Partition::new(0, 4096); - const ACTIVE: Partition = Partition::new(4096, 61440); - const DFU: Partition = Partition::new(61440, 122880); + let flash = BlockingTestFlash::new(BootLoaderConfig { + active: MemFlash::<57344, 4096, 4>::default(), + dfu: MemFlash::<61440, 4096, 4>::default(), + state: MemFlash::<4096, 4096, 4>::default(), + }); - let mut flash = MemFlash::<131072, 4096, 4>([0xff; 131072]); - flash.0[0..4].copy_from_slice(&[BOOT_MAGIC; 4]); - let mut flash = SingleFlashProvider::new(&mut flash); + flash.state().write(0, &[BOOT_MAGIC; 4]).unwrap(); - let mut bootloader: BootLoader<4096> = BootLoader::new(ACTIVE, DFU, STATE); + let mut bootloader = BootLoader::new(BootLoaderConfig { + active: flash.active(), + dfu: flash.dfu(), + state: flash.state(), + }); - assert_eq!(State::Boot, bootloader.prepare_boot(&mut flash).unwrap()); + let mut page = [0; 4096]; + assert_eq!(State::Boot, bootloader.prepare_boot(&mut page).unwrap()); } #[test] + #[cfg(all(feature = "nightly", not(feature = "_verify")))] fn test_swap_state() { - const STATE: Partition = Partition::new(0, 4096); - const ACTIVE: Partition = Partition::new(4096, 61440); - const DFU: Partition = Partition::new(61440, 122880); - let mut flash = MemFlash::<131072, 4096, 4>([0xff; 131072]); + const FIRMWARE_SIZE: usize = 57344; + let flash = AsyncTestFlash::new(BootLoaderConfig { + active: MemFlash::::default(), + dfu: MemFlash::<61440, 4096, 4>::default(), + state: MemFlash::<4096, 4096, 4>::default(), + }); - let original: [u8; ACTIVE.len()] = [rand::random::(); ACTIVE.len()]; - let update: [u8; DFU.len()] = [rand::random::(); DFU.len()]; + const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE]; + const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE]; + let mut aligned = [0; 4]; - for i in ACTIVE.from..ACTIVE.to { - flash.0[i] = original[i - ACTIVE.from]; - } + block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); + block_on(flash.active().write(0, &ORIGINAL)).unwrap(); - let mut bootloader: BootLoader<4096> = BootLoader::new(ACTIVE, DFU, STATE); - let mut updater = FirmwareUpdater::new(DFU, STATE); - let mut offset = 0; - for chunk in update.chunks(4096) { - block_on(updater.write_firmware(offset, &chunk, &mut flash, 4096)).unwrap(); - offset += chunk.len(); - } - block_on(updater.update(&mut flash)).unwrap(); + let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { + dfu: flash.dfu(), + state: flash.state(), + }); + block_on(updater.write_firmware(&mut aligned, 0, &UPDATE)).unwrap(); + block_on(updater.mark_updated(&mut aligned)).unwrap(); - assert_eq!( - State::Swap, - bootloader - .prepare_boot(&mut SingleFlashProvider::new(&mut flash)) - .unwrap() - ); + // Writing after marking updated is not allowed until marked as booted. + let res: Result<(), FirmwareUpdaterError> = block_on(updater.write_firmware(&mut aligned, 0, &UPDATE)); + assert!(matches!(res, Err::<(), _>(FirmwareUpdaterError::BadState))); - for i in ACTIVE.from..ACTIVE.to { - assert_eq!(flash.0[i], update[i - ACTIVE.from], "Index {}", i); - } + let flash = flash.into_blocking(); + let mut bootloader = BootLoader::new(BootLoaderConfig { + active: flash.active(), + dfu: flash.dfu(), + state: flash.state(), + }); + let mut page = [0; 1024]; + assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); + + let mut read_buf = [0; FIRMWARE_SIZE]; + flash.active().read(0, &mut read_buf).unwrap(); + assert_eq!(UPDATE, read_buf); // First DFU page is untouched - for i in DFU.from + 4096..DFU.to { - assert_eq!(flash.0[i], original[i - DFU.from - 4096], "Index {}", i); - } + flash.dfu().read(4096, &mut read_buf).unwrap(); + assert_eq!(ORIGINAL, read_buf); // Running again should cause a revert - assert_eq!( - State::Swap, - bootloader - .prepare_boot(&mut SingleFlashProvider::new(&mut flash)) - .unwrap() - ); + assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); - for i in ACTIVE.from..ACTIVE.to { - assert_eq!(flash.0[i], original[i - ACTIVE.from], "Index {}", i); - } - - // Last page is untouched - for i in DFU.from..DFU.to - 4096 { - assert_eq!(flash.0[i], update[i - DFU.from], "Index {}", i); - } + let mut read_buf = [0; FIRMWARE_SIZE]; + flash.active().read(0, &mut read_buf).unwrap(); + assert_eq!(ORIGINAL, read_buf); + // Last DFU page is untouched + flash.dfu().read(0, &mut read_buf).unwrap(); + assert_eq!(UPDATE, read_buf); // Mark as booted - block_on(updater.mark_booted(&mut flash)).unwrap(); - assert_eq!( - State::Boot, - bootloader - .prepare_boot(&mut SingleFlashProvider::new(&mut flash)) - .unwrap() - ); + let flash = flash.into_async(); + let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { + dfu: flash.dfu(), + state: flash.state(), + }); + block_on(updater.mark_booted(&mut aligned)).unwrap(); + + let flash = flash.into_blocking(); + let mut bootloader = BootLoader::new(BootLoaderConfig { + active: flash.active(), + dfu: flash.dfu(), + state: flash.state(), + }); + assert_eq!(State::Boot, bootloader.prepare_boot(&mut page).unwrap()); } #[test] - fn test_separate_flash_active_page_biggest() { - const STATE: Partition = Partition::new(2048, 4096); - const ACTIVE: Partition = Partition::new(4096, 16384); - const DFU: Partition = Partition::new(0, 16384); + #[cfg(all(feature = "nightly", not(feature = "_verify")))] + fn test_swap_state_active_page_biggest() { + const FIRMWARE_SIZE: usize = 12288; + let flash = AsyncTestFlash::new(BootLoaderConfig { + active: MemFlash::<12288, 4096, 8>::random(), + dfu: MemFlash::<16384, 2048, 8>::random(), + state: MemFlash::<2048, 128, 4>::random(), + }); - let mut active = MemFlash::<16384, 4096, 8>([0xff; 16384]); - let mut dfu = MemFlash::<16384, 2048, 8>([0xff; 16384]); - let mut state = MemFlash::<4096, 128, 4>([0xff; 4096]); + const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE]; + const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE]; + let mut aligned = [0; 4]; - let original: [u8; ACTIVE.len()] = [rand::random::(); ACTIVE.len()]; - let update: [u8; DFU.len()] = [rand::random::(); DFU.len()]; + block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); + block_on(flash.active().write(0, &ORIGINAL)).unwrap(); - for i in ACTIVE.from..ACTIVE.to { - active.0[i] = original[i - ACTIVE.from]; - } + let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { + dfu: flash.dfu(), + state: flash.state(), + }); + block_on(updater.write_firmware(&mut aligned, 0, &UPDATE)).unwrap(); + block_on(updater.mark_updated(&mut aligned)).unwrap(); - let mut updater = FirmwareUpdater::new(DFU, STATE); + let flash = flash.into_blocking(); + let mut bootloader = BootLoader::new(BootLoaderConfig { + active: flash.active(), + dfu: flash.dfu(), + state: flash.state(), + }); - let mut offset = 0; - for chunk in update.chunks(2048) { - block_on(updater.write_firmware(offset, &chunk, &mut dfu, chunk.len())).unwrap(); - offset += chunk.len(); - } - block_on(updater.update(&mut state)).unwrap(); - - let mut bootloader: BootLoader<4096> = BootLoader::new(ACTIVE, DFU, STATE); - assert_eq!( - State::Swap, - bootloader - .prepare_boot(&mut MultiFlashProvider::new(&mut active, &mut state, &mut dfu,)) - .unwrap() - ); - - for i in ACTIVE.from..ACTIVE.to { - assert_eq!(active.0[i], update[i - ACTIVE.from], "Index {}", i); - } + let mut page = [0; 4096]; + assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); + let mut read_buf = [0; FIRMWARE_SIZE]; + flash.active().read(0, &mut read_buf).unwrap(); + assert_eq!(UPDATE, read_buf); // First DFU page is untouched - for i in DFU.from + 4096..DFU.to { - assert_eq!(dfu.0[i], original[i - DFU.from - 4096], "Index {}", i); - } + flash.dfu().read(4096, &mut read_buf).unwrap(); + assert_eq!(ORIGINAL, read_buf); } #[test] - fn test_separate_flash_dfu_page_biggest() { - const STATE: Partition = Partition::new(2048, 4096); - const ACTIVE: Partition = Partition::new(4096, 16384); - const DFU: Partition = Partition::new(0, 16384); + #[cfg(all(feature = "nightly", not(feature = "_verify")))] + fn test_swap_state_dfu_page_biggest() { + const FIRMWARE_SIZE: usize = 12288; + let flash = AsyncTestFlash::new(BootLoaderConfig { + active: MemFlash::::random(), + dfu: MemFlash::<16384, 4096, 8>::random(), + state: MemFlash::<2048, 128, 4>::random(), + }); - let mut active = MemFlash::<16384, 2048, 4>([0xff; 16384]); - let mut dfu = MemFlash::<16384, 4096, 8>([0xff; 16384]); - let mut state = MemFlash::<4096, 128, 4>([0xff; 4096]); + const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE]; + const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE]; + let mut aligned = [0; 4]; - let original: [u8; ACTIVE.len()] = [rand::random::(); ACTIVE.len()]; - let update: [u8; DFU.len()] = [rand::random::(); DFU.len()]; + block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); + block_on(flash.active().write(0, &ORIGINAL)).unwrap(); - for i in ACTIVE.from..ACTIVE.to { - active.0[i] = original[i - ACTIVE.from]; - } + let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { + dfu: flash.dfu(), + state: flash.state(), + }); + block_on(updater.write_firmware(&mut aligned, 0, &UPDATE)).unwrap(); + block_on(updater.mark_updated(&mut aligned)).unwrap(); - let mut updater = FirmwareUpdater::new(DFU, STATE); - - let mut offset = 0; - for chunk in update.chunks(4096) { - block_on(updater.write_firmware(offset, &chunk, &mut dfu, chunk.len())).unwrap(); - offset += chunk.len(); - } - block_on(updater.update(&mut state)).unwrap(); - - let mut bootloader: BootLoader<4096> = BootLoader::new(ACTIVE, DFU, STATE); - assert_eq!( - State::Swap, - bootloader - .prepare_boot(&mut MultiFlashProvider::new(&mut active, &mut state, &mut dfu,)) - .unwrap() - ); - - for i in ACTIVE.from..ACTIVE.to { - assert_eq!(active.0[i], update[i - ACTIVE.from], "Index {}", i); - } + let flash = flash.into_blocking(); + let mut bootloader = BootLoader::new(BootLoaderConfig { + active: flash.active(), + dfu: flash.dfu(), + state: flash.state(), + }); + let mut page = [0; 4096]; + assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); + let mut read_buf = [0; FIRMWARE_SIZE]; + flash.active().read(0, &mut read_buf).unwrap(); + assert_eq!(UPDATE, read_buf); // First DFU page is untouched - for i in DFU.from + 4096..DFU.to { - assert_eq!(dfu.0[i], original[i - DFU.from - 4096], "Index {}", i); - } + flash.dfu().read(4096, &mut read_buf).unwrap(); + assert_eq!(ORIGINAL, read_buf); } - struct MemFlash([u8; SIZE]); + #[test] + #[cfg(all(feature = "nightly", feature = "_verify"))] + fn test_verify() { + // The following key setup is based on: + // https://docs.rs/ed25519-dalek/latest/ed25519_dalek/#example - impl NorFlash - for MemFlash - { - const WRITE_SIZE: usize = WRITE_SIZE; - const ERASE_SIZE: usize = ERASE_SIZE; - fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { - let from = from as usize; - let to = to as usize; - assert!(from % ERASE_SIZE == 0); - assert!(to % ERASE_SIZE == 0, "To: {}, erase size: {}", to, ERASE_SIZE); - for i in from..to { - self.0[i] = 0xFF; - } - Ok(()) - } + use ed25519_dalek::Keypair; + use rand::rngs::OsRng; - fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> { - assert!(data.len() % WRITE_SIZE == 0); - assert!(offset as usize % WRITE_SIZE == 0); - assert!(offset as usize + data.len() <= SIZE); + let mut csprng = OsRng {}; + let keypair: Keypair = Keypair::generate(&mut csprng); - self.0[offset as usize..offset as usize + data.len()].copy_from_slice(data); + use ed25519_dalek::{Digest, Sha512, Signature, Signer}; + let firmware: &[u8] = b"This are bytes that would otherwise be firmware bytes for DFU."; + let mut digest = Sha512::new(); + digest.update(&firmware); + let message = digest.finalize(); + let signature: Signature = keypair.sign(&message); - Ok(()) - } - } + use ed25519_dalek::PublicKey; + let public_key: PublicKey = keypair.public; - impl ErrorType - for MemFlash - { - type Error = Infallible; - } + // Setup flash + let flash = BlockingTestFlash::new(BootLoaderConfig { + active: MemFlash::<0, 0, 0>::default(), + dfu: MemFlash::<4096, 4096, 4>::default(), + state: MemFlash::<4096, 4096, 4>::default(), + }); - impl ReadNorFlash - for MemFlash - { - const READ_SIZE: usize = 4; + let firmware_len = firmware.len(); - fn read(&mut self, offset: u32, buf: &mut [u8]) -> Result<(), Self::Error> { - let len = buf.len(); - buf[..].copy_from_slice(&self.0[offset as usize..offset as usize + len]); - Ok(()) - } + let mut write_buf = [0; 4096]; + write_buf[0..firmware_len].copy_from_slice(firmware); + flash.dfu().write(0, &write_buf).unwrap(); - fn capacity(&self) -> usize { - SIZE - } - } + // On with the test + let flash = flash.into_async(); + let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { + dfu: flash.dfu(), + state: flash.state(), + }); - impl AsyncReadNorFlash - for MemFlash - { - const READ_SIZE: usize = 4; + let mut aligned = [0; 4]; - type ReadFuture<'a> = impl Future> + 'a; - fn read<'a>(&'a mut self, offset: u32, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - async move { - let len = buf.len(); - buf[..].copy_from_slice(&self.0[offset as usize..offset as usize + len]); - Ok(()) - } - } - - fn capacity(&self) -> usize { - SIZE - } - } - - impl AsyncNorFlash - for MemFlash - { - const WRITE_SIZE: usize = WRITE_SIZE; - const ERASE_SIZE: usize = ERASE_SIZE; - - type EraseFuture<'a> = impl Future> + 'a; - fn erase<'a>(&'a mut self, from: u32, to: u32) -> Self::EraseFuture<'a> { - async move { - let from = from as usize; - let to = to as usize; - assert!(from % ERASE_SIZE == 0); - assert!(to % ERASE_SIZE == 0); - for i in from..to { - self.0[i] = 0xFF; - } - Ok(()) - } - } - - type WriteFuture<'a> = impl Future> + 'a; - fn write<'a>(&'a mut self, offset: u32, data: &'a [u8]) -> Self::WriteFuture<'a> { - info!("Writing {} bytes to 0x{:x}", data.len(), offset); - async move { - assert!(data.len() % WRITE_SIZE == 0); - assert!(offset as usize % WRITE_SIZE == 0); - assert!( - offset as usize + data.len() <= SIZE, - "OFFSET: {}, LEN: {}, FLASH SIZE: {}", - offset, - data.len(), - SIZE - ); - - self.0[offset as usize..offset as usize + data.len()].copy_from_slice(data); - - Ok(()) - } - } + assert!(block_on(updater.verify_and_mark_updated( + &public_key.to_bytes(), + &signature.to_bytes(), + firmware_len as u32, + &mut aligned, + )) + .is_ok()); } } diff --git a/embassy-boot/boot/src/mem_flash.rs b/embassy-boot/boot/src/mem_flash.rs new file mode 100644 index 000000000..2728e9720 --- /dev/null +++ b/embassy-boot/boot/src/mem_flash.rs @@ -0,0 +1,173 @@ +#![allow(unused)] + +use core::ops::{Bound, Range, RangeBounds}; + +use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; +#[cfg(feature = "nightly")] +use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; + +pub struct MemFlash { + pub mem: [u8; SIZE], + pub pending_write_successes: Option, +} + +#[derive(Debug)] +pub struct MemFlashError; + +impl MemFlash { + pub const fn new(fill: u8) -> Self { + Self { + mem: [fill; SIZE], + pending_write_successes: None, + } + } + + #[cfg(test)] + pub fn random() -> Self { + let mut mem = [0; SIZE]; + for byte in mem.iter_mut() { + *byte = rand::random::(); + } + Self { + mem, + pending_write_successes: None, + } + } + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), MemFlashError> { + let len = bytes.len(); + bytes.copy_from_slice(&self.mem[offset as usize..offset as usize + len]); + Ok(()) + } + + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), MemFlashError> { + let offset = offset as usize; + assert!(bytes.len() % WRITE_SIZE == 0); + assert!(offset % WRITE_SIZE == 0); + assert!(offset + bytes.len() <= SIZE); + + if let Some(pending_successes) = self.pending_write_successes { + if pending_successes > 0 { + self.pending_write_successes = Some(pending_successes - 1); + } else { + return Err(MemFlashError); + } + } + + for ((offset, mem_byte), new_byte) in self + .mem + .iter_mut() + .enumerate() + .skip(offset) + .take(bytes.len()) + .zip(bytes) + { + assert_eq!(0xFF, *mem_byte, "Offset {} is not erased", offset); + *mem_byte = *new_byte; + } + + Ok(()) + } + + fn erase(&mut self, from: u32, to: u32) -> Result<(), MemFlashError> { + let from = from as usize; + let to = to as usize; + assert!(from % ERASE_SIZE == 0); + assert!(to % ERASE_SIZE == 0, "To: {}, erase size: {}", to, ERASE_SIZE); + for i in from..to { + self.mem[i] = 0xFF; + } + Ok(()) + } + + pub fn program(&mut self, offset: u32, bytes: &[u8]) -> Result<(), MemFlashError> { + let offset = offset as usize; + assert!(bytes.len() % WRITE_SIZE == 0); + assert!(offset % WRITE_SIZE == 0); + assert!(offset + bytes.len() <= SIZE); + + self.mem[offset..offset + bytes.len()].copy_from_slice(bytes); + + Ok(()) + } +} + +impl Default + for MemFlash +{ + fn default() -> Self { + Self::new(0xFF) + } +} + +impl ErrorType + for MemFlash +{ + type Error = MemFlashError; +} + +impl NorFlashError for MemFlashError { + fn kind(&self) -> NorFlashErrorKind { + NorFlashErrorKind::Other + } +} + +impl ReadNorFlash + for MemFlash +{ + const READ_SIZE: usize = 1; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.read(offset, bytes) + } + + fn capacity(&self) -> usize { + SIZE + } +} + +impl NorFlash + for MemFlash +{ + const WRITE_SIZE: usize = WRITE_SIZE; + const ERASE_SIZE: usize = ERASE_SIZE; + + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.write(offset, bytes) + } + + fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.erase(from, to) + } +} + +#[cfg(feature = "nightly")] +impl AsyncReadNorFlash + for MemFlash +{ + const READ_SIZE: usize = 1; + + async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.read(offset, bytes) + } + + fn capacity(&self) -> usize { + SIZE + } +} + +#[cfg(feature = "nightly")] +impl AsyncNorFlash + for MemFlash +{ + const WRITE_SIZE: usize = WRITE_SIZE; + const ERASE_SIZE: usize = ERASE_SIZE; + + async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.write(offset, bytes) + } + + async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.erase(from, to) + } +} diff --git a/embassy-boot/boot/src/test_flash/asynch.rs b/embassy-boot/boot/src/test_flash/asynch.rs new file mode 100644 index 000000000..3ac9e71ab --- /dev/null +++ b/embassy-boot/boot/src/test_flash/asynch.rs @@ -0,0 +1,64 @@ +use embassy_embedded_hal::flash::partition::Partition; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::mutex::Mutex; +use embedded_storage_async::nor_flash::NorFlash; + +use crate::BootLoaderConfig; + +pub struct AsyncTestFlash +where + ACTIVE: NorFlash, + DFU: NorFlash, + STATE: NorFlash, +{ + active: Mutex, + dfu: Mutex, + state: Mutex, +} + +impl AsyncTestFlash +where + ACTIVE: NorFlash, + DFU: NorFlash, + STATE: NorFlash, +{ + pub fn new(config: BootLoaderConfig) -> Self { + Self { + active: Mutex::new(config.active), + dfu: Mutex::new(config.dfu), + state: Mutex::new(config.state), + } + } + + pub fn active(&self) -> Partition { + Self::create_partition(&self.active) + } + + pub fn dfu(&self) -> Partition { + Self::create_partition(&self.dfu) + } + + pub fn state(&self) -> Partition { + Self::create_partition(&self.state) + } + + fn create_partition(mutex: &Mutex) -> Partition { + Partition::new(mutex, 0, mutex.try_lock().unwrap().capacity() as u32) + } +} + +impl AsyncTestFlash +where + ACTIVE: NorFlash + embedded_storage::nor_flash::NorFlash, + DFU: NorFlash + embedded_storage::nor_flash::NorFlash, + STATE: NorFlash + embedded_storage::nor_flash::NorFlash, +{ + pub fn into_blocking(self) -> super::BlockingTestFlash { + let config = BootLoaderConfig { + active: self.active.into_inner(), + dfu: self.dfu.into_inner(), + state: self.state.into_inner(), + }; + super::BlockingTestFlash::new(config) + } +} diff --git a/embassy-boot/boot/src/test_flash/blocking.rs b/embassy-boot/boot/src/test_flash/blocking.rs new file mode 100644 index 000000000..ba33c9208 --- /dev/null +++ b/embassy-boot/boot/src/test_flash/blocking.rs @@ -0,0 +1,69 @@ +use core::cell::RefCell; + +use embassy_embedded_hal::flash::partition::BlockingPartition; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embedded_storage::nor_flash::NorFlash; + +use crate::BootLoaderConfig; + +pub struct BlockingTestFlash +where + ACTIVE: NorFlash, + DFU: NorFlash, + STATE: NorFlash, +{ + active: Mutex>, + dfu: Mutex>, + state: Mutex>, +} + +impl BlockingTestFlash +where + ACTIVE: NorFlash, + DFU: NorFlash, + STATE: NorFlash, +{ + pub fn new(config: BootLoaderConfig) -> Self { + Self { + active: Mutex::new(RefCell::new(config.active)), + dfu: Mutex::new(RefCell::new(config.dfu)), + state: Mutex::new(RefCell::new(config.state)), + } + } + + pub fn active(&self) -> BlockingPartition { + Self::create_partition(&self.active) + } + + pub fn dfu(&self) -> BlockingPartition { + Self::create_partition(&self.dfu) + } + + pub fn state(&self) -> BlockingPartition { + Self::create_partition(&self.state) + } + + pub fn create_partition( + mutex: &Mutex>, + ) -> BlockingPartition { + BlockingPartition::new(mutex, 0, mutex.lock(|f| f.borrow().capacity()) as u32) + } +} + +#[cfg(feature = "nightly")] +impl BlockingTestFlash +where + ACTIVE: NorFlash + embedded_storage_async::nor_flash::NorFlash, + DFU: NorFlash + embedded_storage_async::nor_flash::NorFlash, + STATE: NorFlash + embedded_storage_async::nor_flash::NorFlash, +{ + pub fn into_async(self) -> super::AsyncTestFlash { + let config = BootLoaderConfig { + active: self.active.into_inner().into_inner(), + dfu: self.dfu.into_inner().into_inner(), + state: self.state.into_inner().into_inner(), + }; + super::AsyncTestFlash::new(config) + } +} diff --git a/embassy-boot/boot/src/test_flash/mod.rs b/embassy-boot/boot/src/test_flash/mod.rs new file mode 100644 index 000000000..a0672322e --- /dev/null +++ b/embassy-boot/boot/src/test_flash/mod.rs @@ -0,0 +1,7 @@ +#[cfg(feature = "nightly")] +mod asynch; +mod blocking; + +#[cfg(feature = "nightly")] +pub(crate) use asynch::AsyncTestFlash; +pub(crate) use blocking::BlockingTestFlash; diff --git a/embassy-boot/nrf/Cargo.toml b/embassy-boot/nrf/Cargo.toml index 234393e7c..8186a9958 100644 --- a/embassy-boot/nrf/Cargo.toml +++ b/embassy-boot/nrf/Cargo.toml @@ -3,6 +3,7 @@ edition = "2021" name = "embassy-boot-nrf" version = "0.1.0" description = "Bootloader lib for nRF chips" +license = "MIT OR Apache-2.0" [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-nrf-v$VERSION/embassy-boot/nrf/src/" @@ -16,12 +17,12 @@ target = "thumbv7em-none-eabi" defmt = { version = "0.3", optional = true } embassy-sync = { path = "../../embassy-sync" } -embassy-nrf = { path = "../../embassy-nrf", default-features = false, features = ["nightly"] } +embassy-nrf = { path = "../../embassy-nrf" } embassy-boot = { path = "../boot", default-features = false } cortex-m = { version = "0.7.6" } cortex-m-rt = { version = "0.7" } embedded-storage = "0.3.0" -embedded-storage-async = "0.3.0" +embedded-storage-async = { version = "0.4.0", optional = true } cfg-if = "1.0.0" nrf-softdevice-mbr = { version = "0.1.0", git = "https://github.com/embassy-rs/nrf-softdevice.git", branch = "master", optional = true } @@ -35,3 +36,8 @@ defmt = [ softdevice = [ "nrf-softdevice-mbr", ] +nightly = [ + "dep:embedded-storage-async", + "embassy-boot/nightly", + "embassy-nrf/nightly" +] diff --git a/embassy-boot/nrf/README.md b/embassy-boot/nrf/README.md new file mode 100644 index 000000000..fe581823d --- /dev/null +++ b/embassy-boot/nrf/README.md @@ -0,0 +1,26 @@ +# embassy-boot-nrf + +An [Embassy](https://embassy.dev) project. + +An adaptation of `embassy-boot` for nRF. + +## Features + +* Load applications with or without the softdevice. +* Configure bootloader partitions based on linker script. +* Using watchdog timer to detect application failure. + + +## Minimum supported Rust version (MSRV) + +`embassy-boot-nrf` is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release. + +## License + +This work is licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. diff --git a/embassy-boot/nrf/src/lib.rs b/embassy-boot/nrf/src/lib.rs index 2d6b837cb..bb702073c 100644 --- a/embassy-boot/nrf/src/lib.rs +++ b/embassy-boot/nrf/src/lib.rs @@ -1,88 +1,63 @@ #![no_std] -#![feature(generic_associated_types)] -#![feature(type_alias_impl_trait)] -#![allow(incomplete_features)] -#![feature(generic_const_exprs)] - +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] mod fmt; -pub use embassy_boot::{FirmwareUpdater, FlashConfig, FlashProvider, Partition, SingleFlashProvider}; +#[cfg(feature = "nightly")] +pub use embassy_boot::FirmwareUpdater; +pub use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig}; use embassy_nrf::nvmc::{Nvmc, PAGE_SIZE}; use embassy_nrf::peripherals::WDT; use embassy_nrf::wdt; use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; -pub struct BootLoader { - boot: embassy_boot::BootLoader, +/// A bootloader for nRF devices. +pub struct BootLoader { + boot: embassy_boot::BootLoader, + aligned_buf: AlignedBuffer, } -impl BootLoader { - /// Create a new bootloader instance using parameters from linker script - pub fn default() -> Self { - extern "C" { - static __bootloader_state_start: u32; - static __bootloader_state_end: u32; - static __bootloader_active_start: u32; - static __bootloader_active_end: u32; - static __bootloader_dfu_start: u32; - static __bootloader_dfu_end: u32; - } - - let active = unsafe { - Partition::new( - &__bootloader_active_start as *const u32 as usize, - &__bootloader_active_end as *const u32 as usize, - ) - }; - let dfu = unsafe { - Partition::new( - &__bootloader_dfu_start as *const u32 as usize, - &__bootloader_dfu_end as *const u32 as usize, - ) - }; - let state = unsafe { - Partition::new( - &__bootloader_state_start as *const u32 as usize, - &__bootloader_state_end as *const u32 as usize, - ) - }; - - trace!("ACTIVE: 0x{:x} - 0x{:x}", active.from, active.to); - trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to); - trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to); - - Self::new(active, dfu, state) - } - +impl + BootLoader +{ /// Create a new bootloader instance using the supplied partitions for active, dfu and state. - pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self { + pub fn new(config: BootLoaderConfig) -> Self { Self { - boot: embassy_boot::BootLoader::new(active, dfu, state), + boot: embassy_boot::BootLoader::new(config), + aligned_buf: AlignedBuffer([0; BUFFER_SIZE]), } } - /// Boots the application without softdevice mechanisms - pub fn prepare(&mut self, flash: &mut F) -> usize - where - [(); <::STATE as FlashConfig>::FLASH::WRITE_SIZE]:, - [(); <::ACTIVE as FlashConfig>::FLASH::ERASE_SIZE]:, - { - match self.boot.prepare_boot(flash) { - Ok(_) => self.boot.boot_address(), - Err(_) => panic!("boot prepare error!"), - } + /// Inspect the bootloader state and perform actions required before booting, such as swapping + /// firmware. + pub fn prepare(&mut self) { + self.boot + .prepare_boot(&mut self.aligned_buf.0) + .expect("Boot prepare error"); } + /// Boots the application without softdevice mechanisms. + /// + /// # Safety + /// + /// This modifies the stack pointer and reset vector and will run code placed in the active partition. #[cfg(not(feature = "softdevice"))] - pub unsafe fn load(&mut self, start: usize) -> ! { + pub unsafe fn load(self, start: u32) -> ! { + core::mem::drop(self.boot); + let mut p = cortex_m::Peripherals::steal(); p.SCB.invalidate_icache(); - p.SCB.vtor.write(start as u32); + p.SCB.vtor.write(start); cortex_m::asm::bootload(start as *const u32) } + /// Boots the application assuming softdevice is present. + /// + /// # Safety + /// + /// This modifies the stack pointer and reset vector and will run code placed in the active partition. #[cfg(feature = "softdevice")] - pub unsafe fn load(&mut self, _app: usize) -> ! { + pub unsafe fn load(&mut self, _app: u32) -> ! { use nrf_softdevice_mbr as mbr; const NRF_SUCCESS: u32 = 0; @@ -137,11 +112,7 @@ pub struct WatchdogFlash<'d> { impl<'d> WatchdogFlash<'d> { /// Start a new watchdog with a given flash and WDT peripheral and a timeout - pub fn start(flash: Nvmc<'d>, wdt: WDT, timeout: u32) -> Self { - let mut config = wdt::Config::default(); - config.timeout_ticks = 32768 * timeout; // timeout seconds - config.run_during_sleep = true; - config.run_during_debug_halt = false; + pub fn start(flash: Nvmc<'d>, wdt: WDT, config: wdt::Config) -> Self { let (_wdt, [wdt]) = match wdt::Watchdog::try_new(wdt, config) { Ok(x) => x, Err(_) => { diff --git a/embassy-boot/rp/Cargo.toml b/embassy-boot/rp/Cargo.toml new file mode 100644 index 000000000..5147392ce --- /dev/null +++ b/embassy-boot/rp/Cargo.toml @@ -0,0 +1,79 @@ +[package] +edition = "2021" +name = "embassy-boot-rp" +version = "0.1.0" +description = "Bootloader lib for RP2040 chips" +license = "MIT OR Apache-2.0" + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-rp-v$VERSION/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot/rp/src/" +target = "thumbv6m-none-eabi" + +[lib] + +[dependencies] +defmt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } +log = { version = "0.4", optional = true } + +embassy-sync = { path = "../../embassy-sync" } +embassy-rp = { path = "../../embassy-rp", default-features = false } +embassy-boot = { path = "../boot", default-features = false } +embassy-time = { path = "../../embassy-time" } + +cortex-m = { version = "0.7.6" } +cortex-m-rt = { version = "0.7" } +embedded-storage = "0.3.0" +embedded-storage-async = { version = "0.4.0", optional = true } +cfg-if = "1.0.0" + +[features] +defmt = [ + "dep:defmt", + "embassy-boot/defmt", + "embassy-rp/defmt", +] +log = [ + "dep:log", + "embassy-boot/log", + "embassy-rp/log", +] +debug = ["defmt-rtt"] +nightly = [ + "dep:embedded-storage-async", + "embassy-boot/nightly", + "embassy-rp/nightly", + "embassy-time/nightly" +] + +[profile.dev] +debug = 2 +debug-assertions = true +incremental = false +opt-level = 'z' +overflow-checks = true + +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false +incremental = false +lto = 'fat' +opt-level = 'z' +overflow-checks = false + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false + +[profile.release.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false diff --git a/embassy-boot/rp/README.md b/embassy-boot/rp/README.md new file mode 100644 index 000000000..315d655e3 --- /dev/null +++ b/embassy-boot/rp/README.md @@ -0,0 +1,26 @@ +# embassy-boot-rp + +An [Embassy](https://embassy.dev) project. + +An adaptation of `embassy-boot` for RP2040. + +NOTE: The applications using this bootloader should not link with the `link-rp.x` linker script. + +## Features + +* Configure bootloader partitions based on linker script. +* Load applications from active partition. + +## Minimum supported Rust version (MSRV) + +`embassy-boot-rp` is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release. + +## License + +This work is licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. diff --git a/embassy-boot/rp/build.rs b/embassy-boot/rp/build.rs new file mode 100644 index 000000000..2cbc7ef5e --- /dev/null +++ b/embassy-boot/rp/build.rs @@ -0,0 +1,8 @@ +use std::env; + +fn main() { + let target = env::var("TARGET").unwrap(); + if target.starts_with("thumbv6m-") { + println!("cargo:rustc-cfg=armv6m"); + } +} diff --git a/embassy-usb-hid/src/fmt.rs b/embassy-boot/rp/src/fmt.rs similarity index 100% rename from embassy-usb-hid/src/fmt.rs rename to embassy-boot/rp/src/fmt.rs diff --git a/embassy-boot/rp/src/lib.rs b/embassy-boot/rp/src/lib.rs new file mode 100644 index 000000000..25329f9e9 --- /dev/null +++ b/embassy-boot/rp/src/lib.rs @@ -0,0 +1,102 @@ +#![no_std] +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] +mod fmt; + +#[cfg(feature = "nightly")] +pub use embassy_boot::FirmwareUpdater; +pub use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig, State}; +use embassy_rp::flash::{Flash, ERASE_SIZE}; +use embassy_rp::peripherals::{FLASH, WATCHDOG}; +use embassy_rp::watchdog::Watchdog; +use embassy_time::Duration; +use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; + +/// A bootloader for RP2040 devices. +pub struct BootLoader { + boot: embassy_boot::BootLoader, + aligned_buf: AlignedBuffer, +} + +impl + BootLoader +{ + /// Create a new bootloader instance using the supplied partitions for active, dfu and state. + pub fn new(config: BootLoaderConfig) -> Self { + Self { + boot: embassy_boot::BootLoader::new(config), + aligned_buf: AlignedBuffer([0; BUFFER_SIZE]), + } + } + + /// Inspect the bootloader state and perform actions required before booting, such as swapping + /// firmware. + pub fn prepare(&mut self) { + self.boot + .prepare_boot(self.aligned_buf.as_mut()) + .expect("Boot prepare error"); + } + + /// Boots the application. + /// + /// # Safety + /// + /// This modifies the stack pointer and reset vector and will run code placed in the active partition. + pub unsafe fn load(self, start: u32) -> ! { + core::mem::drop(self.boot); + + trace!("Loading app at 0x{:x}", start); + #[allow(unused_mut)] + let mut p = cortex_m::Peripherals::steal(); + #[cfg(not(armv6m))] + p.SCB.invalidate_icache(); + p.SCB.vtor.write(start); + + cortex_m::asm::bootload(start as *const u32) + } +} + +/// A flash implementation that will feed a watchdog when touching flash. +pub struct WatchdogFlash<'d, const SIZE: usize> { + flash: Flash<'d, FLASH, SIZE>, + watchdog: Watchdog, +} + +impl<'d, const SIZE: usize> WatchdogFlash<'d, SIZE> { + /// Start a new watchdog with a given flash and watchdog peripheral and a timeout + pub fn start(flash: FLASH, watchdog: WATCHDOG, timeout: Duration) -> Self { + let flash: Flash<'_, FLASH, SIZE> = Flash::new(flash); + let mut watchdog = Watchdog::new(watchdog); + watchdog.start(timeout); + Self { flash, watchdog } + } +} + +impl<'d, const SIZE: usize> ErrorType for WatchdogFlash<'d, SIZE> { + type Error = as ErrorType>::Error; +} + +impl<'d, const SIZE: usize> NorFlash for WatchdogFlash<'d, SIZE> { + const WRITE_SIZE: usize = as NorFlash>::WRITE_SIZE; + const ERASE_SIZE: usize = as NorFlash>::ERASE_SIZE; + + fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.watchdog.feed(); + self.flash.erase(from, to) + } + fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> { + self.watchdog.feed(); + self.flash.write(offset, data) + } +} + +impl<'d, const SIZE: usize> ReadNorFlash for WatchdogFlash<'d, SIZE> { + const READ_SIZE: usize = as ReadNorFlash>::READ_SIZE; + fn read(&mut self, offset: u32, data: &mut [u8]) -> Result<(), Self::Error> { + self.watchdog.feed(); + self.flash.read(offset, data) + } + fn capacity(&self) -> usize { + self.flash.capacity() + } +} diff --git a/embassy-boot/stm32/Cargo.toml b/embassy-boot/stm32/Cargo.toml index ad4657e0d..99a6b8e0e 100644 --- a/embassy-boot/stm32/Cargo.toml +++ b/embassy-boot/stm32/Cargo.toml @@ -3,6 +3,7 @@ edition = "2021" name = "embassy-boot-stm32" version = "0.1.0" description = "Bootloader lib for STM32 chips" +license = "MIT OR Apache-2.0" [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-nrf-v$VERSION/embassy-boot/stm32/src/" @@ -14,16 +15,16 @@ target = "thumbv7em-none-eabi" [dependencies] defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } log = { version = "0.4", optional = true } embassy-sync = { path = "../../embassy-sync" } -embassy-stm32 = { path = "../../embassy-stm32", default-features = false, features = ["nightly"] } +embassy-stm32 = { path = "../../embassy-stm32", default-features = false } embassy-boot = { path = "../boot", default-features = false } cortex-m = { version = "0.7.6" } cortex-m-rt = { version = "0.7" } embedded-storage = "0.3.0" -embedded-storage-async = "0.3.0" +embedded-storage-async = { version = "0.4.0", optional = true } cfg-if = "1.0.0" [features] @@ -38,6 +39,11 @@ log = [ "embassy-stm32/log", ] debug = ["defmt-rtt"] +nightly = [ + "dep:embedded-storage-async", + "embassy-boot/nightly", + "embassy-stm32/nightly" +] [profile.dev] debug = 2 diff --git a/embassy-boot/stm32/README.md b/embassy-boot/stm32/README.md index a82b730b9..b4d7ba5a4 100644 --- a/embassy-boot/stm32/README.md +++ b/embassy-boot/stm32/README.md @@ -1,11 +1,24 @@ -# Bootloader for STM32 +# embassy-boot-stm32 -The bootloader uses `embassy-boot` to interact with the flash. +An [Embassy](https://embassy.dev) project. -# Usage +An adaptation of `embassy-boot` for STM32. -Flash the bootloader +## Features -``` -cargo flash --features embassy-stm32/stm32wl55jc-cm4 --release --chip STM32WLE5JCIx -``` +* Configure bootloader partitions based on linker script. +* Load applications from active partition. + +## Minimum supported Rust version (MSRV) + +`embassy-boot-stm32` is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release. + +## License + +This work is licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. diff --git a/embassy-boot/stm32/src/lib.rs b/embassy-boot/stm32/src/lib.rs index 5a4f2d058..069de0d1a 100644 --- a/embassy-boot/stm32/src/lib.rs +++ b/embassy-boot/stm32/src/lib.rs @@ -1,82 +1,52 @@ #![no_std] -#![feature(generic_associated_types)] -#![feature(type_alias_impl_trait)] -#![allow(incomplete_features)] -#![feature(generic_const_exprs)] - +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] mod fmt; -pub use embassy_boot::{FirmwareUpdater, FlashConfig, FlashProvider, Partition, SingleFlashProvider, State}; +#[cfg(feature = "nightly")] +pub use embassy_boot::FirmwareUpdater; +pub use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig, State}; use embedded_storage::nor_flash::NorFlash; -pub struct BootLoader { - boot: embassy_boot::BootLoader, +/// A bootloader for STM32 devices. +pub struct BootLoader { + boot: embassy_boot::BootLoader, + aligned_buf: AlignedBuffer, } -impl BootLoader { - /// Create a new bootloader instance using parameters from linker script - pub fn default() -> Self { - extern "C" { - static __bootloader_state_start: u32; - static __bootloader_state_end: u32; - static __bootloader_active_start: u32; - static __bootloader_active_end: u32; - static __bootloader_dfu_start: u32; - static __bootloader_dfu_end: u32; - } - - let active = unsafe { - Partition::new( - &__bootloader_active_start as *const u32 as usize, - &__bootloader_active_end as *const u32 as usize, - ) - }; - let dfu = unsafe { - Partition::new( - &__bootloader_dfu_start as *const u32 as usize, - &__bootloader_dfu_end as *const u32 as usize, - ) - }; - let state = unsafe { - Partition::new( - &__bootloader_state_start as *const u32 as usize, - &__bootloader_state_end as *const u32 as usize, - ) - }; - - trace!("ACTIVE: 0x{:x} - 0x{:x}", active.from, active.to); - trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to); - trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to); - - Self::new(active, dfu, state) - } - +impl + BootLoader +{ /// Create a new bootloader instance using the supplied partitions for active, dfu and state. - pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self { + pub fn new(config: BootLoaderConfig) -> Self { Self { - boot: embassy_boot::BootLoader::new(active, dfu, state), + boot: embassy_boot::BootLoader::new(config), + aligned_buf: AlignedBuffer([0; BUFFER_SIZE]), } } - /// Boots the application - pub fn prepare(&mut self, flash: &mut F) -> usize - where - [(); <::STATE as FlashConfig>::FLASH::WRITE_SIZE]:, - [(); <::ACTIVE as FlashConfig>::FLASH::ERASE_SIZE]:, - { - match self.boot.prepare_boot(flash) { - Ok(_) => embassy_stm32::flash::FLASH_BASE + self.boot.boot_address(), - Err(_) => panic!("boot prepare error!"), - } + /// Inspect the bootloader state and perform actions required before booting, such as swapping + /// firmware. + pub fn prepare(&mut self) { + self.boot + .prepare_boot(self.aligned_buf.as_mut()) + .expect("Boot prepare error"); } - pub unsafe fn load(&mut self, start: usize) -> ! { + /// Boots the application. + /// + /// # Safety + /// + /// This modifies the stack pointer and reset vector and will run code placed in the active partition. + pub unsafe fn load(self, start: u32) -> ! { + core::mem::drop(self.boot); + trace!("Loading app at 0x{:x}", start); #[allow(unused_mut)] let mut p = cortex_m::Peripherals::steal(); #[cfg(not(armv6m))] p.SCB.invalidate_icache(); - p.SCB.vtor.write(start as u32); + p.SCB.vtor.write(start); cortex_m::asm::bootload(start as *const u32) } diff --git a/embassy-cortex-m/Cargo.toml b/embassy-cortex-m/Cargo.toml deleted file mode 100644 index 7efced669..000000000 --- a/embassy-cortex-m/Cargo.toml +++ /dev/null @@ -1,46 +0,0 @@ -[package] -name = "embassy-cortex-m" -version = "0.1.0" -edition = "2021" - -[package.metadata.embassy_docs] -src_base = "https://github.com/embassy-rs/embassy/blob/embassy-cortex-m-v$VERSION/embassy-cortex-m/src/" -src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-cortex-m/src/" -features = ["prio-bits-3"] -flavors = [ - { name = "thumbv6m-none-eabi", target = "thumbv6m-none-eabi", features = [] }, - { name = "thumbv7m-none-eabi", target = "thumbv7m-none-eabi", features = [] }, - { name = "thumbv7em-none-eabi", target = "thumbv7em-none-eabi", features = [] }, - { name = "thumbv7em-none-eabihf", target = "thumbv7em-none-eabihf", features = [] }, - { name = "thumbv8m.base-none-eabi", target = "thumbv8m.base-none-eabi", features = [] }, - { name = "thumbv8m.main-none-eabi", target = "thumbv8m.main-none-eabi", features = [] }, - { name = "thumbv8m.main-none-eabihf", target = "thumbv8m.main-none-eabihf", features = [] }, -] - -[features] -default = [] - -# Define the number of NVIC priority bits. -prio-bits-0 = [] -prio-bits-1 = [] -prio-bits-2 = [] -prio-bits-3 = [] -prio-bits-4 = [] -prio-bits-5 = [] -prio-bits-6 = [] -prio-bits-7 = [] -prio-bits-8 = [] - -[dependencies] -defmt = { version = "0.3", optional = true } -log = { version = "0.4.14", optional = true } - -embassy-sync = { version = "0.1.0", path = "../embassy-sync" } -embassy-executor = { version = "0.1.0", path = "../embassy-executor"} -embassy-macros = { version = "0.1.0", path = "../embassy-macros"} -embassy-hal-common = { version = "0.1.0", path = "../embassy-hal-common"} -atomic-polyfill = "1.0.1" -critical-section = "1.1" -cfg-if = "1.0.0" -cortex-m = "0.7.6" - diff --git a/embassy-cortex-m/src/executor.rs b/embassy-cortex-m/src/executor.rs deleted file mode 100644 index 0d1745d8a..000000000 --- a/embassy-cortex-m/src/executor.rs +++ /dev/null @@ -1,89 +0,0 @@ -//! Executor specific to cortex-m devices. -use core::marker::PhantomData; - -pub use embassy_executor::*; - -use crate::interrupt::{Interrupt, InterruptExt}; - -fn pend_by_number(n: u16) { - #[derive(Clone, Copy)] - struct N(u16); - unsafe impl cortex_m::interrupt::InterruptNumber for N { - fn number(self) -> u16 { - self.0 - } - } - cortex_m::peripheral::NVIC::pend(N(n)) -} - -/// Interrupt mode executor. -/// -/// This executor runs tasks in interrupt mode. The interrupt handler is set up -/// to poll tasks, and when a task is woken the interrupt is pended from software. -/// -/// This allows running async tasks at a priority higher than thread mode. One -/// use case is to leave thread mode free for non-async tasks. Another use case is -/// to run multiple executors: one in thread mode for low priority tasks and another in -/// interrupt mode for higher priority tasks. Higher priority tasks will preempt lower -/// priority ones. -/// -/// It is even possible to run multiple interrupt mode executors at different priorities, -/// by assigning different priorities to the interrupts. For an example on how to do this, -/// See the 'multiprio' example for 'embassy-nrf'. -/// -/// To use it, you have to pick an interrupt that won't be used by the hardware. -/// Some chips reserve some interrupts for this purpose, sometimes named "software interrupts" (SWI). -/// If this is not the case, you may use an interrupt from any unused peripheral. -/// -/// It is somewhat more complex to use, it's recommended to use the thread-mode -/// [`Executor`] instead, if it works for your use case. -pub struct InterruptExecutor { - irq: I, - inner: raw::Executor, - not_send: PhantomData<*mut ()>, -} - -impl InterruptExecutor { - /// Create a new Executor. - pub fn new(irq: I) -> Self { - let ctx = irq.number() as *mut (); - Self { - irq, - inner: raw::Executor::new(|ctx| pend_by_number(ctx as u16), ctx), - not_send: PhantomData, - } - } - - /// Start the executor. - /// - /// This initializes the executor, configures and enables the interrupt, and returns. - /// The executor keeps running in the background through the interrupt. - /// - /// This returns a [`SendSpawner`] you can use to spawn tasks on it. A [`SendSpawner`] - /// is returned instead of a [`Spawner`](embassy_executor::Spawner) because the executor effectively runs in a - /// different "thread" (the interrupt), so spawning tasks on it is effectively - /// sending them. - /// - /// To obtain a [`Spawner`](embassy_executor::Spawner) for this executor, use [`Spawner::for_current_executor()`](embassy_executor::Spawner::for_current_executor()) from - /// a task running in it. - /// - /// This function requires `&'static mut self`. This means you have to store the - /// Executor instance in a place where it'll live forever and grants you mutable - /// access. There's a few ways to do this: - /// - /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe) - /// - a `static mut` (unsafe) - /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) - pub fn start(&'static mut self) -> SendSpawner { - self.irq.disable(); - - self.irq.set_handler(|ctx| unsafe { - let executor = &*(ctx as *const raw::Executor); - executor.poll(); - }); - self.irq.set_handler_context(&self.inner as *const _ as _); - self.irq.enable(); - - self.inner.spawner().make_send() - } -} diff --git a/embassy-cortex-m/src/lib.rs b/embassy-cortex-m/src/lib.rs deleted file mode 100644 index fba23367b..000000000 --- a/embassy-cortex-m/src/lib.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! Embassy executor and interrupt handling specific to cortex-m devices. -#![no_std] -#![warn(missing_docs)] - -// This mod MUST go first, so that the others see its macros. -pub(crate) mod fmt; - -pub mod executor; -pub mod interrupt; -pub mod peripheral; diff --git a/embassy-cortex-m/src/peripheral.rs b/embassy-cortex-m/src/peripheral.rs deleted file mode 100644 index e2f295579..000000000 --- a/embassy-cortex-m/src/peripheral.rs +++ /dev/null @@ -1,144 +0,0 @@ -//! Peripheral interrupt handling specific to cortex-m devices. -use core::mem::MaybeUninit; - -use cortex_m::peripheral::scb::VectActive; -use cortex_m::peripheral::{NVIC, SCB}; -use embassy_hal_common::{into_ref, Peripheral, PeripheralRef}; - -use crate::interrupt::{Interrupt, InterruptExt, Priority}; - -/// A type which can be used as state with `PeripheralMutex`. -/// -/// It needs to be `Send` because `&mut` references are sent back and forth between the 'thread' which owns the `PeripheralMutex` and the interrupt, -/// and `&mut T` is only `Send` where `T: Send`. -pub trait PeripheralState: Send { - /// The interrupt that is used for this peripheral. - type Interrupt: Interrupt; - - /// The interrupt handler that should be invoked for the peripheral. Implementations need to clear the appropriate interrupt flags to ensure the handle will not be called again. - fn on_interrupt(&mut self); -} - -/// A type for storing the state of a peripheral that can be stored in a static. -pub struct StateStorage(MaybeUninit); - -impl StateStorage { - /// Create a new instance for storing peripheral state. - pub const fn new() -> Self { - Self(MaybeUninit::uninit()) - } -} - -/// A type for a peripheral that keeps the state of a peripheral that can be accessed from thread mode and an interrupt handler in -/// a safe way. -pub struct PeripheralMutex<'a, S: PeripheralState> { - state: *mut S, - irq: PeripheralRef<'a, S::Interrupt>, -} - -/// Whether `irq` can be preempted by the current interrupt. -pub(crate) fn can_be_preempted(irq: &impl Interrupt) -> bool { - match SCB::vect_active() { - // Thread mode can't preempt anything. - VectActive::ThreadMode => false, - // Exceptions don't always preempt interrupts, - // but there isn't much of a good reason to be keeping a `PeripheralMutex` in an exception anyway. - VectActive::Exception(_) => true, - VectActive::Interrupt { irqn } => { - #[derive(Clone, Copy)] - struct NrWrap(u16); - unsafe impl cortex_m::interrupt::InterruptNumber for NrWrap { - fn number(self) -> u16 { - self.0 - } - } - NVIC::get_priority(NrWrap(irqn.into())) < irq.get_priority().into() - } - } -} - -impl<'a, S: PeripheralState> PeripheralMutex<'a, S> { - /// Create a new `PeripheralMutex` wrapping `irq`, with `init` initializing the initial state. - /// - /// Registers `on_interrupt` as the `irq`'s handler, and enables it. - pub fn new( - irq: impl Peripheral

+ 'a, - storage: &'a mut StateStorage, - init: impl FnOnce() -> S, - ) -> Self { - into_ref!(irq); - - if can_be_preempted(&*irq) { - panic!( - "`PeripheralMutex` cannot be created in an interrupt with higher priority than the interrupt it wraps" - ); - } - - let state_ptr = storage.0.as_mut_ptr(); - - // Safety: The pointer is valid and not used by anyone else - // because we have the `&mut StateStorage`. - unsafe { state_ptr.write(init()) }; - - irq.disable(); - irq.set_handler(|p| unsafe { - // Safety: it's OK to get a &mut to the state, since - // - We checked that the thread owning the `PeripheralMutex` can't preempt us in `new`. - // Interrupts' priorities can only be changed with raw embassy `Interrupts`, - // which can't safely store a `PeripheralMutex` across invocations. - // - We can't have preempted a with() call because the irq is disabled during it. - let state = &mut *(p as *mut S); - state.on_interrupt(); - }); - irq.set_handler_context(state_ptr as *mut ()); - irq.enable(); - - Self { irq, state: state_ptr } - } - - /// Access the peripheral state ensuring interrupts are disabled so that the state can be - /// safely accessed. - pub fn with(&mut self, f: impl FnOnce(&mut S) -> R) -> R { - self.irq.disable(); - - // Safety: it's OK to get a &mut to the state, since the irq is disabled. - let state = unsafe { &mut *self.state }; - let r = f(state); - - self.irq.enable(); - - r - } - - /// Returns whether the wrapped interrupt is currently in a pending state. - pub fn is_pending(&self) -> bool { - self.irq.is_pending() - } - - /// Forces the wrapped interrupt into a pending state. - pub fn pend(&self) { - self.irq.pend() - } - - /// Forces the wrapped interrupt out of a pending state. - pub fn unpend(&self) { - self.irq.unpend() - } - - /// Gets the priority of the wrapped interrupt. - pub fn priority(&self) -> Priority { - self.irq.get_priority() - } -} - -impl<'a, S: PeripheralState> Drop for PeripheralMutex<'a, S> { - fn drop(&mut self) { - self.irq.disable(); - self.irq.remove_handler(); - - // safety: - // - we initialized the state in `new`, so we know it's initialized. - // - the irq is disabled, so it won't preempt us while dropping. - unsafe { self.state.drop_in_place() } - } -} diff --git a/embassy-embedded-hal/Cargo.toml b/embassy-embedded-hal/Cargo.toml index 462680720..bba3d48be 100644 --- a/embassy-embedded-hal/Cargo.toml +++ b/embassy-embedded-hal/Cargo.toml @@ -2,26 +2,37 @@ name = "embassy-embedded-hal" version = "0.1.0" edition = "2021" +license = "MIT OR Apache-2.0" [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-embedded-hal-v$VERSION/embassy-embedded-hal/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-embedded-hal/src/" features = ["nightly", "std"] -target = "thumbv7em-none-eabi" +target = "x86_64-unknown-linux-gnu" [features] std = [] # Enable nightly-only features -nightly = ["embedded-hal-async", "embedded-storage-async"] +nightly = ["embassy-futures", "embedded-hal-async", "embedded-storage-async"] +time = ["dep:embassy-time"] +default = ["time"] [dependencies] -embassy-sync = { version = "0.1.0", path = "../embassy-sync" } -embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } -embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.8" } -embedded-hal-async = { version = "0.1.0-alpha.1", optional = true } +embassy-futures = { version = "0.1.0", path = "../embassy-futures", optional = true } +embassy-sync = { version = "0.2.0", path = "../embassy-sync" } +embassy-time = { version = "0.1.2", path = "../embassy-time", optional = true } +embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = [ + "unproven", +] } +embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11" } +embedded-hal-async = { version = "=0.2.0-alpha.2", optional = true } embedded-storage = "0.3.0" -embedded-storage-async = { version = "0.3.0", optional = true } +embedded-storage-async = { version = "0.4.0", optional = true } nb = "1.0.0" defmt = { version = "0.3", optional = true } + +[dev-dependencies] +critical-section = { version = "1.1.1", features = ["std"] } +futures-test = "0.3.17" diff --git a/embassy-embedded-hal/src/adapter.rs b/embassy-embedded-hal/src/adapter.rs deleted file mode 100644 index 1c43f015f..000000000 --- a/embassy-embedded-hal/src/adapter.rs +++ /dev/null @@ -1,242 +0,0 @@ -//! Adapters between embedded-hal traits. - -use core::future::Future; - -use embedded_hal_02::{blocking, serial}; - -/// Wrapper that implements async traits using blocking implementations. -/// -/// This allows driver writers to depend on the async traits while still supporting embedded-hal peripheral implementations. -/// -/// BlockingAsync will implement any async trait that maps to embedded-hal traits implemented for the wrapped driver. -/// -/// Driver users are then free to choose which implementation that is available to them. -pub struct BlockingAsync { - wrapped: T, -} - -impl BlockingAsync { - /// Create a new instance of a wrapper for a given peripheral. - pub fn new(wrapped: T) -> Self { - Self { wrapped } - } -} - -// -// I2C implementations -// -impl embedded_hal_1::i2c::ErrorType for BlockingAsync -where - E: embedded_hal_1::i2c::Error + 'static, - T: blocking::i2c::WriteRead + blocking::i2c::Read + blocking::i2c::Write, -{ - type Error = E; -} - -impl embedded_hal_async::i2c::I2c for BlockingAsync -where - E: embedded_hal_1::i2c::Error + 'static, - T: blocking::i2c::WriteRead + blocking::i2c::Read + blocking::i2c::Write, -{ - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; - type WriteReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn read<'a>(&'a mut self, address: u8, buffer: &'a mut [u8]) -> Self::ReadFuture<'a> { - async move { self.wrapped.read(address, buffer) } - } - - fn write<'a>(&'a mut self, address: u8, bytes: &'a [u8]) -> Self::WriteFuture<'a> { - async move { self.wrapped.write(address, bytes) } - } - - fn write_read<'a>(&'a mut self, address: u8, bytes: &'a [u8], buffer: &'a mut [u8]) -> Self::WriteReadFuture<'a> { - async move { self.wrapped.write_read(address, bytes, buffer) } - } - - type TransactionFuture<'a, 'b> = impl Future> + 'a where Self: 'a, 'b: 'a; - - fn transaction<'a, 'b>( - &'a mut self, - address: u8, - operations: &'a mut [embedded_hal_async::i2c::Operation<'b>], - ) -> Self::TransactionFuture<'a, 'b> { - let _ = address; - let _ = operations; - async move { todo!() } - } -} - -// -// SPI implementatinos -// - -impl embedded_hal_async::spi::ErrorType for BlockingAsync -where - E: embedded_hal_1::spi::Error, - T: blocking::spi::Transfer + blocking::spi::Write, -{ - type Error = E; -} - -impl embedded_hal_async::spi::SpiBus for BlockingAsync -where - E: embedded_hal_1::spi::Error + 'static, - T: blocking::spi::Transfer + blocking::spi::Write, -{ - type TransferFuture<'a> = impl Future> + 'a where Self: 'a; - - fn transfer<'a>(&'a mut self, read: &'a mut [u8], write: &'a [u8]) -> Self::TransferFuture<'a> { - async move { - // Ensure we write the expected bytes - for i in 0..core::cmp::min(read.len(), write.len()) { - read[i] = write[i].clone(); - } - self.wrapped.transfer(read)?; - Ok(()) - } - } - - type TransferInPlaceFuture<'a> = impl Future> + 'a where Self: 'a; - - fn transfer_in_place<'a>(&'a mut self, _: &'a mut [u8]) -> Self::TransferInPlaceFuture<'a> { - async move { todo!() } - } -} - -impl embedded_hal_async::spi::SpiBusFlush for BlockingAsync -where - E: embedded_hal_1::spi::Error + 'static, - T: blocking::spi::Transfer + blocking::spi::Write, -{ - type FlushFuture<'a> = impl Future> + 'a where Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - async move { Ok(()) } - } -} - -impl embedded_hal_async::spi::SpiBusWrite for BlockingAsync -where - E: embedded_hal_1::spi::Error + 'static, - T: blocking::spi::Transfer + blocking::spi::Write, -{ - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - - fn write<'a>(&'a mut self, data: &'a [u8]) -> Self::WriteFuture<'a> { - async move { - self.wrapped.write(data)?; - Ok(()) - } - } -} - -impl embedded_hal_async::spi::SpiBusRead for BlockingAsync -where - E: embedded_hal_1::spi::Error + 'static, - T: blocking::spi::Transfer + blocking::spi::Write, -{ - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn read<'a>(&'a mut self, data: &'a mut [u8]) -> Self::ReadFuture<'a> { - async move { - self.wrapped.transfer(data)?; - Ok(()) - } - } -} - -// Uart implementatinos -impl embedded_hal_1::serial::ErrorType for BlockingAsync -where - T: serial::Read, - E: embedded_hal_1::serial::Error + 'static, -{ - type Error = E; -} - -#[cfg(feature = "_todo_embedded_hal_serial")] -impl embedded_hal_async::serial::Read for BlockingAsync -where - T: serial::Read, - E: embedded_hal_1::serial::Error + 'static, -{ - type ReadFuture<'a> = impl Future> + 'a where T: 'a; - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - async move { - let mut pos = 0; - while pos < buf.len() { - match self.wrapped.read() { - Err(nb::Error::WouldBlock) => {} - Err(nb::Error::Other(e)) => return Err(e), - Ok(b) => { - buf[pos] = b; - pos += 1; - } - } - } - Ok(()) - } - } -} - -#[cfg(feature = "_todo_embedded_hal_serial")] -impl embedded_hal_async::serial::Write for BlockingAsync -where - T: blocking::serial::Write + serial::Read, - E: embedded_hal_1::serial::Error + 'static, -{ - type WriteFuture<'a> = impl Future> + 'a where T: 'a; - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { - async move { self.wrapped.bwrite_all(buf) } - } - - type FlushFuture<'a> = impl Future> + 'a where T: 'a; - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - async move { self.wrapped.bflush() } - } -} - -/// NOR flash wrapper -use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; -use embedded_storage_async::nor_flash::{AsyncNorFlash, AsyncReadNorFlash}; - -impl ErrorType for BlockingAsync -where - T: ErrorType, -{ - type Error = T::Error; -} - -impl AsyncNorFlash for BlockingAsync -where - T: NorFlash, -{ - const WRITE_SIZE: usize = ::WRITE_SIZE; - const ERASE_SIZE: usize = ::ERASE_SIZE; - - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - fn write<'a>(&'a mut self, offset: u32, data: &'a [u8]) -> Self::WriteFuture<'a> { - async move { self.wrapped.write(offset, data) } - } - - type EraseFuture<'a> = impl Future> + 'a where Self: 'a; - fn erase<'a>(&'a mut self, from: u32, to: u32) -> Self::EraseFuture<'a> { - async move { self.wrapped.erase(from, to) } - } -} - -impl AsyncReadNorFlash for BlockingAsync -where - T: ReadNorFlash, -{ - const READ_SIZE: usize = ::READ_SIZE; - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; - fn read<'a>(&'a mut self, address: u32, data: &'a mut [u8]) -> Self::ReadFuture<'a> { - async move { self.wrapped.read(address, data) } - } - - fn capacity(&self) -> usize { - self.wrapped.capacity() - } -} diff --git a/embassy-embedded-hal/src/adapter/blocking_async.rs b/embassy-embedded-hal/src/adapter/blocking_async.rs new file mode 100644 index 000000000..98ae2b02c --- /dev/null +++ b/embassy-embedded-hal/src/adapter/blocking_async.rs @@ -0,0 +1,154 @@ +use embedded_hal_02::{blocking, serial}; + +/// Wrapper that implements async traits using blocking implementations. +/// +/// This allows driver writers to depend on the async traits while still supporting embedded-hal peripheral implementations. +/// +/// BlockingAsync will implement any async trait that maps to embedded-hal traits implemented for the wrapped driver. +/// +/// Driver users are then free to choose which implementation that is available to them. +pub struct BlockingAsync { + wrapped: T, +} + +impl BlockingAsync { + /// Create a new instance of a wrapper for a given peripheral. + pub fn new(wrapped: T) -> Self { + Self { wrapped } + } +} + +// +// I2C implementations +// +impl embedded_hal_1::i2c::ErrorType for BlockingAsync +where + E: embedded_hal_1::i2c::Error + 'static, + T: blocking::i2c::WriteRead + blocking::i2c::Read + blocking::i2c::Write, +{ + type Error = E; +} + +impl embedded_hal_async::i2c::I2c for BlockingAsync +where + E: embedded_hal_1::i2c::Error + 'static, + T: blocking::i2c::WriteRead + blocking::i2c::Read + blocking::i2c::Write, +{ + async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> { + self.wrapped.read(address, read) + } + + async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> { + self.wrapped.write(address, write) + } + + async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> { + self.wrapped.write_read(address, write, read) + } + + async fn transaction( + &mut self, + address: u8, + operations: &mut [embedded_hal_1::i2c::Operation<'_>], + ) -> Result<(), Self::Error> { + let _ = address; + let _ = operations; + todo!() + } +} + +// +// SPI implementatinos +// + +impl embedded_hal_async::spi::ErrorType for BlockingAsync +where + E: embedded_hal_1::spi::Error, + T: blocking::spi::Transfer + blocking::spi::Write, +{ + type Error = E; +} + +impl embedded_hal_async::spi::SpiBus for BlockingAsync +where + E: embedded_hal_1::spi::Error + 'static, + T: blocking::spi::Transfer + blocking::spi::Write, +{ + async fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + + async fn write(&mut self, data: &[u8]) -> Result<(), Self::Error> { + self.wrapped.write(data)?; + Ok(()) + } + + async fn read(&mut self, data: &mut [u8]) -> Result<(), Self::Error> { + self.wrapped.transfer(data)?; + Ok(()) + } + + async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { + // Ensure we write the expected bytes + for i in 0..core::cmp::min(read.len(), write.len()) { + read[i] = write[i].clone(); + } + self.wrapped.transfer(read)?; + Ok(()) + } + + async fn transfer_in_place(&mut self, data: &mut [u8]) -> Result<(), Self::Error> { + self.wrapped.transfer(data)?; + Ok(()) + } +} + +// Uart implementatinos +impl embedded_hal_1::serial::ErrorType for BlockingAsync +where + T: serial::Read, + E: embedded_hal_1::serial::Error + 'static, +{ + type Error = E; +} + +/// NOR flash wrapper +use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; +use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; + +impl ErrorType for BlockingAsync +where + T: ErrorType, +{ + type Error = T::Error; +} + +impl AsyncNorFlash for BlockingAsync +where + T: NorFlash, +{ + const WRITE_SIZE: usize = ::WRITE_SIZE; + const ERASE_SIZE: usize = ::ERASE_SIZE; + + async fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> { + self.wrapped.write(offset, data) + } + + async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.wrapped.erase(from, to) + } +} + +impl AsyncReadNorFlash for BlockingAsync +where + T: ReadNorFlash, +{ + const READ_SIZE: usize = ::READ_SIZE; + async fn read(&mut self, address: u32, data: &mut [u8]) -> Result<(), Self::Error> { + self.wrapped.read(address, data) + } + + fn capacity(&self) -> usize { + self.wrapped.capacity() + } +} diff --git a/embassy-embedded-hal/src/adapter/mod.rs b/embassy-embedded-hal/src/adapter/mod.rs new file mode 100644 index 000000000..28da56137 --- /dev/null +++ b/embassy-embedded-hal/src/adapter/mod.rs @@ -0,0 +1,7 @@ +//! Adapters between embedded-hal traits. + +mod blocking_async; +mod yielding_async; + +pub use blocking_async::BlockingAsync; +pub use yielding_async::YieldingAsync; diff --git a/embassy-embedded-hal/src/adapter/yielding_async.rs b/embassy-embedded-hal/src/adapter/yielding_async.rs new file mode 100644 index 000000000..fe9c9c341 --- /dev/null +++ b/embassy-embedded-hal/src/adapter/yielding_async.rs @@ -0,0 +1,169 @@ +use embassy_futures::yield_now; + +/// Wrapper that yields for each operation to the wrapped instance +/// +/// This can be used in combination with BlockingAsync to enforce yields +/// between long running blocking operations. +pub struct YieldingAsync { + wrapped: T, +} + +impl YieldingAsync { + /// Create a new instance of a wrapper that yields after each operation. + pub fn new(wrapped: T) -> Self { + Self { wrapped } + } +} + +// +// I2C implementations +// +impl embedded_hal_1::i2c::ErrorType for YieldingAsync +where + T: embedded_hal_1::i2c::ErrorType, +{ + type Error = T::Error; +} + +impl embedded_hal_async::i2c::I2c for YieldingAsync +where + T: embedded_hal_async::i2c::I2c, +{ + async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> { + self.wrapped.read(address, read).await?; + yield_now().await; + Ok(()) + } + + async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> { + self.wrapped.write(address, write).await?; + yield_now().await; + Ok(()) + } + + async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> { + self.wrapped.write_read(address, write, read).await?; + yield_now().await; + Ok(()) + } + + async fn transaction( + &mut self, + address: u8, + operations: &mut [embedded_hal_1::i2c::Operation<'_>], + ) -> Result<(), Self::Error> { + self.wrapped.transaction(address, operations).await?; + yield_now().await; + Ok(()) + } +} + +// +// SPI implementations +// + +impl embedded_hal_async::spi::ErrorType for YieldingAsync +where + T: embedded_hal_async::spi::ErrorType, +{ + type Error = T::Error; +} + +impl embedded_hal_async::spi::SpiBus for YieldingAsync +where + T: embedded_hal_async::spi::SpiBus, +{ + async fn flush(&mut self) -> Result<(), Self::Error> { + self.wrapped.flush().await?; + yield_now().await; + Ok(()) + } + + async fn write(&mut self, data: &[Word]) -> Result<(), Self::Error> { + self.wrapped.write(data).await?; + yield_now().await; + Ok(()) + } + + async fn read(&mut self, data: &mut [Word]) -> Result<(), Self::Error> { + self.wrapped.read(data).await?; + yield_now().await; + Ok(()) + } + + async fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> { + self.wrapped.transfer(read, write).await?; + yield_now().await; + Ok(()) + } + + async fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { + self.wrapped.transfer_in_place(words).await?; + yield_now().await; + Ok(()) + } +} + +/// +/// NOR flash implementations +/// +impl embedded_storage::nor_flash::ErrorType for YieldingAsync { + type Error = T::Error; +} + +impl embedded_storage_async::nor_flash::ReadNorFlash + for YieldingAsync +{ + const READ_SIZE: usize = T::READ_SIZE; + + async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.wrapped.read(offset, bytes).await?; + Ok(()) + } + + fn capacity(&self) -> usize { + self.wrapped.capacity() + } +} + +impl embedded_storage_async::nor_flash::NorFlash for YieldingAsync { + const WRITE_SIZE: usize = T::WRITE_SIZE; + const ERASE_SIZE: usize = T::ERASE_SIZE; + + async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.wrapped.write(offset, bytes).await?; + yield_now().await; + Ok(()) + } + + async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + // Yield between each actual erase + for from in (from..to).step_by(T::ERASE_SIZE) { + let to = core::cmp::min(from + T::ERASE_SIZE as u32, to); + self.wrapped.erase(from, to).await?; + yield_now().await; + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use embedded_storage_async::nor_flash::NorFlash; + + use super::*; + use crate::flash::mem_flash::MemFlash; + + #[futures_test::test] + async fn can_erase() { + let flash = MemFlash::<1024, 128, 4>::new(0x00); + let mut yielding = YieldingAsync::new(flash); + + yielding.erase(0, 256).await.unwrap(); + + let flash = yielding.wrapped; + assert_eq!(2, flash.erases.len()); + assert_eq!((0, 128), flash.erases[0]); + assert_eq!((128, 256), flash.erases[1]); + } +} diff --git a/embassy-embedded-hal/src/flash/concat_flash.rs b/embassy-embedded-hal/src/flash/concat_flash.rs new file mode 100644 index 000000000..1ea84269c --- /dev/null +++ b/embassy-embedded-hal/src/flash/concat_flash.rs @@ -0,0 +1,228 @@ +use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, ReadNorFlash}; +#[cfg(feature = "nightly")] +use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; + +/// Convenience helper for concatenating two consecutive flashes into one. +/// This is especially useful if used with "flash regions", where one may +/// want to concatenate multiple regions into one larger region. +pub struct ConcatFlash(First, Second); + +impl ConcatFlash { + /// Create a new flash that concatenates two consecutive flashes. + pub fn new(first: First, second: Second) -> Self { + Self(first, second) + } +} + +const fn get_read_size(first_read_size: usize, second_read_size: usize) -> usize { + if first_read_size != second_read_size { + panic!("The read size for the concatenated flashes must be the same"); + } + first_read_size +} + +const fn get_write_size(first_write_size: usize, second_write_size: usize) -> usize { + if first_write_size != second_write_size { + panic!("The write size for the concatenated flashes must be the same"); + } + first_write_size +} + +const fn get_max_erase_size(first_erase_size: usize, second_erase_size: usize) -> usize { + let max_erase_size = if first_erase_size > second_erase_size { + first_erase_size + } else { + second_erase_size + }; + if max_erase_size % first_erase_size != 0 || max_erase_size % second_erase_size != 0 { + panic!("The erase sizes for the concatenated flashes must have have a gcd equal to the max erase size"); + } + max_erase_size +} + +impl ErrorType for ConcatFlash +where + First: ErrorType, + Second: ErrorType, + E: NorFlashError, +{ + type Error = E; +} + +impl ReadNorFlash for ConcatFlash +where + First: ReadNorFlash, + Second: ReadNorFlash, + E: NorFlashError, +{ + const READ_SIZE: usize = get_read_size(First::READ_SIZE, Second::READ_SIZE); + + fn read(&mut self, mut offset: u32, mut bytes: &mut [u8]) -> Result<(), E> { + if offset < self.0.capacity() as u32 { + let len = core::cmp::min(self.0.capacity() - offset as usize, bytes.len()); + self.0.read(offset, &mut bytes[..len])?; + offset += len as u32; + bytes = &mut bytes[len..]; + } + + if !bytes.is_empty() { + self.1.read(offset - self.0.capacity() as u32, bytes)?; + } + + Ok(()) + } + + fn capacity(&self) -> usize { + self.0.capacity() + self.1.capacity() + } +} + +impl NorFlash for ConcatFlash +where + First: NorFlash, + Second: NorFlash, + E: NorFlashError, +{ + const WRITE_SIZE: usize = get_write_size(First::WRITE_SIZE, Second::WRITE_SIZE); + const ERASE_SIZE: usize = get_max_erase_size(First::ERASE_SIZE, Second::ERASE_SIZE); + + fn write(&mut self, mut offset: u32, mut bytes: &[u8]) -> Result<(), E> { + if offset < self.0.capacity() as u32 { + let len = core::cmp::min(self.0.capacity() - offset as usize, bytes.len()); + self.0.write(offset, &bytes[..len])?; + offset += len as u32; + bytes = &bytes[len..]; + } + + if !bytes.is_empty() { + self.1.write(offset - self.0.capacity() as u32, bytes)?; + } + + Ok(()) + } + + fn erase(&mut self, mut from: u32, to: u32) -> Result<(), E> { + if from < self.0.capacity() as u32 { + let to = core::cmp::min(self.0.capacity() as u32, to); + self.0.erase(from, to)?; + from = self.0.capacity() as u32; + } + + if from < to { + self.1 + .erase(from - self.0.capacity() as u32, to - self.0.capacity() as u32)?; + } + + Ok(()) + } +} + +#[cfg(feature = "nightly")] +impl AsyncReadNorFlash for ConcatFlash +where + First: AsyncReadNorFlash, + Second: AsyncReadNorFlash, + E: NorFlashError, +{ + const READ_SIZE: usize = get_read_size(First::READ_SIZE, Second::READ_SIZE); + + async fn read(&mut self, mut offset: u32, mut bytes: &mut [u8]) -> Result<(), E> { + if offset < self.0.capacity() as u32 { + let len = core::cmp::min(self.0.capacity() - offset as usize, bytes.len()); + self.0.read(offset, &mut bytes[..len]).await?; + offset += len as u32; + bytes = &mut bytes[len..]; + } + + if !bytes.is_empty() { + self.1.read(offset - self.0.capacity() as u32, bytes).await?; + } + + Ok(()) + } + + fn capacity(&self) -> usize { + self.0.capacity() + self.1.capacity() + } +} + +#[cfg(feature = "nightly")] +impl AsyncNorFlash for ConcatFlash +where + First: AsyncNorFlash, + Second: AsyncNorFlash, + E: NorFlashError, +{ + const WRITE_SIZE: usize = get_write_size(First::WRITE_SIZE, Second::WRITE_SIZE); + const ERASE_SIZE: usize = get_max_erase_size(First::ERASE_SIZE, Second::ERASE_SIZE); + + async fn write(&mut self, mut offset: u32, mut bytes: &[u8]) -> Result<(), E> { + if offset < self.0.capacity() as u32 { + let len = core::cmp::min(self.0.capacity() - offset as usize, bytes.len()); + self.0.write(offset, &bytes[..len]).await?; + offset += len as u32; + bytes = &bytes[len..]; + } + + if !bytes.is_empty() { + self.1.write(offset - self.0.capacity() as u32, bytes).await?; + } + + Ok(()) + } + + async fn erase(&mut self, mut from: u32, to: u32) -> Result<(), E> { + if from < self.0.capacity() as u32 { + let to = core::cmp::min(self.0.capacity() as u32, to); + self.0.erase(from, to).await?; + from = self.0.capacity() as u32; + } + + if from < to { + self.1 + .erase(from - self.0.capacity() as u32, to - self.0.capacity() as u32) + .await?; + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; + + use super::ConcatFlash; + use crate::flash::mem_flash::MemFlash; + + #[test] + fn can_write_and_read_across_flashes() { + let first = MemFlash::<64, 16, 4>::default(); + let second = MemFlash::<64, 64, 4>::default(); + let mut f = ConcatFlash::new(first, second); + + f.write(60, &[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88]).unwrap(); + + assert_eq!(&[0x11, 0x22, 0x33, 0x44], &f.0.mem[60..]); + assert_eq!(&[0x55, 0x66, 0x77, 0x88], &f.1.mem[0..4]); + + let mut read_buf = [0; 8]; + f.read(60, &mut read_buf).unwrap(); + + assert_eq!(&[0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88], &read_buf); + } + + #[test] + fn can_erase_across_flashes() { + let first = MemFlash::<128, 16, 4>::new(0x00); + let second = MemFlash::<128, 64, 4>::new(0x00); + let mut f = ConcatFlash::new(first, second); + + f.erase(64, 192).unwrap(); + + assert_eq!(&[0x00; 64], &f.0.mem[0..64]); + assert_eq!(&[0xff; 64], &f.0.mem[64..128]); + assert_eq!(&[0xff; 64], &f.1.mem[0..64]); + assert_eq!(&[0x00; 64], &f.1.mem[64..128]); + } +} diff --git a/embassy-embedded-hal/src/flash/mem_flash.rs b/embassy-embedded-hal/src/flash/mem_flash.rs new file mode 100644 index 000000000..afb0d1a15 --- /dev/null +++ b/embassy-embedded-hal/src/flash/mem_flash.rs @@ -0,0 +1,128 @@ +use alloc::vec::Vec; + +use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; +#[cfg(feature = "nightly")] +use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; + +extern crate alloc; + +pub(crate) struct MemFlash { + pub mem: [u8; SIZE], + pub writes: Vec<(u32, usize)>, + pub erases: Vec<(u32, u32)>, +} + +impl MemFlash { + #[allow(unused)] + pub const fn new(fill: u8) -> Self { + Self { + mem: [fill; SIZE], + writes: Vec::new(), + erases: Vec::new(), + } + } + + fn read(&mut self, offset: u32, bytes: &mut [u8]) { + let len = bytes.len(); + bytes.copy_from_slice(&self.mem[offset as usize..offset as usize + len]); + } + + fn write(&mut self, offset: u32, bytes: &[u8]) { + self.writes.push((offset, bytes.len())); + let offset = offset as usize; + assert_eq!(0, bytes.len() % WRITE_SIZE); + assert_eq!(0, offset % WRITE_SIZE); + assert!(offset + bytes.len() <= SIZE); + + self.mem[offset..offset + bytes.len()].copy_from_slice(bytes); + } + + fn erase(&mut self, from: u32, to: u32) { + self.erases.push((from, to)); + let from = from as usize; + let to = to as usize; + assert_eq!(0, from % ERASE_SIZE); + assert_eq!(0, to % ERASE_SIZE); + self.mem[from..to].fill(0xff); + } +} + +impl Default + for MemFlash +{ + fn default() -> Self { + Self::new(0xff) + } +} + +impl ErrorType + for MemFlash +{ + type Error = core::convert::Infallible; +} + +impl ReadNorFlash + for MemFlash +{ + const READ_SIZE: usize = 1; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.read(offset, bytes); + Ok(()) + } + + fn capacity(&self) -> usize { + SIZE + } +} + +impl NorFlash + for MemFlash +{ + const WRITE_SIZE: usize = WRITE_SIZE; + const ERASE_SIZE: usize = ERASE_SIZE; + + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.write(offset, bytes); + Ok(()) + } + + fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.erase(from, to); + Ok(()) + } +} + +#[cfg(feature = "nightly")] +impl AsyncReadNorFlash + for MemFlash +{ + const READ_SIZE: usize = 1; + + async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.read(offset, bytes); + Ok(()) + } + + fn capacity(&self) -> usize { + SIZE + } +} + +#[cfg(feature = "nightly")] +impl AsyncNorFlash + for MemFlash +{ + const WRITE_SIZE: usize = WRITE_SIZE; + const ERASE_SIZE: usize = ERASE_SIZE; + + async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.write(offset, bytes); + Ok(()) + } + + async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.erase(from, to); + Ok(()) + } +} diff --git a/embassy-embedded-hal/src/flash/mod.rs b/embassy-embedded-hal/src/flash/mod.rs new file mode 100644 index 000000000..7e4ef290f --- /dev/null +++ b/embassy-embedded-hal/src/flash/mod.rs @@ -0,0 +1,8 @@ +//! Utilities related to flash. + +mod concat_flash; +#[cfg(test)] +pub(crate) mod mem_flash; +pub mod partition; + +pub use concat_flash::ConcatFlash; diff --git a/embassy-embedded-hal/src/flash/partition/asynch.rs b/embassy-embedded-hal/src/flash/partition/asynch.rs new file mode 100644 index 000000000..5920436dd --- /dev/null +++ b/embassy-embedded-hal/src/flash/partition/asynch.rs @@ -0,0 +1,139 @@ +use embassy_sync::blocking_mutex::raw::RawMutex; +use embassy_sync::mutex::Mutex; +use embedded_storage::nor_flash::ErrorType; +use embedded_storage_async::nor_flash::{NorFlash, ReadNorFlash}; + +use super::Error; + +/// A logical partition of an underlying shared flash +/// +/// A partition holds an offset and a size of the flash, +/// and is restricted to operate with that range. +/// There is no guarantee that muliple partitions on the same flash +/// operate on mutually exclusive ranges - such a separation is up to +/// the user to guarantee. +pub struct Partition<'a, M: RawMutex, T: NorFlash> { + flash: &'a Mutex, + offset: u32, + size: u32, +} + +impl<'a, M: RawMutex, T: NorFlash> Partition<'a, M, T> { + /// Create a new partition + pub const fn new(flash: &'a Mutex, offset: u32, size: u32) -> Self { + if offset % T::READ_SIZE as u32 != 0 || offset % T::WRITE_SIZE as u32 != 0 || offset % T::ERASE_SIZE as u32 != 0 + { + panic!("Partition offset must be a multiple of read, write and erase size"); + } + if size % T::READ_SIZE as u32 != 0 || size % T::WRITE_SIZE as u32 != 0 || size % T::ERASE_SIZE as u32 != 0 { + panic!("Partition size must be a multiple of read, write and erase size"); + } + Self { flash, offset, size } + } + + /// Get the partition offset within the flash + pub const fn offset(&self) -> u32 { + self.offset + } + + /// Get the partition size + pub const fn size(&self) -> u32 { + self.size + } +} + +impl ErrorType for Partition<'_, M, T> { + type Error = Error; +} + +impl ReadNorFlash for Partition<'_, M, T> { + const READ_SIZE: usize = T::READ_SIZE; + + async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + if offset + bytes.len() as u32 > self.size { + return Err(Error::OutOfBounds); + } + + let mut flash = self.flash.lock().await; + flash.read(self.offset + offset, bytes).await.map_err(Error::Flash) + } + + fn capacity(&self) -> usize { + self.size as usize + } +} + +impl NorFlash for Partition<'_, M, T> { + const WRITE_SIZE: usize = T::WRITE_SIZE; + const ERASE_SIZE: usize = T::ERASE_SIZE; + + async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + if offset + bytes.len() as u32 > self.size { + return Err(Error::OutOfBounds); + } + + let mut flash = self.flash.lock().await; + flash.write(self.offset + offset, bytes).await.map_err(Error::Flash) + } + + async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + if to > self.size { + return Err(Error::OutOfBounds); + } + + let mut flash = self.flash.lock().await; + flash + .erase(self.offset + from, self.offset + to) + .await + .map_err(Error::Flash) + } +} + +#[cfg(test)] +mod tests { + use embassy_sync::blocking_mutex::raw::NoopRawMutex; + + use super::*; + use crate::flash::mem_flash::MemFlash; + + #[futures_test::test] + async fn can_read() { + let mut flash = MemFlash::<1024, 128, 4>::default(); + flash.mem[132..132 + 8].fill(0xAA); + + let flash = Mutex::::new(flash); + let mut partition = Partition::new(&flash, 128, 256); + + let mut read_buf = [0; 8]; + partition.read(4, &mut read_buf).await.unwrap(); + + assert!(read_buf.iter().position(|&x| x != 0xAA).is_none()); + } + + #[futures_test::test] + async fn can_write() { + let flash = MemFlash::<1024, 128, 4>::default(); + + let flash = Mutex::::new(flash); + let mut partition = Partition::new(&flash, 128, 256); + + let write_buf = [0xAA; 8]; + partition.write(4, &write_buf).await.unwrap(); + + let flash = flash.try_lock().unwrap(); + assert!(flash.mem[132..132 + 8].iter().position(|&x| x != 0xAA).is_none()); + } + + #[futures_test::test] + async fn can_erase() { + let flash = MemFlash::<1024, 128, 4>::new(0x00); + + let flash = Mutex::::new(flash); + let mut partition = Partition::new(&flash, 128, 256); + + partition.erase(0, 128).await.unwrap(); + + let flash = flash.try_lock().unwrap(); + assert!(flash.mem[128..256].iter().position(|&x| x != 0xFF).is_none()); + } +} diff --git a/embassy-embedded-hal/src/flash/partition/blocking.rs b/embassy-embedded-hal/src/flash/partition/blocking.rs new file mode 100644 index 000000000..2ddbe3de0 --- /dev/null +++ b/embassy-embedded-hal/src/flash/partition/blocking.rs @@ -0,0 +1,149 @@ +use core::cell::RefCell; + +use embassy_sync::blocking_mutex::raw::RawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; + +use super::Error; + +/// A logical partition of an underlying shared flash +/// +/// A partition holds an offset and a size of the flash, +/// and is restricted to operate with that range. +/// There is no guarantee that muliple partitions on the same flash +/// operate on mutually exclusive ranges - such a separation is up to +/// the user to guarantee. +pub struct BlockingPartition<'a, M: RawMutex, T: NorFlash> { + flash: &'a Mutex>, + offset: u32, + size: u32, +} + +impl<'a, M: RawMutex, T: NorFlash> BlockingPartition<'a, M, T> { + /// Create a new partition + pub const fn new(flash: &'a Mutex>, offset: u32, size: u32) -> Self { + if offset % T::READ_SIZE as u32 != 0 || offset % T::WRITE_SIZE as u32 != 0 || offset % T::ERASE_SIZE as u32 != 0 + { + panic!("Partition offset must be a multiple of read, write and erase size"); + } + if size % T::READ_SIZE as u32 != 0 || size % T::WRITE_SIZE as u32 != 0 || size % T::ERASE_SIZE as u32 != 0 { + panic!("Partition size must be a multiple of read, write and erase size"); + } + Self { flash, offset, size } + } + + /// Get the partition offset within the flash + pub const fn offset(&self) -> u32 { + self.offset + } + + /// Get the partition size + pub const fn size(&self) -> u32 { + self.size + } +} + +impl ErrorType for BlockingPartition<'_, M, T> { + type Error = Error; +} + +impl ReadNorFlash for BlockingPartition<'_, M, T> { + const READ_SIZE: usize = T::READ_SIZE; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + if offset + bytes.len() as u32 > self.size { + return Err(Error::OutOfBounds); + } + + self.flash.lock(|flash| { + flash + .borrow_mut() + .read(self.offset + offset, bytes) + .map_err(Error::Flash) + }) + } + + fn capacity(&self) -> usize { + self.size as usize + } +} + +impl NorFlash for BlockingPartition<'_, M, T> { + const WRITE_SIZE: usize = T::WRITE_SIZE; + const ERASE_SIZE: usize = T::ERASE_SIZE; + + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + if offset + bytes.len() as u32 > self.size { + return Err(Error::OutOfBounds); + } + + self.flash.lock(|flash| { + flash + .borrow_mut() + .write(self.offset + offset, bytes) + .map_err(Error::Flash) + }) + } + + fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + if to > self.size { + return Err(Error::OutOfBounds); + } + + self.flash.lock(|flash| { + flash + .borrow_mut() + .erase(self.offset + from, self.offset + to) + .map_err(Error::Flash) + }) + } +} + +#[cfg(test)] +mod tests { + use embassy_sync::blocking_mutex::raw::NoopRawMutex; + + use super::*; + use crate::flash::mem_flash::MemFlash; + + #[test] + fn can_read() { + let mut flash = MemFlash::<1024, 128, 4>::default(); + flash.mem[132..132 + 8].fill(0xAA); + + let flash = Mutex::::new(RefCell::new(flash)); + let mut partition = BlockingPartition::new(&flash, 128, 256); + + let mut read_buf = [0; 8]; + partition.read(4, &mut read_buf).unwrap(); + + assert!(read_buf.iter().position(|&x| x != 0xAA).is_none()); + } + + #[test] + fn can_write() { + let flash = MemFlash::<1024, 128, 4>::default(); + + let flash = Mutex::::new(RefCell::new(flash)); + let mut partition = BlockingPartition::new(&flash, 128, 256); + + let write_buf = [0xAA; 8]; + partition.write(4, &write_buf).unwrap(); + + let flash = flash.into_inner().take(); + assert!(flash.mem[132..132 + 8].iter().position(|&x| x != 0xAA).is_none()); + } + + #[test] + fn can_erase() { + let flash = MemFlash::<1024, 128, 4>::new(0x00); + + let flash = Mutex::::new(RefCell::new(flash)); + let mut partition = BlockingPartition::new(&flash, 128, 256); + + partition.erase(0, 128).unwrap(); + + let flash = flash.into_inner().take(); + assert!(flash.mem[128..256].iter().position(|&x| x != 0xFF).is_none()); + } +} diff --git a/embassy-embedded-hal/src/flash/partition/mod.rs b/embassy-embedded-hal/src/flash/partition/mod.rs new file mode 100644 index 000000000..a12e49ce1 --- /dev/null +++ b/embassy-embedded-hal/src/flash/partition/mod.rs @@ -0,0 +1,30 @@ +//! Flash Partition utilities + +use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind}; + +#[cfg(feature = "nightly")] +mod asynch; +mod blocking; + +#[cfg(feature = "nightly")] +pub use asynch::Partition; +pub use blocking::BlockingPartition; + +/// Partition error +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// The requested flash area is outside the partition + OutOfBounds, + /// Underlying flash error + Flash(T), +} + +impl NorFlashError for Error { + fn kind(&self) -> NorFlashErrorKind { + match self { + Error::OutOfBounds => NorFlashErrorKind::OutOfBounds, + Error::Flash(f) => f.kind(), + } + } +} diff --git a/embassy-embedded-hal/src/lib.rs b/embassy-embedded-hal/src/lib.rs index 0c6f2786a..3aad838bd 100644 --- a/embassy-embedded-hal/src/lib.rs +++ b/embassy-embedded-hal/src/lib.rs @@ -1,5 +1,5 @@ #![cfg_attr(not(feature = "std"), no_std)] -#![cfg_attr(feature = "nightly", feature(generic_associated_types, type_alias_impl_trait))] +#![cfg_attr(feature = "nightly", feature(async_fn_in_trait, impl_trait_projections, try_blocks))] #![warn(missing_docs)] //! Utilities to use `embedded-hal` traits with Embassy. @@ -7,6 +7,8 @@ #[cfg(feature = "nightly")] pub mod adapter; +pub mod flash; + pub mod shared_bus; /// Set the configuration of a peripheral driver. diff --git a/embassy-embedded-hal/src/shared_bus/asynch/i2c.rs b/embassy-embedded-hal/src/shared_bus/asynch/i2c.rs index 0bc6afd98..87e8a4304 100644 --- a/embassy-embedded-hal/src/shared_bus/asynch/i2c.rs +++ b/embassy-embedded-hal/src/shared_bus/asynch/i2c.rs @@ -22,7 +22,6 @@ //! let i2c_dev2 = I2cDevice::new(i2c_bus); //! let mpu = Mpu6050::new(i2c_dev2); //! ``` -use core::future::Future; use embassy_sync::blocking_mutex::raw::RawMutex; use embassy_sync::mutex::Mutex; @@ -55,53 +54,41 @@ where M: RawMutex + 'static, BUS: i2c::I2c + 'static, { - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn read<'a>(&'a mut self, address: u8, buffer: &'a mut [u8]) -> Self::ReadFuture<'a> { - async move { - let mut bus = self.bus.lock().await; - bus.read(address, buffer).await.map_err(I2cDeviceError::I2c)?; - Ok(()) - } + async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), I2cDeviceError> { + let mut bus = self.bus.lock().await; + bus.read(address, read).await.map_err(I2cDeviceError::I2c)?; + Ok(()) } - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - - fn write<'a>(&'a mut self, address: u8, bytes: &'a [u8]) -> Self::WriteFuture<'a> { - async move { - let mut bus = self.bus.lock().await; - bus.write(address, bytes).await.map_err(I2cDeviceError::I2c)?; - Ok(()) - } + async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), I2cDeviceError> { + let mut bus = self.bus.lock().await; + bus.write(address, write).await.map_err(I2cDeviceError::I2c)?; + Ok(()) } - type WriteReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn write_read<'a>( - &'a mut self, + async fn write_read( + &mut self, address: u8, - wr_buffer: &'a [u8], - rd_buffer: &'a mut [u8], - ) -> Self::WriteReadFuture<'a> { - async move { - let mut bus = self.bus.lock().await; - bus.write_read(address, wr_buffer, rd_buffer) - .await - .map_err(I2cDeviceError::I2c)?; - Ok(()) - } + write: &[u8], + read: &mut [u8], + ) -> Result<(), I2cDeviceError> { + let mut bus = self.bus.lock().await; + bus.write_read(address, write, read) + .await + .map_err(I2cDeviceError::I2c)?; + Ok(()) } - type TransactionFuture<'a, 'b> = impl Future> + 'a where Self: 'a, 'b: 'a; - - fn transaction<'a, 'b>( - &'a mut self, + async fn transaction( + &mut self, address: u8, - operations: &'a mut [embedded_hal_async::i2c::Operation<'b>], - ) -> Self::TransactionFuture<'a, 'b> { - let _ = address; - let _ = operations; - async move { todo!() } + operations: &mut [embedded_hal_async::i2c::Operation<'_>], + ) -> Result<(), I2cDeviceError> { + let mut bus = self.bus.lock().await; + bus.transaction(address, operations) + .await + .map_err(I2cDeviceError::I2c)?; + Ok(()) } } @@ -136,55 +123,40 @@ where M: RawMutex + 'static, BUS: i2c::I2c + SetConfig + 'static, { - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn read<'a>(&'a mut self, address: u8, buffer: &'a mut [u8]) -> Self::ReadFuture<'a> { - async move { - let mut bus = self.bus.lock().await; - bus.set_config(&self.config); - bus.read(address, buffer).await.map_err(I2cDeviceError::I2c)?; - Ok(()) - } + async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), I2cDeviceError> { + let mut bus = self.bus.lock().await; + bus.set_config(&self.config); + bus.read(address, buffer).await.map_err(I2cDeviceError::I2c)?; + Ok(()) } - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - - fn write<'a>(&'a mut self, address: u8, bytes: &'a [u8]) -> Self::WriteFuture<'a> { - async move { - let mut bus = self.bus.lock().await; - bus.set_config(&self.config); - bus.write(address, bytes).await.map_err(I2cDeviceError::I2c)?; - Ok(()) - } + async fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), I2cDeviceError> { + let mut bus = self.bus.lock().await; + bus.set_config(&self.config); + bus.write(address, bytes).await.map_err(I2cDeviceError::I2c)?; + Ok(()) } - type WriteReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn write_read<'a>( - &'a mut self, + async fn write_read( + &mut self, address: u8, - wr_buffer: &'a [u8], - rd_buffer: &'a mut [u8], - ) -> Self::WriteReadFuture<'a> { - async move { - let mut bus = self.bus.lock().await; - bus.set_config(&self.config); - bus.write_read(address, wr_buffer, rd_buffer) - .await - .map_err(I2cDeviceError::I2c)?; - Ok(()) - } + wr_buffer: &[u8], + rd_buffer: &mut [u8], + ) -> Result<(), I2cDeviceError> { + let mut bus = self.bus.lock().await; + bus.set_config(&self.config); + bus.write_read(address, wr_buffer, rd_buffer) + .await + .map_err(I2cDeviceError::I2c)?; + Ok(()) } - type TransactionFuture<'a, 'b> = impl Future> + 'a where Self: 'a, 'b: 'a; - - fn transaction<'a, 'b>( - &'a mut self, - address: u8, - operations: &'a mut [embedded_hal_async::i2c::Operation<'b>], - ) -> Self::TransactionFuture<'a, 'b> { - let _ = address; - let _ = operations; - async move { todo!() } + async fn transaction(&mut self, address: u8, operations: &mut [i2c::Operation<'_>]) -> Result<(), Self::Error> { + let mut bus = self.bus.lock().await; + bus.set_config(&self.config); + bus.transaction(address, operations) + .await + .map_err(I2cDeviceError::I2c)?; + Ok(()) } } diff --git a/embassy-embedded-hal/src/shared_bus/asynch/spi.rs b/embassy-embedded-hal/src/shared_bus/asynch/spi.rs index c95b59ef0..030392183 100644 --- a/embassy-embedded-hal/src/shared_bus/asynch/spi.rs +++ b/embassy-embedded-hal/src/shared_bus/asynch/spi.rs @@ -25,12 +25,11 @@ //! let spi_dev2 = SpiDevice::new(spi_bus, cs_pin2); //! let display2 = ST7735::new(spi_dev2, dc2, rst2, Default::default(), 160, 128); //! ``` -use core::future::Future; use embassy_sync::blocking_mutex::raw::RawMutex; use embassy_sync::mutex::Mutex; -use embedded_hal_1::digital::blocking::OutputPin; -use embedded_hal_1::spi::ErrorType; +use embedded_hal_1::digital::OutputPin; +use embedded_hal_1::spi::Operation; use embedded_hal_async::spi; use crate::shared_bus::SpiDeviceError; @@ -59,39 +58,40 @@ where impl spi::SpiDevice for SpiDevice<'_, M, BUS, CS> where - M: RawMutex + 'static, - BUS: spi::SpiBusFlush + 'static, + M: RawMutex, + BUS: spi::SpiBus, CS: OutputPin, { - type Bus = BUS; + async fn transaction(&mut self, operations: &mut [spi::Operation<'_, u8>]) -> Result<(), Self::Error> { + let mut bus = self.bus.lock().await; + self.cs.set_low().map_err(SpiDeviceError::Cs)?; - type TransactionFuture<'a, R, F, Fut> = impl Future> + 'a - where - Self: 'a, R: 'a, F: FnOnce(*mut Self::Bus) -> Fut + 'a, - Fut: Future::Error>> + 'a; + let op_res: Result<(), BUS::Error> = try { + for op in operations { + match op { + Operation::Read(buf) => bus.read(buf).await?, + Operation::Write(buf) => bus.write(buf).await?, + Operation::Transfer(read, write) => bus.transfer(read, write).await?, + Operation::TransferInPlace(buf) => bus.transfer_in_place(buf).await?, + #[cfg(not(feature = "time"))] + Operation::DelayUs(_) => return Err(SpiDeviceError::DelayUsNotSupported), + #[cfg(feature = "time")] + Operation::DelayUs(us) => { + embassy_time::Timer::after(embassy_time::Duration::from_micros(*us as _)).await + } + } + } + }; - fn transaction<'a, R, F, Fut>(&'a mut self, f: F) -> Self::TransactionFuture<'a, R, F, Fut> - where - R: 'a, - F: FnOnce(*mut Self::Bus) -> Fut + 'a, - Fut: Future::Error>> + 'a, - { - async move { - let mut bus = self.bus.lock().await; - self.cs.set_low().map_err(SpiDeviceError::Cs)?; + // On failure, it's important to still flush and deassert CS. + let flush_res = bus.flush().await; + let cs_res = self.cs.set_high(); - let f_res = f(&mut *bus).await; + let op_res = op_res.map_err(SpiDeviceError::Spi)?; + flush_res.map_err(SpiDeviceError::Spi)?; + cs_res.map_err(SpiDeviceError::Cs)?; - // On failure, it's important to still flush and deassert CS. - let flush_res = bus.flush().await; - let cs_res = self.cs.set_high(); - - let f_res = f_res.map_err(SpiDeviceError::Spi)?; - flush_res.map_err(SpiDeviceError::Spi)?; - cs_res.map_err(SpiDeviceError::Cs)?; - - Ok(f_res) - } + Ok(op_res) } } @@ -124,39 +124,40 @@ where impl spi::SpiDevice for SpiDeviceWithConfig<'_, M, BUS, CS> where - M: RawMutex + 'static, - BUS: spi::SpiBusFlush + SetConfig + 'static, + M: RawMutex, + BUS: spi::SpiBus + SetConfig, CS: OutputPin, { - type Bus = BUS; + async fn transaction(&mut self, operations: &mut [spi::Operation<'_, u8>]) -> Result<(), Self::Error> { + let mut bus = self.bus.lock().await; + bus.set_config(&self.config); + self.cs.set_low().map_err(SpiDeviceError::Cs)?; - type TransactionFuture<'a, R, F, Fut> = impl Future> + 'a - where - Self: 'a, R: 'a, F: FnOnce(*mut Self::Bus) -> Fut + 'a, - Fut: Future::Error>> + 'a; + let op_res: Result<(), BUS::Error> = try { + for op in operations { + match op { + Operation::Read(buf) => bus.read(buf).await?, + Operation::Write(buf) => bus.write(buf).await?, + Operation::Transfer(read, write) => bus.transfer(read, write).await?, + Operation::TransferInPlace(buf) => bus.transfer_in_place(buf).await?, + #[cfg(not(feature = "time"))] + Operation::DelayUs(_) => return Err(SpiDeviceError::DelayUsNotSupported), + #[cfg(feature = "time")] + Operation::DelayUs(us) => { + embassy_time::Timer::after(embassy_time::Duration::from_micros(*us as _)).await + } + } + } + }; - fn transaction<'a, R, F, Fut>(&'a mut self, f: F) -> Self::TransactionFuture<'a, R, F, Fut> - where - R: 'a, - F: FnOnce(*mut Self::Bus) -> Fut + 'a, - Fut: Future::Error>> + 'a, - { - async move { - let mut bus = self.bus.lock().await; - bus.set_config(&self.config); - self.cs.set_low().map_err(SpiDeviceError::Cs)?; + // On failure, it's important to still flush and deassert CS. + let flush_res = bus.flush().await; + let cs_res = self.cs.set_high(); - let f_res = f(&mut *bus).await; + let op_res = op_res.map_err(SpiDeviceError::Spi)?; + flush_res.map_err(SpiDeviceError::Spi)?; + cs_res.map_err(SpiDeviceError::Cs)?; - // On failure, it's important to still flush and deassert CS. - let flush_res = bus.flush().await; - let cs_res = self.cs.set_high(); - - let f_res = f_res.map_err(SpiDeviceError::Spi)?; - flush_res.map_err(SpiDeviceError::Spi)?; - cs_res.map_err(SpiDeviceError::Cs)?; - - Ok(f_res) - } + Ok(op_res) } } diff --git a/embassy-embedded-hal/src/shared_bus/blocking/i2c.rs b/embassy-embedded-hal/src/shared_bus/blocking/i2c.rs index a611e2d27..af73df059 100644 --- a/embassy-embedded-hal/src/shared_bus/blocking/i2c.rs +++ b/embassy-embedded-hal/src/shared_bus/blocking/i2c.rs @@ -2,13 +2,12 @@ //! //! # Example (nrf52) //! -//! ```rust +//! ```rust,ignore //! use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice; //! use embassy_sync::blocking_mutex::{NoopMutex, raw::NoopRawMutex}; //! //! static I2C_BUS: StaticCell>>> = StaticCell::new(); -//! let irq = interrupt::take!(SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); -//! let i2c = Twim::new(p.TWISPI0, irq, p.P0_03, p.P0_04, Config::default()); +//! let i2c = Twim::new(p.TWISPI0, Irqs, p.P0_03, p.P0_04, Config::default()); //! let i2c_bus = NoopMutex::new(RefCell::new(i2c)); //! let i2c_bus = I2C_BUS.init(i2c_bus); //! @@ -20,8 +19,7 @@ use core::cell::RefCell; use embassy_sync::blocking_mutex::raw::RawMutex; use embassy_sync::blocking_mutex::Mutex; -use embedded_hal_1::i2c::blocking::{I2c, Operation}; -use embedded_hal_1::i2c::ErrorType; +use embedded_hal_1::i2c::{ErrorType, I2c, Operation}; use crate::shared_bus::I2cDeviceError; use crate::SetConfig; @@ -73,34 +71,6 @@ where let _ = operations; todo!() } - - fn write_iter>(&mut self, addr: u8, bytes: B) -> Result<(), Self::Error> { - let _ = addr; - let _ = bytes; - todo!() - } - - fn write_iter_read>( - &mut self, - addr: u8, - bytes: B, - buffer: &mut [u8], - ) -> Result<(), Self::Error> { - let _ = addr; - let _ = bytes; - let _ = buffer; - todo!() - } - - fn transaction_iter<'a, O: IntoIterator>>( - &mut self, - address: u8, - operations: O, - ) -> Result<(), Self::Error> { - let _ = address; - let _ = operations; - todo!() - } } impl<'a, M, BUS, E> embedded_hal_02::blocking::i2c::Write for I2cDevice<'_, M, BUS> @@ -205,32 +175,4 @@ where let _ = operations; todo!() } - - fn write_iter>(&mut self, addr: u8, bytes: B) -> Result<(), Self::Error> { - let _ = addr; - let _ = bytes; - todo!() - } - - fn write_iter_read>( - &mut self, - addr: u8, - bytes: B, - buffer: &mut [u8], - ) -> Result<(), Self::Error> { - let _ = addr; - let _ = bytes; - let _ = buffer; - todo!() - } - - fn transaction_iter<'a, O: IntoIterator>>( - &mut self, - address: u8, - operations: O, - ) -> Result<(), Self::Error> { - let _ = address; - let _ = operations; - todo!() - } } diff --git a/embassy-embedded-hal/src/shared_bus/blocking/spi.rs b/embassy-embedded-hal/src/shared_bus/blocking/spi.rs index 23845d887..6d03d6263 100644 --- a/embassy-embedded-hal/src/shared_bus/blocking/spi.rs +++ b/embassy-embedded-hal/src/shared_bus/blocking/spi.rs @@ -2,13 +2,12 @@ //! //! # Example (nrf52) //! -//! ```rust +//! ```rust,ignore //! use embassy_embedded_hal::shared_bus::blocking::spi::SpiDevice; //! use embassy_sync::blocking_mutex::{NoopMutex, raw::NoopRawMutex}; //! //! static SPI_BUS: StaticCell>>> = StaticCell::new(); -//! let irq = interrupt::take!(SPIM3); -//! let spi = Spim::new_txonly(p.SPI3, irq, p.P0_15, p.P0_18, Config::default()); +//! let spi = Spim::new_txonly(p.SPI3, Irqs, p.P0_15, p.P0_18, Config::default()); //! let spi_bus = NoopMutex::new(RefCell::new(spi)); //! let spi_bus = SPI_BUS.init(spi_bus); //! @@ -22,9 +21,8 @@ use core::cell::RefCell; use embassy_sync::blocking_mutex::raw::RawMutex; use embassy_sync::blocking_mutex::Mutex; -use embedded_hal_1::digital::blocking::OutputPin; -use embedded_hal_1::spi; -use embedded_hal_1::spi::blocking::SpiBusFlush; +use embedded_hal_1::digital::OutputPin; +use embedded_hal_1::spi::{self, Operation, SpiBus}; use crate::shared_bus::SpiDeviceError; use crate::SetConfig; @@ -50,30 +48,40 @@ where type Error = SpiDeviceError; } -impl embedded_hal_1::spi::blocking::SpiDevice for SpiDevice<'_, M, BUS, CS> +impl embedded_hal_1::spi::SpiDevice for SpiDevice<'_, M, BUS, CS> where M: RawMutex, - BUS: SpiBusFlush, + BUS: SpiBus, CS: OutputPin, { - type Bus = BUS; - - fn transaction(&mut self, f: impl FnOnce(&mut Self::Bus) -> Result) -> Result { + fn transaction(&mut self, operations: &mut [Operation<'_, u8>]) -> Result<(), Self::Error> { self.bus.lock(|bus| { let mut bus = bus.borrow_mut(); self.cs.set_low().map_err(SpiDeviceError::Cs)?; - let f_res = f(&mut bus); + let op_res = operations.iter_mut().try_for_each(|op| match op { + Operation::Read(buf) => bus.read(buf), + Operation::Write(buf) => bus.write(buf), + Operation::Transfer(read, write) => bus.transfer(read, write), + Operation::TransferInPlace(buf) => bus.transfer_in_place(buf), + #[cfg(not(feature = "time"))] + Operation::DelayUs(_) => Err(SpiDeviceError::DelayUsNotSupported), + #[cfg(feature = "time")] + Operation::DelayUs(us) => { + embassy_time::block_for(embassy_time::Duration::from_micros(*us as _)); + Ok(()) + } + }); // On failure, it's important to still flush and deassert CS. let flush_res = bus.flush(); let cs_res = self.cs.set_high(); - let f_res = f_res.map_err(SpiDeviceError::Spi)?; + let op_res = op_res.map_err(SpiDeviceError::Spi)?; flush_res.map_err(SpiDeviceError::Spi)?; cs_res.map_err(SpiDeviceError::Cs)?; - Ok(f_res) + Ok(op_res) }) } } @@ -89,11 +97,11 @@ where self.bus.lock(|bus| { let mut bus = bus.borrow_mut(); self.cs.set_low().map_err(SpiDeviceError::Cs)?; - let f_res = bus.transfer(words); + let op_res = bus.transfer(words); let cs_res = self.cs.set_high(); - let f_res = f_res.map_err(SpiDeviceError::Spi)?; + let op_res = op_res.map_err(SpiDeviceError::Spi)?; cs_res.map_err(SpiDeviceError::Cs)?; - Ok(f_res) + Ok(op_res) }) } } @@ -110,11 +118,11 @@ where self.bus.lock(|bus| { let mut bus = bus.borrow_mut(); self.cs.set_low().map_err(SpiDeviceError::Cs)?; - let f_res = bus.write(words); + let op_res = bus.write(words); let cs_res = self.cs.set_high(); - let f_res = f_res.map_err(SpiDeviceError::Spi)?; + let op_res = op_res.map_err(SpiDeviceError::Spi)?; cs_res.map_err(SpiDeviceError::Cs)?; - Ok(f_res) + Ok(op_res) }) } } @@ -146,30 +154,40 @@ where type Error = SpiDeviceError; } -impl embedded_hal_1::spi::blocking::SpiDevice for SpiDeviceWithConfig<'_, M, BUS, CS> +impl embedded_hal_1::spi::SpiDevice for SpiDeviceWithConfig<'_, M, BUS, CS> where M: RawMutex, - BUS: SpiBusFlush + SetConfig, + BUS: SpiBus + SetConfig, CS: OutputPin, { - type Bus = BUS; - - fn transaction(&mut self, f: impl FnOnce(&mut Self::Bus) -> Result) -> Result { + fn transaction(&mut self, operations: &mut [Operation<'_, u8>]) -> Result<(), Self::Error> { self.bus.lock(|bus| { let mut bus = bus.borrow_mut(); bus.set_config(&self.config); self.cs.set_low().map_err(SpiDeviceError::Cs)?; - let f_res = f(&mut bus); + let op_res = operations.iter_mut().try_for_each(|op| match op { + Operation::Read(buf) => bus.read(buf), + Operation::Write(buf) => bus.write(buf), + Operation::Transfer(read, write) => bus.transfer(read, write), + Operation::TransferInPlace(buf) => bus.transfer_in_place(buf), + #[cfg(not(feature = "time"))] + Operation::DelayUs(_) => Err(SpiDeviceError::DelayUsNotSupported), + #[cfg(feature = "time")] + Operation::DelayUs(us) => { + embassy_time::block_for(embassy_time::Duration::from_micros(*us as _)); + Ok(()) + } + }); // On failure, it's important to still flush and deassert CS. let flush_res = bus.flush(); let cs_res = self.cs.set_high(); - let f_res = f_res.map_err(SpiDeviceError::Spi)?; + let op_res = op_res.map_err(SpiDeviceError::Spi)?; flush_res.map_err(SpiDeviceError::Spi)?; cs_res.map_err(SpiDeviceError::Cs)?; - Ok(f_res) + Ok(op_res) }) } } diff --git a/embassy-embedded-hal/src/shared_bus/mod.rs b/embassy-embedded-hal/src/shared_bus/mod.rs index 617d921e9..79a90bd52 100644 --- a/embassy-embedded-hal/src/shared_bus/mod.rs +++ b/embassy-embedded-hal/src/shared_bus/mod.rs @@ -30,11 +30,14 @@ where /// Error returned by SPI device implementations in this crate. #[derive(Copy, Clone, Eq, PartialEq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] pub enum SpiDeviceError { /// An operation on the inner SPI bus failed. Spi(BUS), /// Setting the value of the Chip Select (CS) pin failed. Cs(CS), + /// DelayUs operations are not supported when the `time` Cargo feature is not enabled. + DelayUsNotSupported, } impl spi::Error for SpiDeviceError @@ -46,6 +49,7 @@ where match self { Self::Spi(e) => e.kind(), Self::Cs(_) => spi::ErrorKind::Other, + Self::DelayUsNotSupported => spi::ErrorKind::Other, } } } diff --git a/embassy-executor/CHANGELOG.md b/embassy-executor/CHANGELOG.md new file mode 100644 index 000000000..4fd3dccf7 --- /dev/null +++ b/embassy-executor/CHANGELOG.md @@ -0,0 +1,23 @@ +# Changelog + +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). + +## 0.2.0 - 2023-04-27 + +- Replace unnecessary atomics in runqueue +- add Pender, rework Cargo features. +- add support for turbo-wakers. +- Allow TaskStorage to auto-implement `Sync` +- Use AtomicPtr for signal_ctx, removes 1 unsafe. +- Replace unsound critical sections with atomics + +## 0.1.1 - 2022-11-23 + +- Fix features for documentation + +## 0.1.0 - 2022-11-23 + +- First release diff --git a/embassy-executor/Cargo.toml b/embassy-executor/Cargo.toml index 1a611720c..590718e3e 100644 --- a/embassy-executor/Cargo.toml +++ b/embassy-executor/Cargo.toml @@ -1,37 +1,59 @@ [package] name = "embassy-executor" -version = "0.1.0" +version = "0.2.0" edition = "2021" - +license = "MIT OR Apache-2.0" +description = "async/await executor designed for embedded usage" +repository = "https://github.com/embassy-rs/embassy" +categories = [ + "embedded", + "no-std", + "asynchronous", +] [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-executor-v$VERSION/embassy-executor/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-executor/src/" -features = ["nightly", "defmt", "unstable-traits"] +features = ["nightly", "defmt", "pender-callback"] flavors = [ - { name = "std", target = "x86_64-unknown-linux-gnu", features = ["std"] }, - { name = "wasm", target = "wasm32-unknown-unknown", features = ["wasm"] }, - { name = "thumbv6m-none-eabi", target = "thumbv6m-none-eabi", features = [] }, - { name = "thumbv7m-none-eabi", target = "thumbv7m-none-eabi", features = [] }, - { name = "thumbv7em-none-eabi", target = "thumbv7em-none-eabi", features = [] }, - { name = "thumbv7em-none-eabihf", target = "thumbv7em-none-eabihf", features = [] }, - { name = "thumbv8m.base-none-eabi", target = "thumbv8m.base-none-eabi", features = [] }, - { name = "thumbv8m.main-none-eabi", target = "thumbv8m.main-none-eabi", features = [] }, - { name = "thumbv8m.main-none-eabihf", target = "thumbv8m.main-none-eabihf", features = [] }, + { name = "std", target = "x86_64-unknown-linux-gnu", features = ["arch-std", "executor-thread"] }, + { name = "wasm", target = "wasm32-unknown-unknown", features = ["arch-wasm", "executor-thread"] }, + { name = "cortex-m", target = "thumbv7em-none-eabi", features = ["arch-cortex-m", "executor-thread", "executor-interrupt"] }, + { name = "riscv32", target = "riscv32imac-unknown-none-elf", features = ["arch-riscv32", "executor-thread"] }, ] +[package.metadata.docs.rs] +default-target = "thumbv7em-none-eabi" +targets = ["thumbv7em-none-eabi"] +features = ["nightly", "defmt", "pender-callback", "arch-cortex-m", "executor-thread", "executor-interrupt"] + [features] -default = [] -std = ["embassy-macros/std"] -wasm = ["dep:wasm-bindgen", "dep:js-sys", "embassy-macros/wasm"] + +# Architecture +_arch = [] # some arch was picked +arch-std = ["_arch", "critical-section/std"] +arch-cortex-m = ["_arch", "dep:cortex-m"] +arch-xtensa = ["_arch"] +arch-riscv32 = ["_arch"] +arch-wasm = ["_arch", "dep:wasm-bindgen", "dep:js-sys"] + +# Enable creating a `Pender` from an arbitrary function pointer callback. +pender-callback = [] + +# Enable the thread-mode executor (using WFE/SEV in Cortex-M, WFI in other embedded archs) +executor-thread = [] +# Enable the interrupt-mode executor (available in Cortex-M only) +executor-interrupt = [] # Enable nightly-only features nightly = [] +turbowakers = [] + integrated-timers = ["dep:embassy-time"] # Trace interrupt invocations with rtos-trace. -rtos-trace-interrupt = ["rtos-trace"] +rtos-trace-interrupt = ["rtos-trace", "embassy-macros/rtos-trace-interrupt"] [dependencies] defmt = { version = "0.3", optional = true } @@ -39,13 +61,15 @@ log = { version = "0.4.14", optional = true } rtos-trace = { version = "0.1.2", optional = true } futures-util = { version = "0.3.17", default-features = false } -embassy-macros = { version = "0.1.0", path = "../embassy-macros"} -embassy-time = { version = "0.1.0", path = "../embassy-time", optional = true} +embassy-macros = { version = "0.2.0", path = "../embassy-macros" } +embassy-time = { version = "0.1.2", path = "../embassy-time", optional = true} atomic-polyfill = "1.0.1" critical-section = "1.1" -cfg-if = "1.0.0" -static_cell = "1.0" +static_cell = "1.1" -# WASM dependencies +# arch-cortex-m dependencies +cortex-m = { version = "0.7.6", optional = true } + +# arch-wasm dependencies wasm-bindgen = { version = "0.2.82", optional = true } js-sys = { version = "0.3", optional = true } diff --git a/embassy-executor/src/arch/cortex_m.rs b/embassy-executor/src/arch/cortex_m.rs index 4b27a264e..94c8134d6 100644 --- a/embassy-executor/src/arch/cortex_m.rs +++ b/embassy-executor/src/arch/cortex_m.rs @@ -1,59 +1,224 @@ -use core::arch::asm; -use core::marker::PhantomData; -use core::ptr; +#[cfg(feature = "executor-thread")] +pub use thread::*; +#[cfg(feature = "executor-thread")] +mod thread { + use core::arch::asm; + use core::marker::PhantomData; -use super::{raw, Spawner}; + #[cfg(feature = "nightly")] + pub use embassy_macros::main_cortex_m as main; -/// Thread mode executor, using WFE/SEV. -/// -/// This is the simplest and most common kind of executor. It runs on -/// thread mode (at the lowest priority level), and uses the `WFE` ARM instruction -/// to sleep when it has no more work to do. When a task is woken, a `SEV` instruction -/// is executed, to make the `WFE` exit from sleep and poll the task. -/// -/// This executor allows for ultra low power consumption for chips where `WFE` -/// triggers low-power sleep without extra steps. If your chip requires extra steps, -/// you may use [`raw::Executor`] directly to program custom behavior. -pub struct Executor { - inner: raw::Executor, - not_send: PhantomData<*mut ()>, -} + use crate::raw::{Pender, PenderInner}; + use crate::{raw, Spawner}; -impl Executor { - /// Create a new Executor. - pub fn new() -> Self { - Self { - inner: raw::Executor::new(|_| unsafe { asm!("sev") }, ptr::null_mut()), - not_send: PhantomData, + #[derive(Copy, Clone)] + pub(crate) struct ThreadPender; + + impl ThreadPender { + pub(crate) fn pend(self) { + unsafe { core::arch::asm!("sev") } } } - /// Run the executor. + /// Thread mode executor, using WFE/SEV. /// - /// The `init` closure is called with a [`Spawner`] that spawns tasks on - /// this executor. Use it to spawn the initial task(s). After `init` returns, - /// the executor starts running the tasks. + /// This is the simplest and most common kind of executor. It runs on + /// thread mode (at the lowest priority level), and uses the `WFE` ARM instruction + /// to sleep when it has no more work to do. When a task is woken, a `SEV` instruction + /// is executed, to make the `WFE` exit from sleep and poll the task. /// - /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), - /// for example by passing it as an argument to the initial tasks. - /// - /// This function requires `&'static mut self`. This means you have to store the - /// Executor instance in a place where it'll live forever and grants you mutable - /// access. There's a few ways to do this: - /// - /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe) - /// - a `static mut` (unsafe) - /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) - /// - /// This function never returns. - pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { - init(self.inner.spawner()); + /// This executor allows for ultra low power consumption for chips where `WFE` + /// triggers low-power sleep without extra steps. If your chip requires extra steps, + /// you may use [`raw::Executor`] directly to program custom behavior. + pub struct Executor { + inner: raw::Executor, + not_send: PhantomData<*mut ()>, + } + + impl Executor { + /// Create a new Executor. + pub fn new() -> Self { + Self { + inner: raw::Executor::new(Pender(PenderInner::Thread(ThreadPender))), + not_send: PhantomData, + } + } + + /// Run the executor. + /// + /// The `init` closure is called with a [`Spawner`] that spawns tasks on + /// this executor. Use it to spawn the initial task(s). After `init` returns, + /// the executor starts running the tasks. + /// + /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), + /// for example by passing it as an argument to the initial tasks. + /// + /// This function requires `&'static mut self`. This means you have to store the + /// Executor instance in a place where it'll live forever and grants you mutable + /// access. There's a few ways to do this: + /// + /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe) + /// - a `static mut` (unsafe) + /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) + /// + /// This function never returns. + pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { + init(self.inner.spawner()); + + loop { + unsafe { + self.inner.poll(); + asm!("wfe"); + }; + } + } + } +} + +#[cfg(feature = "executor-interrupt")] +pub use interrupt::*; +#[cfg(feature = "executor-interrupt")] +mod interrupt { + use core::cell::UnsafeCell; + use core::mem::MaybeUninit; + + use atomic_polyfill::{AtomicBool, Ordering}; + use cortex_m::interrupt::InterruptNumber; + use cortex_m::peripheral::NVIC; + + use crate::raw::{self, Pender, PenderInner}; + + #[derive(Clone, Copy)] + pub(crate) struct InterruptPender(u16); + + impl InterruptPender { + pub(crate) fn pend(self) { + // STIR is faster, but is only available in v7 and higher. + #[cfg(not(armv6m))] + { + let mut nvic: cortex_m::peripheral::NVIC = unsafe { core::mem::transmute(()) }; + nvic.request(self); + } + + #[cfg(armv6m)] + cortex_m::peripheral::NVIC::pend(self); + } + } + + unsafe impl cortex_m::interrupt::InterruptNumber for InterruptPender { + fn number(self) -> u16 { + self.0 + } + } + + /// Interrupt mode executor. + /// + /// This executor runs tasks in interrupt mode. The interrupt handler is set up + /// to poll tasks, and when a task is woken the interrupt is pended from software. + /// + /// This allows running async tasks at a priority higher than thread mode. One + /// use case is to leave thread mode free for non-async tasks. Another use case is + /// to run multiple executors: one in thread mode for low priority tasks and another in + /// interrupt mode for higher priority tasks. Higher priority tasks will preempt lower + /// priority ones. + /// + /// It is even possible to run multiple interrupt mode executors at different priorities, + /// by assigning different priorities to the interrupts. For an example on how to do this, + /// See the 'multiprio' example for 'embassy-nrf'. + /// + /// To use it, you have to pick an interrupt that won't be used by the hardware. + /// Some chips reserve some interrupts for this purpose, sometimes named "software interrupts" (SWI). + /// If this is not the case, you may use an interrupt from any unused peripheral. + /// + /// It is somewhat more complex to use, it's recommended to use the thread-mode + /// [`Executor`] instead, if it works for your use case. + pub struct InterruptExecutor { + started: AtomicBool, + executor: UnsafeCell>, + } + + unsafe impl Send for InterruptExecutor {} + unsafe impl Sync for InterruptExecutor {} + + impl InterruptExecutor { + /// Create a new, not started `InterruptExecutor`. + #[inline] + pub const fn new() -> Self { + Self { + started: AtomicBool::new(false), + executor: UnsafeCell::new(MaybeUninit::uninit()), + } + } + + /// Executor interrupt callback. + /// + /// # Safety + /// + /// You MUST call this from the interrupt handler, and from nowhere else. + pub unsafe fn on_interrupt(&'static self) { + let executor = unsafe { (&*self.executor.get()).assume_init_ref() }; + executor.poll(); + } + + /// Start the executor. + /// + /// This initializes the executor, enables the interrupt, and returns. + /// The executor keeps running in the background through the interrupt. + /// + /// This returns a [`SendSpawner`] you can use to spawn tasks on it. A [`SendSpawner`] + /// is returned instead of a [`Spawner`](embassy_executor::Spawner) because the executor effectively runs in a + /// different "thread" (the interrupt), so spawning tasks on it is effectively + /// sending them. + /// + /// To obtain a [`Spawner`](embassy_executor::Spawner) for this executor, use [`Spawner::for_current_executor()`](embassy_executor::Spawner::for_current_executor()) from + /// a task running in it. + /// + /// # Interrupt requirements + /// + /// You must write the interrupt handler yourself, and make it call [`on_interrupt()`](Self::on_interrupt). + /// + /// This method already enables (unmasks) the interrupt, you must NOT do it yourself. + /// + /// You must set the interrupt priority before calling this method. You MUST NOT + /// do it after. + /// + pub fn start(&'static self, irq: impl InterruptNumber) -> crate::SendSpawner { + if self + .started + .compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed) + .is_err() + { + panic!("InterruptExecutor::start() called multiple times on the same executor."); + } - loop { unsafe { - self.inner.poll(); - asm!("wfe"); - }; + (&mut *self.executor.get()) + .as_mut_ptr() + .write(raw::Executor::new(Pender(PenderInner::Interrupt(InterruptPender( + irq.number(), + ))))) + } + + let executor = unsafe { (&*self.executor.get()).assume_init_ref() }; + + unsafe { NVIC::unmask(irq) } + + executor.spawner().make_send() + } + + /// Get a SendSpawner for this executor + /// + /// This returns a [`SendSpawner`] you can use to spawn tasks on this + /// executor. + /// + /// This MUST only be called on an executor that has already been spawned. + /// The function will panic otherwise. + pub fn spawner(&'static self) -> crate::SendSpawner { + if !self.started.load(Ordering::Acquire) { + panic!("InterruptExecutor::spawner() called on uninitialized executor."); + } + let executor = unsafe { (&*self.executor.get()).assume_init_ref() }; + executor.spawner().make_send() } } } diff --git a/embassy-executor/src/arch/riscv32.rs b/embassy-executor/src/arch/riscv32.rs index 2a4b006da..ff7ec1575 100644 --- a/embassy-executor/src/arch/riscv32.rs +++ b/embassy-executor/src/arch/riscv32.rs @@ -1,73 +1,86 @@ -use core::marker::PhantomData; -use core::ptr; +#[cfg(feature = "executor-interrupt")] +compile_error!("`executor-interrupt` is not supported with `arch-riscv32`."); -use atomic_polyfill::{AtomicBool, Ordering}; +#[cfg(feature = "executor-thread")] +pub use thread::*; +#[cfg(feature = "executor-thread")] +mod thread { + use core::marker::PhantomData; + use core::sync::atomic::{AtomicBool, Ordering}; -use super::{raw, Spawner}; + #[cfg(feature = "nightly")] + pub use embassy_macros::main_riscv as main; -/// global atomic used to keep track of whether there is work to do since sev() is not available on RISCV -/// -static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false); + use crate::raw::{Pender, PenderInner}; + use crate::{raw, Spawner}; -/// RISCV32 Executor -pub struct Executor { - inner: raw::Executor, - not_send: PhantomData<*mut ()>, -} + #[derive(Copy, Clone)] + pub(crate) struct ThreadPender; -impl Executor { - /// Create a new Executor. - pub fn new() -> Self { - Self { - // use Signal_Work_Thread_Mode as substitute for local interrupt register - inner: raw::Executor::new( - |_| { - SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst); - }, - ptr::null_mut(), - ), - not_send: PhantomData, + impl ThreadPender { + #[allow(unused)] + pub(crate) fn pend(self) { + SIGNAL_WORK_THREAD_MODE.store(true, core::sync::atomic::Ordering::SeqCst); } } - /// Run the executor. - /// - /// The `init` closure is called with a [`Spawner`] that spawns tasks on - /// this executor. Use it to spawn the initial task(s). After `init` returns, - /// the executor starts running the tasks. - /// - /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), - /// for example by passing it as an argument to the initial tasks. - /// - /// This function requires `&'static mut self`. This means you have to store the - /// Executor instance in a place where it'll live forever and grants you mutable - /// access. There's a few ways to do this: - /// - /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe) - /// - a `static mut` (unsafe) - /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) - /// - /// This function never returns. - pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { - init(self.inner.spawner()); + /// global atomic used to keep track of whether there is work to do since sev() is not available on RISCV + static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false); - loop { - unsafe { - self.inner.poll(); - // we do not care about race conditions between the load and store operations, interrupts - //will only set this value to true. - critical_section::with(|_| { - // if there is work to do, loop back to polling - // TODO can we relax this? - if SIGNAL_WORK_THREAD_MODE.load(Ordering::SeqCst) { - SIGNAL_WORK_THREAD_MODE.store(false, Ordering::SeqCst); - } - // if not, wait for interrupt - else { - core::arch::asm!("wfi"); - } - }); - // if an interrupt occurred while waiting, it will be serviced here + /// RISCV32 Executor + pub struct Executor { + inner: raw::Executor, + not_send: PhantomData<*mut ()>, + } + + impl Executor { + /// Create a new Executor. + pub fn new() -> Self { + Self { + inner: raw::Executor::new(Pender(PenderInner::Thread(ThreadPender))), + not_send: PhantomData, + } + } + + /// Run the executor. + /// + /// The `init` closure is called with a [`Spawner`] that spawns tasks on + /// this executor. Use it to spawn the initial task(s). After `init` returns, + /// the executor starts running the tasks. + /// + /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), + /// for example by passing it as an argument to the initial tasks. + /// + /// This function requires `&'static mut self`. This means you have to store the + /// Executor instance in a place where it'll live forever and grants you mutable + /// access. There's a few ways to do this: + /// + /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe) + /// - a `static mut` (unsafe) + /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) + /// + /// This function never returns. + pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { + init(self.inner.spawner()); + + loop { + unsafe { + self.inner.poll(); + // we do not care about race conditions between the load and store operations, interrupts + //will only set this value to true. + critical_section::with(|_| { + // if there is work to do, loop back to polling + // TODO can we relax this? + if SIGNAL_WORK_THREAD_MODE.load(Ordering::SeqCst) { + SIGNAL_WORK_THREAD_MODE.store(false, Ordering::SeqCst); + } + // if not, wait for interrupt + else { + core::arch::asm!("wfi"); + } + }); + // if an interrupt occurred while waiting, it will be serviced here + } } } } diff --git a/embassy-executor/src/arch/std.rs b/embassy-executor/src/arch/std.rs index 701f0eb18..4e4a178f0 100644 --- a/embassy-executor/src/arch/std.rs +++ b/embassy-executor/src/arch/std.rs @@ -1,84 +1,100 @@ -use std::marker::PhantomData; -use std::sync::{Condvar, Mutex}; +#[cfg(feature = "executor-interrupt")] +compile_error!("`executor-interrupt` is not supported with `arch-std`."); -use super::{raw, Spawner}; +#[cfg(feature = "executor-thread")] +pub use thread::*; +#[cfg(feature = "executor-thread")] +mod thread { + use std::marker::PhantomData; + use std::sync::{Condvar, Mutex}; -/// Single-threaded std-based executor. -pub struct Executor { - inner: raw::Executor, - not_send: PhantomData<*mut ()>, - signaler: &'static Signaler, -} + #[cfg(feature = "nightly")] + pub use embassy_macros::main_std as main; -impl Executor { - /// Create a new Executor. - pub fn new() -> Self { - let signaler = &*Box::leak(Box::new(Signaler::new())); - Self { - inner: raw::Executor::new( - |p| unsafe { - let s = &*(p as *const () as *const Signaler); - s.signal() - }, - signaler as *const _ as _, - ), - not_send: PhantomData, - signaler, + use crate::raw::{Pender, PenderInner}; + use crate::{raw, Spawner}; + + #[derive(Copy, Clone)] + pub(crate) struct ThreadPender(&'static Signaler); + + impl ThreadPender { + #[allow(unused)] + pub(crate) fn pend(self) { + self.0.signal() } } - /// Run the executor. - /// - /// The `init` closure is called with a [`Spawner`] that spawns tasks on - /// this executor. Use it to spawn the initial task(s). After `init` returns, - /// the executor starts running the tasks. - /// - /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), - /// for example by passing it as an argument to the initial tasks. - /// - /// This function requires `&'static mut self`. This means you have to store the - /// Executor instance in a place where it'll live forever and grants you mutable - /// access. There's a few ways to do this: - /// - /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe) - /// - a `static mut` (unsafe) - /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) - /// - /// This function never returns. - pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { - init(self.inner.spawner()); - - loop { - unsafe { self.inner.poll() }; - self.signaler.wait() - } + /// Single-threaded std-based executor. + pub struct Executor { + inner: raw::Executor, + not_send: PhantomData<*mut ()>, + signaler: &'static Signaler, } -} -struct Signaler { - mutex: Mutex, - condvar: Condvar, -} + impl Executor { + /// Create a new Executor. + pub fn new() -> Self { + let signaler = &*Box::leak(Box::new(Signaler::new())); + Self { + inner: raw::Executor::new(Pender(PenderInner::Thread(ThreadPender(signaler)))), + not_send: PhantomData, + signaler, + } + } -impl Signaler { - fn new() -> Self { - Self { - mutex: Mutex::new(false), - condvar: Condvar::new(), + /// Run the executor. + /// + /// The `init` closure is called with a [`Spawner`] that spawns tasks on + /// this executor. Use it to spawn the initial task(s). After `init` returns, + /// the executor starts running the tasks. + /// + /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), + /// for example by passing it as an argument to the initial tasks. + /// + /// This function requires `&'static mut self`. This means you have to store the + /// Executor instance in a place where it'll live forever and grants you mutable + /// access. There's a few ways to do this: + /// + /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe) + /// - a `static mut` (unsafe) + /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) + /// + /// This function never returns. + pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { + init(self.inner.spawner()); + + loop { + unsafe { self.inner.poll() }; + self.signaler.wait() + } } } - fn wait(&self) { - let mut signaled = self.mutex.lock().unwrap(); - while !*signaled { - signaled = self.condvar.wait(signaled).unwrap(); - } - *signaled = false; + struct Signaler { + mutex: Mutex, + condvar: Condvar, } - fn signal(&self) { - let mut signaled = self.mutex.lock().unwrap(); - *signaled = true; - self.condvar.notify_one(); + impl Signaler { + fn new() -> Self { + Self { + mutex: Mutex::new(false), + condvar: Condvar::new(), + } + } + + fn wait(&self) { + let mut signaled = self.mutex.lock().unwrap(); + while !*signaled { + signaled = self.condvar.wait(signaled).unwrap(); + } + *signaled = false; + } + + fn signal(&self) { + let mut signaled = self.mutex.lock().unwrap(); + *signaled = true; + self.condvar.notify_one(); + } } } diff --git a/embassy-executor/src/arch/wasm.rs b/embassy-executor/src/arch/wasm.rs index 98091cfbb..08ab16b99 100644 --- a/embassy-executor/src/arch/wasm.rs +++ b/embassy-executor/src/arch/wasm.rs @@ -1,74 +1,88 @@ -use core::marker::PhantomData; +#[cfg(feature = "executor-interrupt")] +compile_error!("`executor-interrupt` is not supported with `arch-wasm`."); -use js_sys::Promise; -use wasm_bindgen::prelude::*; +#[cfg(feature = "executor-thread")] +pub use thread::*; +#[cfg(feature = "executor-thread")] +mod thread { -use super::raw::util::UninitCell; -use super::raw::{self}; -use super::Spawner; + use core::marker::PhantomData; -/// WASM executor, wasm_bindgen to schedule tasks on the JS event loop. -pub struct Executor { - inner: raw::Executor, - ctx: &'static WasmContext, - not_send: PhantomData<*mut ()>, -} + #[cfg(feature = "nightly")] + pub use embassy_macros::main_wasm as main; + use js_sys::Promise; + use wasm_bindgen::prelude::*; -pub(crate) struct WasmContext { - promise: Promise, - closure: UninitCell>, -} + use crate::raw::util::UninitCell; + use crate::raw::{Pender, PenderInner}; + use crate::{raw, Spawner}; -impl WasmContext { - pub fn new() -> Self { - Self { - promise: Promise::resolve(&JsValue::undefined()), - closure: UninitCell::uninit(), - } + /// WASM executor, wasm_bindgen to schedule tasks on the JS event loop. + pub struct Executor { + inner: raw::Executor, + ctx: &'static WasmContext, + not_send: PhantomData<*mut ()>, } -} -impl Executor { - /// Create a new Executor. - pub fn new() -> Self { - let ctx = &*Box::leak(Box::new(WasmContext::new())); - let inner = raw::Executor::new( - |p| unsafe { - let ctx = &*(p as *const () as *const WasmContext); - let _ = ctx.promise.then(ctx.closure.as_mut()); - }, - ctx as *const _ as _, - ); - Self { - inner, - not_send: PhantomData, - ctx, + pub(crate) struct WasmContext { + promise: Promise, + closure: UninitCell>, + } + + #[derive(Copy, Clone)] + pub(crate) struct ThreadPender(&'static WasmContext); + + impl ThreadPender { + #[allow(unused)] + pub(crate) fn pend(self) { + let _ = self.0.promise.then(unsafe { self.0.closure.as_mut() }); } } - /// Run the executor. - /// - /// The `init` closure is called with a [`Spawner`] that spawns tasks on - /// this executor. Use it to spawn the initial task(s). After `init` returns, - /// the executor starts running the tasks. - /// - /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), - /// for example by passing it as an argument to the initial tasks. - /// - /// This function requires `&'static mut self`. This means you have to store the - /// Executor instance in a place where it'll live forever and grants you mutable - /// access. There's a few ways to do this: - /// - /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe) - /// - a `static mut` (unsafe) - /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) - pub fn start(&'static mut self, init: impl FnOnce(Spawner)) { - unsafe { - let executor = &self.inner; - self.ctx.closure.write(Closure::new(move |_| { - executor.poll(); - })); - init(self.inner.spawner()); + impl WasmContext { + pub fn new() -> Self { + Self { + promise: Promise::resolve(&JsValue::undefined()), + closure: UninitCell::uninit(), + } + } + } + + impl Executor { + /// Create a new Executor. + pub fn new() -> Self { + let ctx = &*Box::leak(Box::new(WasmContext::new())); + Self { + inner: raw::Executor::new(Pender(PenderInner::Thread(ThreadPender(ctx)))), + not_send: PhantomData, + ctx, + } + } + + /// Run the executor. + /// + /// The `init` closure is called with a [`Spawner`] that spawns tasks on + /// this executor. Use it to spawn the initial task(s). After `init` returns, + /// the executor starts running the tasks. + /// + /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), + /// for example by passing it as an argument to the initial tasks. + /// + /// This function requires `&'static mut self`. This means you have to store the + /// Executor instance in a place where it'll live forever and grants you mutable + /// access. There's a few ways to do this: + /// + /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe) + /// - a `static mut` (unsafe) + /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) + pub fn start(&'static mut self, init: impl FnOnce(Spawner)) { + unsafe { + let executor = &self.inner; + self.ctx.closure.write(Closure::new(move |_| { + executor.poll(); + })); + init(self.inner.spawner()); + } } } } diff --git a/embassy-executor/src/arch/xtensa.rs b/embassy-executor/src/arch/xtensa.rs index f908aaa70..017b2c52b 100644 --- a/embassy-executor/src/arch/xtensa.rs +++ b/embassy-executor/src/arch/xtensa.rs @@ -1,66 +1,84 @@ -use core::marker::PhantomData; -use core::ptr; +#[cfg(feature = "executor-interrupt")] +compile_error!("`executor-interrupt` is not supported with `arch-xtensa`."); -use atomic_polyfill::{AtomicBool, Ordering}; +#[cfg(feature = "executor-thread")] +pub use thread::*; +#[cfg(feature = "executor-thread")] +mod thread { + use core::marker::PhantomData; + use core::sync::atomic::{AtomicBool, Ordering}; -use super::{raw, Spawner}; + use crate::raw::{Pender, PenderInner}; + use crate::{raw, Spawner}; -/// global atomic used to keep track of whether there is work to do since sev() is not available on Xtensa -/// -static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false); + #[derive(Copy, Clone)] + pub(crate) struct ThreadPender; -/// Xtensa Executor -pub struct Executor { - inner: raw::Executor, - not_send: PhantomData<*mut ()>, -} - -impl Executor { - /// Create a new Executor. - pub fn new() -> Self { - Self { - // use Signal_Work_Thread_Mode as substitute for local interrupt register - inner: raw::Executor::new( - |_| { - SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst); - }, - ptr::null_mut(), - ), - not_send: PhantomData, + impl ThreadPender { + #[allow(unused)] + pub(crate) fn pend(self) { + SIGNAL_WORK_THREAD_MODE.store(true, core::sync::atomic::Ordering::SeqCst); } } - /// Run the executor. - /// - /// The `init` closure is called with a [`Spawner`] that spawns tasks on - /// this executor. Use it to spawn the initial task(s). After `init` returns, - /// the executor starts running the tasks. - /// - /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), - /// for example by passing it as an argument to the initial tasks. - /// - /// This function requires `&'static mut self`. This means you have to store the - /// Executor instance in a place where it'll live forever and grants you mutable - /// access. There's a few ways to do this: - /// - /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe) - /// - a `static mut` (unsafe) - /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) - /// - /// This function never returns. - pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { - init(self.inner.spawner()); + /// global atomic used to keep track of whether there is work to do since sev() is not available on Xtensa + static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false); - loop { - unsafe { - self.inner.poll(); - // we do not care about race conditions between the load and store operations, interrupts - // will only set this value to true. - // if there is work to do, loop back to polling - // TODO can we relax this? - critical_section::with(|_| { + /// Xtensa Executor + pub struct Executor { + inner: raw::Executor, + not_send: PhantomData<*mut ()>, + } + + impl Executor { + /// Create a new Executor. + pub fn new() -> Self { + Self { + inner: raw::Executor::new(Pender(PenderInner::Thread(ThreadPender))), + not_send: PhantomData, + } + } + + /// Run the executor. + /// + /// The `init` closure is called with a [`Spawner`] that spawns tasks on + /// this executor. Use it to spawn the initial task(s). After `init` returns, + /// the executor starts running the tasks. + /// + /// To spawn more tasks later, you may keep copies of the [`Spawner`] (it is `Copy`), + /// for example by passing it as an argument to the initial tasks. + /// + /// This function requires `&'static mut self`. This means you have to store the + /// Executor instance in a place where it'll live forever and grants you mutable + /// access. There's a few ways to do this: + /// + /// - a [StaticCell](https://docs.rs/static_cell/latest/static_cell/) (safe) + /// - a `static mut` (unsafe) + /// - a local variable in a function you know never returns (like `fn main() -> !`), upgrading its lifetime with `transmute`. (unsafe) + /// + /// This function never returns. + pub fn run(&'static mut self, init: impl FnOnce(Spawner)) -> ! { + init(self.inner.spawner()); + + loop { + unsafe { + self.inner.poll(); + + // Manual critical section implementation that only masks interrupts handlers. + // We must not acquire the cross-core on dual-core systems because that would + // prevent the other core from doing useful work while this core is sleeping. + let token: critical_section::RawRestoreState; + core::arch::asm!("rsil {0}, 5", out(reg) token); + + // we do not care about race conditions between the load and store operations, interrupts + // will only set this value to true. + // if there is work to do, loop back to polling if SIGNAL_WORK_THREAD_MODE.load(Ordering::SeqCst) { SIGNAL_WORK_THREAD_MODE.store(false, Ordering::SeqCst); + + core::arch::asm!( + "wsr.ps {0}", + "rsync", in(reg) token) } else { // waiti sets the PS.INTLEVEL when slipping into sleep // because critical sections in Xtensa are implemented via increasing @@ -68,7 +86,7 @@ impl Executor { // take care not add code after `waiti` if it needs to be inside the CS core::arch::asm!("waiti 0"); // critical section ends here } - }); + } } } } diff --git a/embassy-executor/src/lib.rs b/embassy-executor/src/lib.rs index e4cbd04b9..3ce687eb6 100644 --- a/embassy-executor/src/lib.rs +++ b/embassy-executor/src/lib.rs @@ -1,5 +1,5 @@ -#![cfg_attr(not(any(feature = "std", feature = "wasm")), no_std)] -#![cfg_attr(all(feature = "nightly", target_arch = "xtensa"), feature(asm_experimental_arch))] +#![cfg_attr(not(any(feature = "arch-std", feature = "arch-wasm")), no_std)] +#![cfg_attr(all(feature = "nightly", feature = "arch-xtensa"), feature(asm_experimental_arch))] #![allow(clippy::new_without_default)] #![doc = include_str!("../README.md")] #![warn(missing_docs)] @@ -8,41 +8,45 @@ pub(crate) mod fmt; #[cfg(feature = "nightly")] -pub use embassy_macros::{main, task}; +pub use embassy_macros::task; -cfg_if::cfg_if! { - if #[cfg(cortex_m)] { - #[path="arch/cortex_m.rs"] - mod arch; - pub use arch::*; - } - else if #[cfg(target_arch="riscv32")] { - #[path="arch/riscv32.rs"] - mod arch; - pub use arch::*; - } - else if #[cfg(all(target_arch="xtensa", feature = "nightly"))] { - #[path="arch/xtensa.rs"] - mod arch; - pub use arch::*; - } - else if #[cfg(feature="wasm")] { - #[path="arch/wasm.rs"] - mod arch; - pub use arch::*; - } - else if #[cfg(feature="std")] { - #[path="arch/std.rs"] - mod arch; - pub use arch::*; - } +macro_rules! check_at_most_one { + (@amo [$($feats:literal)*] [] [$($res:tt)*]) => { + #[cfg(any($($res)*))] + compile_error!(concat!("At most one of these features can be enabled at the same time:", $(" `", $feats, "`",)*)); + }; + (@amo $feats:tt [$curr:literal $($rest:literal)*] [$($res:tt)*]) => { + check_at_most_one!(@amo $feats [$($rest)*] [$($res)* $(all(feature=$curr, feature=$rest),)*]); + }; + ($($f:literal),*$(,)?) => { + check_at_most_one!(@amo [$($f)*] [$($f)*] []); + }; } +check_at_most_one!("arch-cortex-m", "arch-riscv32", "arch-xtensa", "arch-std", "arch-wasm",); +#[cfg(feature = "_arch")] +#[cfg_attr(feature = "arch-cortex-m", path = "arch/cortex_m.rs")] +#[cfg_attr(feature = "arch-riscv32", path = "arch/riscv32.rs")] +#[cfg_attr(feature = "arch-xtensa", path = "arch/xtensa.rs")] +#[cfg_attr(feature = "arch-std", path = "arch/std.rs")] +#[cfg_attr(feature = "arch-wasm", path = "arch/wasm.rs")] +mod arch; + +#[cfg(feature = "_arch")] +pub use arch::*; + +pub mod raw; + +mod spawner; +pub use spawner::*; + +/// Implementation details for embassy macros. +/// Do not use. Used for macros and HALs only. Not covered by semver guarantees. #[doc(hidden)] -/// Implementation details for embassy macros. DO NOT USE. -pub mod export { +pub mod _export { #[cfg(feature = "rtos-trace")] pub use rtos_trace::trace; + pub use static_cell::StaticCell; /// Expands the given block of code when `embassy-executor` is compiled with /// the `rtos-trace-interrupt` feature. @@ -62,14 +66,3 @@ pub mod export { ($($tt:tt)*) => {}; } } - -pub mod raw; - -mod spawner; -pub use spawner::*; - -/// Do not use. Used for macros and HALs only. Not covered by semver guarantees. -#[doc(hidden)] -pub mod _export { - pub use static_cell::StaticCell; -} diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index e1258ebb5..f3760f589 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs @@ -11,17 +11,17 @@ mod run_queue; #[cfg(feature = "integrated-timers")] mod timer_queue; pub(crate) mod util; +#[cfg_attr(feature = "turbowakers", path = "waker_turbo.rs")] mod waker; -use core::cell::Cell; use core::future::Future; +use core::marker::PhantomData; +use core::mem; use core::pin::Pin; use core::ptr::NonNull; use core::task::{Context, Poll}; -use core::{mem, ptr}; use atomic_polyfill::{AtomicU32, Ordering}; -use critical_section::CriticalSection; #[cfg(feature = "integrated-timers")] use embassy_time::driver::{self, AlarmHandle}; #[cfg(feature = "integrated-timers")] @@ -30,7 +30,7 @@ use embassy_time::Instant; use rtos_trace::trace; use self::run_queue::{RunQueue, RunQueueItem}; -use self::util::UninitCell; +use self::util::{SyncUnsafeCell, UninitCell}; pub use self::waker::task_from_waker; use super::SpawnToken; @@ -43,35 +43,49 @@ pub(crate) const STATE_RUN_QUEUED: u32 = 1 << 1; pub(crate) const STATE_TIMER_QUEUED: u32 = 1 << 2; /// Raw task header for use in task pointers. -/// -/// This is an opaque struct, used for raw pointers to tasks, for use -/// with funtions like [`wake_task`] and [`task_from_waker`]. -pub struct TaskHeader { +pub(crate) struct TaskHeader { pub(crate) state: AtomicU32, pub(crate) run_queue_item: RunQueueItem, - pub(crate) executor: Cell<*const Executor>, // Valid if state != 0 - pub(crate) poll_fn: UninitCell)>, // Valid if STATE_SPAWNED + pub(crate) executor: SyncUnsafeCell>, + poll_fn: SyncUnsafeCell>, #[cfg(feature = "integrated-timers")] - pub(crate) expires_at: Cell, + pub(crate) expires_at: SyncUnsafeCell, #[cfg(feature = "integrated-timers")] pub(crate) timer_queue_item: timer_queue::TimerQueueItem, } -impl TaskHeader { - pub(crate) const fn new() -> Self { - Self { - state: AtomicU32::new(0), - run_queue_item: RunQueueItem::new(), - executor: Cell::new(ptr::null()), - poll_fn: UninitCell::uninit(), +/// This is essentially a `&'static TaskStorage` where the type of the future has been erased. +#[derive(Clone, Copy)] +pub struct TaskRef { + ptr: NonNull, +} - #[cfg(feature = "integrated-timers")] - expires_at: Cell::new(Instant::from_ticks(0)), - #[cfg(feature = "integrated-timers")] - timer_queue_item: timer_queue::TimerQueueItem::new(), +unsafe impl Send for TaskRef where &'static TaskHeader: Send {} +unsafe impl Sync for TaskRef where &'static TaskHeader: Sync {} + +impl TaskRef { + fn new(task: &'static TaskStorage) -> Self { + Self { + ptr: NonNull::from(task).cast(), } } + + /// Safety: The pointer must have been obtained with `Task::as_ptr` + pub(crate) unsafe fn from_ptr(ptr: *const TaskHeader) -> Self { + Self { + ptr: NonNull::new_unchecked(ptr as *mut TaskHeader), + } + } + + pub(crate) fn header(self) -> &'static TaskHeader { + unsafe { self.ptr.as_ref() } + } + + /// The returned pointer is valid for the entire TaskStorage. + pub(crate) fn as_ptr(self) -> *const TaskHeader { + self.ptr.as_ptr() + } } /// Raw storage in which a task can be spawned. @@ -101,7 +115,18 @@ impl TaskStorage { /// Create a new TaskStorage, in not-spawned state. pub const fn new() -> Self { Self { - raw: TaskHeader::new(), + raw: TaskHeader { + state: AtomicU32::new(0), + run_queue_item: RunQueueItem::new(), + executor: SyncUnsafeCell::new(None), + // Note: this is lazily initialized so that a static `TaskStorage` will go in `.bss` + poll_fn: SyncUnsafeCell::new(None), + + #[cfg(feature = "integrated-timers")] + expires_at: SyncUnsafeCell::new(Instant::from_ticks(0)), + #[cfg(feature = "integrated-timers")] + timer_queue_item: timer_queue::TimerQueueItem::new(), + }, future: UninitCell::uninit(), } } @@ -120,29 +145,17 @@ impl TaskStorage { /// Once the task has finished running, you may spawn it again. It is allowed to spawn it /// on a different executor. pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken { - if self.spawn_mark_used() { - return unsafe { SpawnToken::::new(self.spawn_initialize(future)) }; + let task = AvailableTask::claim(self); + match task { + Some(task) => { + let task = task.initialize(future); + unsafe { SpawnToken::::new(task) } + } + None => SpawnToken::new_failed(), } - - SpawnToken::::new_failed() } - fn spawn_mark_used(&'static self) -> bool { - let state = STATE_SPAWNED | STATE_RUN_QUEUED; - self.raw - .state - .compare_exchange(0, state, Ordering::AcqRel, Ordering::Acquire) - .is_ok() - } - - unsafe fn spawn_initialize(&'static self, future: impl FnOnce() -> F) -> NonNull { - // Initialize the task - self.raw.poll_fn.write(Self::poll); - self.future.write(future()); - NonNull::new_unchecked(self as *const TaskStorage as *const TaskHeader as *mut TaskHeader) - } - - unsafe fn poll(p: NonNull) { + unsafe fn poll(p: TaskRef) { let this = &*(p.as_ptr() as *const TaskStorage); let future = Pin::new_unchecked(this.future.as_mut()); @@ -152,6 +165,9 @@ impl TaskStorage { Poll::Ready(_) => { this.future.drop_in_place(); this.raw.state.fetch_and(!STATE_SPAWNED, Ordering::AcqRel); + + #[cfg(feature = "integrated-timers")] + this.raw.expires_at.set(Instant::MAX); } Poll::Pending => {} } @@ -160,9 +176,37 @@ impl TaskStorage { // it's a noop for our waker. mem::forget(waker); } + + #[doc(hidden)] + #[allow(dead_code)] + fn _assert_sync(self) { + fn assert_sync(_: T) {} + + assert_sync(self) + } } -unsafe impl Sync for TaskStorage {} +struct AvailableTask { + task: &'static TaskStorage, +} + +impl AvailableTask { + fn claim(task: &'static TaskStorage) -> Option { + task.raw + .state + .compare_exchange(0, STATE_SPAWNED | STATE_RUN_QUEUED, Ordering::AcqRel, Ordering::Acquire) + .ok() + .map(|_| Self { task }) + } + + fn initialize(self, future: impl FnOnce() -> F) -> TaskRef { + unsafe { + self.task.raw.poll_fn.set(Some(TaskStorage::::poll)); + self.task.future.write(future()); + } + TaskRef::new(self.task) + } +} /// Raw storage that can hold up to N tasks of the same type. /// @@ -187,13 +231,14 @@ impl TaskPool { /// is currently free. If none is free, a "poisoned" SpawnToken is returned, /// which will cause [`Spawner::spawn()`](super::Spawner::spawn) to return the error. pub fn spawn(&'static self, future: impl FnOnce() -> F) -> SpawnToken { - for task in &self.pool { - if task.spawn_mark_used() { - return unsafe { SpawnToken::::new(task.spawn_initialize(future)) }; + let task = self.pool.iter().find_map(AvailableTask::claim); + match task { + Some(task) => { + let task = task.initialize(future); + unsafe { SpawnToken::::new(task) } } + None => SpawnToken::new_failed(), } - - SpawnToken::::new_failed() } /// Like spawn(), but allows the task to be send-spawned if the args are Send even if @@ -235,39 +280,71 @@ impl TaskPool { // This ONLY holds for `async fn` futures. The other `spawn` methods can be called directly // by the user, with arbitrary hand-implemented futures. This is why these return `SpawnToken`. - for task in &self.pool { - if task.spawn_mark_used() { - return SpawnToken::::new(task.spawn_initialize(future)); + let task = self.pool.iter().find_map(AvailableTask::claim); + match task { + Some(task) => { + let task = task.initialize(future); + unsafe { SpawnToken::::new(task) } } + None => SpawnToken::new_failed(), } - - SpawnToken::::new_failed() } } -/// Raw executor. +#[derive(Clone, Copy)] +pub(crate) enum PenderInner { + #[cfg(feature = "executor-thread")] + Thread(crate::arch::ThreadPender), + #[cfg(feature = "executor-interrupt")] + Interrupt(crate::arch::InterruptPender), + #[cfg(feature = "pender-callback")] + Callback { func: fn(*mut ()), context: *mut () }, +} + +unsafe impl Send for PenderInner {} +unsafe impl Sync for PenderInner {} + +/// Platform/architecture-specific action executed when an executor has pending work. /// -/// This is the core of the Embassy executor. It is low-level, requiring manual -/// handling of wakeups and task polling. If you can, prefer using one of the -/// [higher level executors](crate::Executor). +/// When a task within an executor is woken, the `Pender` is called. This does a +/// platform/architecture-specific action to signal there is pending work in the executor. +/// When this happens, you must arrange for [`Executor::poll`] to be called. /// -/// The raw executor leaves it up to you to handle wakeups and scheduling: -/// -/// - To get the executor to do work, call `poll()`. This will poll all queued tasks (all tasks -/// that "want to run"). -/// - You must supply a `signal_fn`. The executor will call it to notify you it has work -/// to do. You must arrange for `poll()` to be called as soon as possible. -/// -/// `signal_fn` can be called from *any* context: any thread, any interrupt priority -/// level, etc. It may be called synchronously from any `Executor` method call as well. -/// You must deal with this correctly. -/// -/// In particular, you must NOT call `poll` directly from `signal_fn`, as this violates -/// the requirement for `poll` to not be called reentrantly. -pub struct Executor { +/// You can think of it as a waker, but for the whole executor. +pub struct Pender(pub(crate) PenderInner); + +impl Pender { + /// Create a `Pender` that will call an arbitrary function pointer. + /// + /// # Arguments + /// + /// - `func`: The function pointer to call. + /// - `context`: Opaque context pointer, that will be passed to the function pointer. + #[cfg(feature = "pender-callback")] + pub fn new_from_callback(func: fn(*mut ()), context: *mut ()) -> Self { + Self(PenderInner::Callback { + func, + context: context.into(), + }) + } +} + +impl Pender { + pub(crate) fn pend(&self) { + match self.0 { + #[cfg(feature = "executor-thread")] + PenderInner::Thread(x) => x.pend(), + #[cfg(feature = "executor-interrupt")] + PenderInner::Interrupt(x) => x.pend(), + #[cfg(feature = "pender-callback")] + PenderInner::Callback { func, context } => func(context), + } + } +} + +pub(crate) struct SyncExecutor { run_queue: RunQueue, - signal_fn: fn(*mut ()), - signal_ctx: *mut (), + pender: Pender, #[cfg(feature = "integrated-timers")] pub(crate) timer_queue: timer_queue::TimerQueue, @@ -275,23 +352,14 @@ pub struct Executor { alarm: AlarmHandle, } -impl Executor { - /// Create a new executor. - /// - /// When the executor has work to do, it will call `signal_fn` with - /// `signal_ctx` as argument. - /// - /// See [`Executor`] docs for details on `signal_fn`. - pub fn new(signal_fn: fn(*mut ()), signal_ctx: *mut ()) -> Self { +impl SyncExecutor { + pub(crate) fn new(pender: Pender) -> Self { #[cfg(feature = "integrated-timers")] let alarm = unsafe { unwrap!(driver::allocate_alarm()) }; - #[cfg(feature = "integrated-timers")] - driver::set_alarm_callback(alarm, signal_fn, signal_ctx); Self { run_queue: RunQueue::new(), - signal_fn, - signal_ctx, + pender, #[cfg(feature = "integrated-timers")] timer_queue: timer_queue::TimerQueue::new(), @@ -307,12 +375,133 @@ impl Executor { /// - `task` must be set up to run in this executor. /// - `task` must NOT be already enqueued (in this executor or another one). #[inline(always)] - unsafe fn enqueue(&self, cs: CriticalSection, task: NonNull) { + unsafe fn enqueue(&self, task: TaskRef) { #[cfg(feature = "rtos-trace")] trace::task_ready_begin(task.as_ptr() as u32); - if self.run_queue.enqueue(cs, task) { - (self.signal_fn)(self.signal_ctx) + if self.run_queue.enqueue(task) { + self.pender.pend(); + } + } + + #[cfg(feature = "integrated-timers")] + fn alarm_callback(ctx: *mut ()) { + let this: &Self = unsafe { &*(ctx as *const Self) }; + this.pender.pend(); + } + + pub(super) unsafe fn spawn(&'static self, task: TaskRef) { + task.header().executor.set(Some(self)); + + #[cfg(feature = "rtos-trace")] + trace::task_new(task.as_ptr() as u32); + + self.enqueue(task); + } + + /// # Safety + /// + /// Same as [`Executor::poll`], plus you must only call this on the thread this executor was created. + pub(crate) unsafe fn poll(&'static self) { + #[cfg(feature = "integrated-timers")] + driver::set_alarm_callback(self.alarm, Self::alarm_callback, self as *const _ as *mut ()); + + #[allow(clippy::never_loop)] + loop { + #[cfg(feature = "integrated-timers")] + self.timer_queue.dequeue_expired(Instant::now(), |task| wake_task(task)); + + self.run_queue.dequeue_all(|p| { + let task = p.header(); + + #[cfg(feature = "integrated-timers")] + task.expires_at.set(Instant::MAX); + + let state = task.state.fetch_and(!STATE_RUN_QUEUED, Ordering::AcqRel); + if state & STATE_SPAWNED == 0 { + // If task is not running, ignore it. This can happen in the following scenario: + // - Task gets dequeued, poll starts + // - While task is being polled, it gets woken. It gets placed in the queue. + // - Task poll finishes, returning done=true + // - RUNNING bit is cleared, but the task is already in the queue. + return; + } + + #[cfg(feature = "rtos-trace")] + trace::task_exec_begin(p.as_ptr() as u32); + + // Run the task + task.poll_fn.get().unwrap_unchecked()(p); + + #[cfg(feature = "rtos-trace")] + trace::task_exec_end(); + + // Enqueue or update into timer_queue + #[cfg(feature = "integrated-timers")] + self.timer_queue.update(p); + }); + + #[cfg(feature = "integrated-timers")] + { + // If this is already in the past, set_alarm might return false + // In that case do another poll loop iteration. + let next_expiration = self.timer_queue.next_expiration(); + if driver::set_alarm(self.alarm, next_expiration.as_ticks()) { + break; + } + } + + #[cfg(not(feature = "integrated-timers"))] + { + break; + } + } + + #[cfg(feature = "rtos-trace")] + trace::system_idle(); + } +} + +/// Raw executor. +/// +/// This is the core of the Embassy executor. It is low-level, requiring manual +/// handling of wakeups and task polling. If you can, prefer using one of the +/// [higher level executors](crate::Executor). +/// +/// The raw executor leaves it up to you to handle wakeups and scheduling: +/// +/// - To get the executor to do work, call `poll()`. This will poll all queued tasks (all tasks +/// that "want to run"). +/// - You must supply a [`Pender`]. The executor will call it to notify you it has work +/// to do. You must arrange for `poll()` to be called as soon as possible. +/// +/// The [`Pender`] can be called from *any* context: any thread, any interrupt priority +/// level, etc. It may be called synchronously from any `Executor` method call as well. +/// You must deal with this correctly. +/// +/// In particular, you must NOT call `poll` directly from the pender callback, as this violates +/// the requirement for `poll` to not be called reentrantly. +#[repr(transparent)] +pub struct Executor { + pub(crate) inner: SyncExecutor, + + _not_sync: PhantomData<*mut ()>, +} + +impl Executor { + pub(crate) unsafe fn wrap(inner: &SyncExecutor) -> &Self { + mem::transmute(inner) + } + + /// Create a new executor. + /// + /// When the executor has work to do, it will call the [`Pender`]. + /// + /// See [`Executor`] docs for details on `Pender`. + pub fn new(pender: Pender) -> Self { + Self { + inner: SyncExecutor::new(pender), + _not_sync: PhantomData, } } @@ -325,15 +514,8 @@ impl Executor { /// It is OK to use `unsafe` to call this from a thread that's not the executor thread. /// In this case, the task's Future must be Send. This is because this is effectively /// sending the task to the executor thread. - pub(super) unsafe fn spawn(&'static self, task: NonNull) { - task.as_ref().executor.set(self); - - #[cfg(feature = "rtos-trace")] - trace::task_new(task.as_ptr() as u32); - - critical_section::with(|cs| { - self.enqueue(cs, task); - }) + pub(super) unsafe fn spawn(&'static self, task: TaskRef) { + self.inner.spawn(task) } /// Poll all queued tasks in this executor. @@ -341,63 +523,20 @@ impl Executor { /// This loops over all tasks that are queued to be polled (i.e. they're /// freshly spawned or they've been woken). Other tasks are not polled. /// - /// You must call `poll` after receiving a call to `signal_fn`. It is OK - /// to call `poll` even when not requested by `signal_fn`, but it wastes + /// You must call `poll` after receiving a call to the [`Pender`]. It is OK + /// to call `poll` even when not requested by the `Pender`, but it wastes /// energy. /// /// # Safety /// /// You must NOT call `poll` reentrantly on the same executor. /// - /// In particular, note that `poll` may call `signal_fn` synchronously. Therefore, you - /// must NOT directly call `poll()` from your `signal_fn`. Instead, `signal_fn` has to + /// In particular, note that `poll` may call the `Pender` synchronously. Therefore, you + /// must NOT directly call `poll()` from the `Pender` callback. Instead, the callback has to /// somehow schedule for `poll()` to be called later, at a time you know for sure there's /// no `poll()` already running. pub unsafe fn poll(&'static self) { - #[cfg(feature = "integrated-timers")] - self.timer_queue.dequeue_expired(Instant::now(), |task| wake_task(task)); - - self.run_queue.dequeue_all(|p| { - let task = p.as_ref(); - - #[cfg(feature = "integrated-timers")] - task.expires_at.set(Instant::MAX); - - let state = task.state.fetch_and(!STATE_RUN_QUEUED, Ordering::AcqRel); - if state & STATE_SPAWNED == 0 { - // If task is not running, ignore it. This can happen in the following scenario: - // - Task gets dequeued, poll starts - // - While task is being polled, it gets woken. It gets placed in the queue. - // - Task poll finishes, returning done=true - // - RUNNING bit is cleared, but the task is already in the queue. - return; - } - - #[cfg(feature = "rtos-trace")] - trace::task_exec_begin(p.as_ptr() as u32); - - // Run the task - task.poll_fn.read()(p as _); - - #[cfg(feature = "rtos-trace")] - trace::task_exec_end(); - - // Enqueue or update into timer_queue - #[cfg(feature = "integrated-timers")] - self.timer_queue.update(p); - }); - - #[cfg(feature = "integrated-timers")] - { - // If this is already in the past, set_alarm will immediately trigger the alarm. - // This will cause `signal_fn` to be called, which will cause `poll()` to be called again, - // so we immediately do another poll loop iteration. - let next_expiration = self.timer_queue.next_expiration(); - driver::set_alarm(self.alarm, next_expiration.as_ticks()); - } - - #[cfg(feature = "rtos-trace")] - trace::system_idle(); + self.inner.poll() } /// Get a spawner that spawns tasks in this executor. @@ -409,41 +548,49 @@ impl Executor { } } -/// Wake a task by raw pointer. +/// Wake a task by `TaskRef`. /// -/// You can obtain task pointers from `Waker`s using [`task_from_waker`]. -/// -/// # Safety -/// -/// `task` must be a valid task pointer obtained from [`task_from_waker`]. -pub unsafe fn wake_task(task: NonNull) { - critical_section::with(|cs| { - let header = task.as_ref(); - let state = header.state.load(Ordering::Relaxed); +/// You can obtain a `TaskRef` from a `Waker` using [`task_from_waker`]. +pub fn wake_task(task: TaskRef) { + let header = task.header(); + let res = header.state.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |state| { // If already scheduled, or if not started, if (state & STATE_RUN_QUEUED != 0) || (state & STATE_SPAWNED == 0) { - return; + None + } else { + // Mark it as scheduled + Some(state | STATE_RUN_QUEUED) } + }); - // Mark it as scheduled - header.state.store(state | STATE_RUN_QUEUED, Ordering::Relaxed); - + if res.is_ok() { // We have just marked the task as scheduled, so enqueue it. - let executor = &*header.executor.get(); - executor.enqueue(cs, task); - }) + unsafe { + let executor = header.executor.get().unwrap_unchecked(); + executor.enqueue(task); + } + } } #[cfg(feature = "integrated-timers")] -#[no_mangle] -unsafe fn _embassy_time_schedule_wake(at: Instant, waker: &core::task::Waker) { - let task = waker::task_from_waker(waker); - let task = task.as_ref(); - let expires_at = task.expires_at.get(); - task.expires_at.set(expires_at.min(at)); +struct TimerQueue; + +#[cfg(feature = "integrated-timers")] +impl embassy_time::queue::TimerQueue for TimerQueue { + fn schedule_wake(&'static self, at: Instant, waker: &core::task::Waker) { + let task = waker::task_from_waker(waker); + let task = task.header(); + unsafe { + let expires_at = task.expires_at.get(); + task.expires_at.set(expires_at.min(at)); + } + } } +#[cfg(feature = "integrated-timers")] +embassy_time::timer_queue_impl!(static TIMER_QUEUE: TimerQueue = TimerQueue); + #[cfg(feature = "rtos-trace")] impl rtos_trace::RtosTraceOSCallbacks for Executor { fn task_list() { diff --git a/embassy-executor/src/raw/run_queue.rs b/embassy-executor/src/raw/run_queue.rs index ed8c82a5c..f1ec19ac1 100644 --- a/embassy-executor/src/raw/run_queue.rs +++ b/embassy-executor/src/raw/run_queue.rs @@ -2,18 +2,18 @@ use core::ptr; use core::ptr::NonNull; use atomic_polyfill::{AtomicPtr, Ordering}; -use critical_section::CriticalSection; -use super::TaskHeader; +use super::{TaskHeader, TaskRef}; +use crate::raw::util::SyncUnsafeCell; pub(crate) struct RunQueueItem { - next: AtomicPtr, + next: SyncUnsafeCell>, } impl RunQueueItem { pub const fn new() -> Self { Self { - next: AtomicPtr::new(ptr::null_mut()), + next: SyncUnsafeCell::new(None), } } } @@ -46,29 +46,43 @@ impl RunQueue { /// /// `item` must NOT be already enqueued in any queue. #[inline(always)] - pub(crate) unsafe fn enqueue(&self, _cs: CriticalSection, task: NonNull) -> bool { - let prev = self.head.load(Ordering::Relaxed); - task.as_ref().run_queue_item.next.store(prev, Ordering::Relaxed); - self.head.store(task.as_ptr(), Ordering::Relaxed); - prev.is_null() + pub(crate) unsafe fn enqueue(&self, task: TaskRef) -> bool { + let mut was_empty = false; + + self.head + .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |prev| { + was_empty = prev.is_null(); + unsafe { + // safety: the pointer is either null or valid + let prev = NonNull::new(prev).map(|ptr| TaskRef::from_ptr(ptr.as_ptr())); + // safety: there are no concurrent accesses to `next` + task.header().run_queue_item.next.set(prev); + } + Some(task.as_ptr() as *mut _) + }) + .ok(); + + was_empty } /// Empty the queue, then call `on_task` for each task that was in the queue. /// NOTE: It is OK for `on_task` to enqueue more tasks. In this case they're left in the queue /// and will be processed by the *next* call to `dequeue_all`, *not* the current one. - pub(crate) fn dequeue_all(&self, on_task: impl Fn(NonNull)) { + pub(crate) fn dequeue_all(&self, on_task: impl Fn(TaskRef)) { // Atomically empty the queue. - let mut ptr = self.head.swap(ptr::null_mut(), Ordering::AcqRel); + let ptr = self.head.swap(ptr::null_mut(), Ordering::AcqRel); + + // safety: the pointer is either null or valid + let mut next = unsafe { NonNull::new(ptr).map(|ptr| TaskRef::from_ptr(ptr.as_ptr())) }; // Iterate the linked list of tasks that were previously in the queue. - while let Some(task) = NonNull::new(ptr) { + while let Some(task) = next { // If the task re-enqueues itself, the `next` pointer will get overwritten. // Therefore, first read the next pointer, and only then process the task. - let next = unsafe { task.as_ref() }.run_queue_item.next.load(Ordering::Relaxed); + // safety: there are no concurrent accesses to `next` + next = unsafe { task.header().run_queue_item.next.get() }; on_task(task); - - ptr = next } } } diff --git a/embassy-executor/src/raw/timer_queue.rs b/embassy-executor/src/raw/timer_queue.rs index 24c31892a..dc71c95b1 100644 --- a/embassy-executor/src/raw/timer_queue.rs +++ b/embassy-executor/src/raw/timer_queue.rs @@ -1,45 +1,43 @@ -use core::cell::Cell; use core::cmp::min; -use core::ptr; -use core::ptr::NonNull; use atomic_polyfill::Ordering; use embassy_time::Instant; -use super::{TaskHeader, STATE_TIMER_QUEUED}; +use super::{TaskRef, STATE_TIMER_QUEUED}; +use crate::raw::util::SyncUnsafeCell; pub(crate) struct TimerQueueItem { - next: Cell<*mut TaskHeader>, + next: SyncUnsafeCell>, } impl TimerQueueItem { pub const fn new() -> Self { Self { - next: Cell::new(ptr::null_mut()), + next: SyncUnsafeCell::new(None), } } } pub(crate) struct TimerQueue { - head: Cell<*mut TaskHeader>, + head: SyncUnsafeCell>, } impl TimerQueue { pub const fn new() -> Self { Self { - head: Cell::new(ptr::null_mut()), + head: SyncUnsafeCell::new(None), } } - pub(crate) unsafe fn update(&self, p: NonNull) { - let task = p.as_ref(); + pub(crate) unsafe fn update(&self, p: TaskRef) { + let task = p.header(); if task.expires_at.get() != Instant::MAX { let old_state = task.state.fetch_or(STATE_TIMER_QUEUED, Ordering::AcqRel); let is_new = old_state & STATE_TIMER_QUEUED == 0; if is_new { task.timer_queue_item.next.set(self.head.get()); - self.head.set(p.as_ptr()); + self.head.set(Some(p)); } } } @@ -47,7 +45,7 @@ impl TimerQueue { pub(crate) unsafe fn next_expiration(&self) -> Instant { let mut res = Instant::MAX; self.retain(|p| { - let task = p.as_ref(); + let task = p.header(); let expires = task.expires_at.get(); res = min(res, expires); expires != Instant::MAX @@ -55,9 +53,9 @@ impl TimerQueue { res } - pub(crate) unsafe fn dequeue_expired(&self, now: Instant, on_task: impl Fn(NonNull)) { + pub(crate) unsafe fn dequeue_expired(&self, now: Instant, on_task: impl Fn(TaskRef)) { self.retain(|p| { - let task = p.as_ref(); + let task = p.header(); if task.expires_at.get() <= now { on_task(p); false @@ -67,11 +65,10 @@ impl TimerQueue { }); } - pub(crate) unsafe fn retain(&self, mut f: impl FnMut(NonNull) -> bool) { + pub(crate) unsafe fn retain(&self, mut f: impl FnMut(TaskRef) -> bool) { let mut prev = &self.head; - while !prev.get().is_null() { - let p = NonNull::new_unchecked(prev.get()); - let task = &*p.as_ptr(); + while let Some(p) = prev.get() { + let task = p.header(); if f(p) { // Skip to next prev = &task.timer_queue_item.next; diff --git a/embassy-executor/src/raw/util.rs b/embassy-executor/src/raw/util.rs index ed5822188..e2e8f4df8 100644 --- a/embassy-executor/src/raw/util.rs +++ b/embassy-executor/src/raw/util.rs @@ -26,8 +26,31 @@ impl UninitCell { } } -impl UninitCell { - pub unsafe fn read(&self) -> T { - ptr::read(self.as_mut_ptr()) +unsafe impl Sync for UninitCell {} + +#[repr(transparent)] +pub struct SyncUnsafeCell { + value: UnsafeCell, +} + +unsafe impl Sync for SyncUnsafeCell {} + +impl SyncUnsafeCell { + #[inline] + pub const fn new(value: T) -> Self { + Self { + value: UnsafeCell::new(value), + } + } + + pub unsafe fn set(&self, value: T) { + *self.value.get() = value; + } + + pub unsafe fn get(&self) -> T + where + T: Copy, + { + *self.value.get() } } diff --git a/embassy-executor/src/raw/waker.rs b/embassy-executor/src/raw/waker.rs index 5765259f2..400b37fa9 100644 --- a/embassy-executor/src/raw/waker.rs +++ b/embassy-executor/src/raw/waker.rs @@ -1,8 +1,7 @@ use core::mem; -use core::ptr::NonNull; use core::task::{RawWaker, RawWakerVTable, Waker}; -use super::{wake_task, TaskHeader}; +use super::{wake_task, TaskHeader, TaskRef}; const VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake, drop); @@ -11,14 +10,14 @@ unsafe fn clone(p: *const ()) -> RawWaker { } unsafe fn wake(p: *const ()) { - wake_task(NonNull::new_unchecked(p as *mut TaskHeader)) + wake_task(TaskRef::from_ptr(p as *const TaskHeader)) } unsafe fn drop(_: *const ()) { // nop } -pub(crate) unsafe fn from_task(p: NonNull) -> Waker { +pub(crate) unsafe fn from_task(p: TaskRef) -> Waker { Waker::from_raw(RawWaker::new(p.as_ptr() as _, &VTABLE)) } @@ -33,7 +32,7 @@ pub(crate) unsafe fn from_task(p: NonNull) -> Waker { /// # Panics /// /// Panics if the waker is not created by the Embassy executor. -pub fn task_from_waker(waker: &Waker) -> NonNull { +pub fn task_from_waker(waker: &Waker) -> TaskRef { // safety: OK because WakerHack has the same layout as Waker. // This is not really guaranteed because the structs are `repr(Rust)`, it is // indeed the case in the current implementation. @@ -43,8 +42,8 @@ pub fn task_from_waker(waker: &Waker) -> NonNull { panic!("Found waker not created by the Embassy executor. `embassy_time::Timer` only works with the Embassy executor.") } - // safety: we never create a waker with a null data pointer. - unsafe { NonNull::new_unchecked(hack.data as *mut TaskHeader) } + // safety: our wakers are always created with `TaskRef::as_ptr` + unsafe { TaskRef::from_ptr(hack.data as *const TaskHeader) } } struct WakerHack { diff --git a/embassy-executor/src/raw/waker_turbo.rs b/embassy-executor/src/raw/waker_turbo.rs new file mode 100644 index 000000000..435a0ff7e --- /dev/null +++ b/embassy-executor/src/raw/waker_turbo.rs @@ -0,0 +1,34 @@ +use core::ptr::NonNull; +use core::task::Waker; + +use super::{wake_task, TaskHeader, TaskRef}; + +pub(crate) unsafe fn from_task(p: TaskRef) -> Waker { + Waker::from_turbo_ptr(NonNull::new_unchecked(p.as_ptr() as _)) +} + +/// Get a task pointer from a waker. +/// +/// This can be used as an optimization in wait queues to store task pointers +/// (1 word) instead of full Wakers (2 words). This saves a bit of RAM and helps +/// avoid dynamic dispatch. +/// +/// You can use the returned task pointer to wake the task with [`wake_task`](super::wake_task). +/// +/// # Panics +/// +/// Panics if the waker is not created by the Embassy executor. +pub fn task_from_waker(waker: &Waker) -> TaskRef { + let ptr = waker.as_turbo_ptr().as_ptr(); + + // safety: our wakers are always created with `TaskRef::as_ptr` + unsafe { TaskRef::from_ptr(ptr as *const TaskHeader) } +} + +#[inline(never)] +#[no_mangle] +fn _turbo_wake(ptr: NonNull<()>) { + // safety: our wakers are always created with `TaskRef::as_ptr` + let task = unsafe { TaskRef::from_ptr(ptr.as_ptr() as *const TaskHeader) }; + wake_task(task) +} diff --git a/embassy-executor/src/spawner.rs b/embassy-executor/src/spawner.rs index 25a0d7dbb..2b6224045 100644 --- a/embassy-executor/src/spawner.rs +++ b/embassy-executor/src/spawner.rs @@ -1,10 +1,8 @@ +use core::future::poll_fn; use core::marker::PhantomData; use core::mem; -use core::ptr::NonNull; use core::task::Poll; -use futures_util::future::poll_fn; - use super::raw; /// Token to spawn a newly-created task in an executor. @@ -23,12 +21,12 @@ use super::raw; /// Once you've invoked a task function and obtained a SpawnToken, you *must* spawn it. #[must_use = "Calling a task function does nothing on its own. You must spawn the returned SpawnToken, typically with Spawner::spawn()"] pub struct SpawnToken { - raw_task: Option>, + raw_task: Option, phantom: PhantomData<*mut S>, } impl SpawnToken { - pub(crate) unsafe fn new(raw_task: NonNull) -> Self { + pub(crate) unsafe fn new(raw_task: raw::TaskRef) -> Self { Self { raw_task: Some(raw_task), phantom: PhantomData, @@ -91,10 +89,11 @@ impl Spawner { /// /// Panics if the current executor is not an Embassy executor. pub async fn for_current_executor() -> Self { - poll_fn(|cx| unsafe { + poll_fn(|cx| { let task = raw::task_from_waker(cx.waker()); - let executor = (*task.as_ptr()).executor.get(); - Poll::Ready(Self::new(&*executor)) + let executor = unsafe { task.header().executor.get().unwrap_unchecked() }; + let executor = unsafe { raw::Executor::wrap(executor) }; + Poll::Ready(Self::new(executor)) }) .await } @@ -132,9 +131,7 @@ impl Spawner { /// spawner to other threads, but the spawner loses the ability to spawn /// non-Send tasks. pub fn make_send(&self) -> SendSpawner { - SendSpawner { - executor: self.executor, - } + SendSpawner::new(&self.executor.inner) } } @@ -147,14 +144,11 @@ impl Spawner { /// If you want to spawn non-Send tasks, use [Spawner]. #[derive(Copy, Clone)] pub struct SendSpawner { - executor: &'static raw::Executor, + executor: &'static raw::SyncExecutor, } -unsafe impl Send for SendSpawner {} -unsafe impl Sync for SendSpawner {} - impl SendSpawner { - pub(crate) fn new(executor: &'static raw::Executor) -> Self { + pub(crate) fn new(executor: &'static raw::SyncExecutor) -> Self { Self { executor } } @@ -167,10 +161,10 @@ impl SendSpawner { /// /// Panics if the current executor is not an Embassy executor. pub async fn for_current_executor() -> Self { - poll_fn(|cx| unsafe { + poll_fn(|cx| { let task = raw::task_from_waker(cx.waker()); - let executor = (*task.as_ptr()).executor.get(); - Poll::Ready(Self::new(&*executor)) + let executor = unsafe { task.header().executor.get().unwrap_unchecked() }; + Poll::Ready(Self::new(executor)) }) .await } diff --git a/embassy-futures/Cargo.toml b/embassy-futures/Cargo.toml index e564f5a96..89bb3af0b 100644 --- a/embassy-futures/Cargo.toml +++ b/embassy-futures/Cargo.toml @@ -2,13 +2,26 @@ name = "embassy-futures" version = "0.1.0" edition = "2021" +description = "no-std, no-alloc utilities for working with futures" +repository = "https://github.com/embassy-rs/embassy" +readme = "README.md" +license = "MIT OR Apache-2.0" +categories = [ + "embedded", + "no-std", + "concurrency", + "asynchronous", +] [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-futures-v$VERSION/embassy-futures/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-futures/src/" -features = ["nightly"] +features = ["defmt"] target = "thumbv7em-none-eabi" +[package.metadata.docs.rs] +features = ["defmt"] + [dependencies] defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } diff --git a/embassy-futures/README.md b/embassy-futures/README.md index 971f4c835..7add22c7b 100644 --- a/embassy-futures/README.md +++ b/embassy-futures/README.md @@ -1,9 +1,28 @@ # embassy-futures -Utilities for working with futures: +An [Embassy](https://embassy.dev) project. + +Utilities for working with futures, compatible with `no_std` and not using `alloc`. Optimized for code size, +ideal for embedded systems. + +- Future combinators, like [`join`](join) and [`select`](select) +- Utilities to use `async` without a fully fledged executor: [`block_on`](block_on::block_on) and [`yield_now`](yield_now::yield_now). + +## Interoperability + +Futures from this crate can run on any executor. + +## Minimum supported Rust version (MSRV) + +Embassy is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release. + +## License + +This work is licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. -- [`select`](select::select) - waiting for one out of two futures to complete. -- [`select3`](select::select3) - waiting for one out of three futures to complete. -- [`select4`](select::select4) - waiting for one out of four futures to complete. -- [`select_all`](select::select_all) - waiting for one future in a list of futures to complete. -- [`yield_now`](yield_now::yield_now) - yielding the current task. diff --git a/embassy-futures/src/block_on.rs b/embassy-futures/src/block_on.rs new file mode 100644 index 000000000..77695216c --- /dev/null +++ b/embassy-futures/src/block_on.rs @@ -0,0 +1,45 @@ +use core::future::Future; +use core::pin::Pin; +use core::ptr; +use core::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; + +static VTABLE: RawWakerVTable = RawWakerVTable::new(|_| RawWaker::new(ptr::null(), &VTABLE), |_| {}, |_| {}, |_| {}); + +/// Run a future to completion using a busy loop. +/// +/// This calls `.poll()` on the future in a busy loop, which blocks +/// the current thread at 100% cpu usage until the future is done. The +/// future's `Waker` mechanism is not used. +/// +/// You can use this to run multiple futures concurrently with [`join`][crate::join]. +/// +/// It's suitable for systems with no or limited concurrency and without +/// strict requirements around power consumption. For more complex use +/// cases, prefer using a "real" executor like `embassy-executor`, which +/// supports multiple tasks, and putting the core to sleep when no task +/// needs to do work. +pub fn block_on(mut fut: F) -> F::Output { + // safety: we don't move the future after this line. + let mut fut = unsafe { Pin::new_unchecked(&mut fut) }; + + let raw_waker = RawWaker::new(ptr::null(), &VTABLE); + let waker = unsafe { Waker::from_raw(raw_waker) }; + let mut cx = Context::from_waker(&waker); + loop { + if let Poll::Ready(res) = fut.as_mut().poll(&mut cx) { + return res; + } + } +} + +/// Poll a future once. +pub fn poll_once(mut fut: F) -> Poll { + // safety: we don't move the future after this line. + let mut fut = unsafe { Pin::new_unchecked(&mut fut) }; + + let raw_waker = RawWaker::new(ptr::null(), &VTABLE); + let waker = unsafe { Waker::from_raw(raw_waker) }; + let mut cx = Context::from_waker(&waker); + + fut.as_mut().poll(&mut cx) +} diff --git a/embassy-futures/src/fmt.rs b/embassy-futures/src/fmt.rs index f8bb0a035..066970813 100644 --- a/embassy-futures/src/fmt.rs +++ b/embassy-futures/src/fmt.rs @@ -195,9 +195,6 @@ macro_rules! unwrap { } } -#[cfg(feature = "defmt-timestamp-uptime")] -defmt::timestamp! {"{=u64:us}", crate::time::Instant::now().as_micros() } - #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct NoneError; diff --git a/embassy-futures/src/join.rs b/embassy-futures/src/join.rs new file mode 100644 index 000000000..bc0cb5303 --- /dev/null +++ b/embassy-futures/src/join.rs @@ -0,0 +1,322 @@ +//! Wait for multiple futures to complete. + +use core::future::Future; +use core::mem::MaybeUninit; +use core::pin::Pin; +use core::task::{Context, Poll}; +use core::{fmt, mem}; + +#[derive(Debug)] +enum MaybeDone { + /// A not-yet-completed future + Future(/* #[pin] */ Fut), + /// The output of the completed future + Done(Fut::Output), + /// The empty variant after the result of a [`MaybeDone`] has been + /// taken using the [`take_output`](MaybeDone::take_output) method. + Gone, +} + +impl MaybeDone { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> bool { + let this = unsafe { self.get_unchecked_mut() }; + match this { + Self::Future(fut) => match unsafe { Pin::new_unchecked(fut) }.poll(cx) { + Poll::Ready(res) => { + *this = Self::Done(res); + true + } + Poll::Pending => false, + }, + _ => true, + } + } + + fn take_output(&mut self) -> Fut::Output { + match &*self { + Self::Done(_) => {} + Self::Future(_) | Self::Gone => panic!("take_output when MaybeDone is not done."), + } + match mem::replace(self, Self::Gone) { + MaybeDone::Done(output) => output, + _ => unreachable!(), + } + } +} + +impl Unpin for MaybeDone {} + +macro_rules! generate { + ($( + $(#[$doc:meta])* + ($Join:ident, <$($Fut:ident),*>), + )*) => ($( + $(#[$doc])* + #[must_use = "futures do nothing unless you `.await` or poll them"] + #[allow(non_snake_case)] + pub struct $Join<$($Fut: Future),*> { + $( + $Fut: MaybeDone<$Fut>, + )* + } + + impl<$($Fut),*> fmt::Debug for $Join<$($Fut),*> + where + $( + $Fut: Future + fmt::Debug, + $Fut::Output: fmt::Debug, + )* + { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct(stringify!($Join)) + $(.field(stringify!($Fut), &self.$Fut))* + .finish() + } + } + + impl<$($Fut: Future),*> $Join<$($Fut),*> { + #[allow(non_snake_case)] + fn new($($Fut: $Fut),*) -> Self { + Self { + $($Fut: MaybeDone::Future($Fut)),* + } + } + } + + impl<$($Fut: Future),*> Future for $Join<$($Fut),*> { + type Output = ($($Fut::Output),*); + + fn poll( + self: Pin<&mut Self>, cx: &mut Context<'_> + ) -> Poll { + let this = unsafe { self.get_unchecked_mut() }; + let mut all_done = true; + $( + all_done &= unsafe { Pin::new_unchecked(&mut this.$Fut) }.poll(cx); + )* + + if all_done { + Poll::Ready(($(this.$Fut.take_output()), *)) + } else { + Poll::Pending + } + } + } + )*) +} + +generate! { + /// Future for the [`join`](join()) function. + (Join, ), + + /// Future for the [`join3`] function. + (Join3, ), + + /// Future for the [`join4`] function. + (Join4, ), + + /// Future for the [`join5`] function. + (Join5, ), +} + +/// Joins the result of two futures, waiting for them both to complete. +/// +/// This function will return a new future which awaits both futures to +/// complete. The returned future will finish with a tuple of both results. +/// +/// Note that this function consumes the passed futures and returns a +/// wrapped version of it. +/// +/// # Examples +/// +/// ``` +/// # embassy_futures::block_on(async { +/// +/// let a = async { 1 }; +/// let b = async { 2 }; +/// let pair = embassy_futures::join::join(a, b).await; +/// +/// assert_eq!(pair, (1, 2)); +/// # }); +/// ``` +pub fn join(future1: Fut1, future2: Fut2) -> Join +where + Fut1: Future, + Fut2: Future, +{ + Join::new(future1, future2) +} + +/// Joins the result of three futures, waiting for them all to complete. +/// +/// This function will return a new future which awaits all futures to +/// complete. The returned future will finish with a tuple of all results. +/// +/// Note that this function consumes the passed futures and returns a +/// wrapped version of it. +/// +/// # Examples +/// +/// ``` +/// # embassy_futures::block_on(async { +/// +/// let a = async { 1 }; +/// let b = async { 2 }; +/// let c = async { 3 }; +/// let res = embassy_futures::join::join3(a, b, c).await; +/// +/// assert_eq!(res, (1, 2, 3)); +/// # }); +/// ``` +pub fn join3(future1: Fut1, future2: Fut2, future3: Fut3) -> Join3 +where + Fut1: Future, + Fut2: Future, + Fut3: Future, +{ + Join3::new(future1, future2, future3) +} + +/// Joins the result of four futures, waiting for them all to complete. +/// +/// This function will return a new future which awaits all futures to +/// complete. The returned future will finish with a tuple of all results. +/// +/// Note that this function consumes the passed futures and returns a +/// wrapped version of it. +/// +/// # Examples +/// +/// ``` +/// # embassy_futures::block_on(async { +/// +/// let a = async { 1 }; +/// let b = async { 2 }; +/// let c = async { 3 }; +/// let d = async { 4 }; +/// let res = embassy_futures::join::join4(a, b, c, d).await; +/// +/// assert_eq!(res, (1, 2, 3, 4)); +/// # }); +/// ``` +pub fn join4( + future1: Fut1, + future2: Fut2, + future3: Fut3, + future4: Fut4, +) -> Join4 +where + Fut1: Future, + Fut2: Future, + Fut3: Future, + Fut4: Future, +{ + Join4::new(future1, future2, future3, future4) +} + +/// Joins the result of five futures, waiting for them all to complete. +/// +/// This function will return a new future which awaits all futures to +/// complete. The returned future will finish with a tuple of all results. +/// +/// Note that this function consumes the passed futures and returns a +/// wrapped version of it. +/// +/// # Examples +/// +/// ``` +/// # embassy_futures::block_on(async { +/// +/// let a = async { 1 }; +/// let b = async { 2 }; +/// let c = async { 3 }; +/// let d = async { 4 }; +/// let e = async { 5 }; +/// let res = embassy_futures::join::join5(a, b, c, d, e).await; +/// +/// assert_eq!(res, (1, 2, 3, 4, 5)); +/// # }); +/// ``` +pub fn join5( + future1: Fut1, + future2: Fut2, + future3: Fut3, + future4: Fut4, + future5: Fut5, +) -> Join5 +where + Fut1: Future, + Fut2: Future, + Fut3: Future, + Fut4: Future, + Fut5: Future, +{ + Join5::new(future1, future2, future3, future4, future5) +} + +// ===================================================== + +/// Future for the [`join_array`] function. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct JoinArray { + futures: [MaybeDone; N], +} + +impl fmt::Debug for JoinArray +where + Fut: Future + fmt::Debug, + Fut::Output: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("JoinArray").field("futures", &self.futures).finish() + } +} + +impl Future for JoinArray { + type Output = [Fut::Output; N]; + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = unsafe { self.get_unchecked_mut() }; + let mut all_done = true; + for f in this.futures.iter_mut() { + all_done &= unsafe { Pin::new_unchecked(f) }.poll(cx); + } + + if all_done { + let mut array: [MaybeUninit; N] = unsafe { MaybeUninit::uninit().assume_init() }; + for i in 0..N { + array[i].write(this.futures[i].take_output()); + } + Poll::Ready(unsafe { (&array as *const _ as *const [Fut::Output; N]).read() }) + } else { + Poll::Pending + } + } +} + +/// Joins the result of an array of futures, waiting for them all to complete. +/// +/// This function will return a new future which awaits all futures to +/// complete. The returned future will finish with a tuple of all results. +/// +/// Note that this function consumes the passed futures and returns a +/// wrapped version of it. +/// +/// # Examples +/// +/// ``` +/// # embassy_futures::block_on(async { +/// +/// async fn foo(n: u32) -> u32 { n } +/// let a = foo(1); +/// let b = foo(2); +/// let c = foo(3); +/// let res = embassy_futures::join::join_array([a, b, c]).await; +/// +/// assert_eq!(res, [1, 2, 3]); +/// # }); +/// ``` +pub fn join_array(futures: [Fut; N]) -> JoinArray { + JoinArray { + futures: futures.map(MaybeDone::Future), + } +} diff --git a/embassy-futures/src/lib.rs b/embassy-futures/src/lib.rs index 45bea2529..8c769bdfc 100644 --- a/embassy-futures/src/lib.rs +++ b/embassy-futures/src/lib.rs @@ -5,8 +5,11 @@ // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; -mod select; +mod block_on; mod yield_now; -pub use select::*; +pub mod join; +pub mod select; + +pub use block_on::*; pub use yield_now::*; diff --git a/embassy-futures/src/select.rs b/embassy-futures/src/select.rs index 8cecb7fa0..97a81a86d 100644 --- a/embassy-futures/src/select.rs +++ b/embassy-futures/src/select.rs @@ -1,9 +1,12 @@ +//! Wait for the first of several futures to complete. + use core::future::Future; use core::pin::Pin; use core::task::{Context, Poll}; /// Result for [`select`]. #[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Either { /// First future finished first. First(A), @@ -60,6 +63,7 @@ where /// Result for [`select3`]. #[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Either3 { /// First future finished first. First(A), @@ -118,6 +122,7 @@ where /// Result for [`select4`]. #[derive(Debug, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Either4 { /// First future finished first. First(A), @@ -183,28 +188,70 @@ where // ==================================================================== -/// Future for the [`select_all`] function. +/// Future for the [`select_array`] function. #[derive(Debug)] #[must_use = "futures do nothing unless you `.await` or poll them"] -pub struct SelectAll { +pub struct SelectArray { inner: [Fut; N], } -/// Creates a new future which will select over a list of futures. +/// Creates a new future which will select over an array of futures. /// -/// The returned future will wait for any future within `iter` to be ready. Upon +/// The returned future will wait for any future to be ready. Upon /// completion the item resolved will be returned, along with the index of the /// future that was ready. /// -/// # Panics -/// -/// This function will panic if the array specified contains no items. -pub fn select_all(arr: [Fut; N]) -> SelectAll { - assert!(N > 0); - SelectAll { inner: arr } +/// If the array is empty, the resulting future will be Pending forever. +pub fn select_array(arr: [Fut; N]) -> SelectArray { + SelectArray { inner: arr } } -impl Future for SelectAll { +impl Future for SelectArray { + type Output = (Fut::Output, usize); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // Safety: Since `self` is pinned, `inner` cannot move. Since `inner` cannot move, + // its elements also cannot move. Therefore it is safe to access `inner` and pin + // references to the contained futures. + let item = unsafe { + self.get_unchecked_mut() + .inner + .iter_mut() + .enumerate() + .find_map(|(i, f)| match Pin::new_unchecked(f).poll(cx) { + Poll::Pending => None, + Poll::Ready(e) => Some((i, e)), + }) + }; + + match item { + Some((idx, res)) => Poll::Ready((res, idx)), + None => Poll::Pending, + } + } +} + +// ==================================================================== + +/// Future for the [`select_slice`] function. +#[derive(Debug)] +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct SelectSlice<'a, Fut> { + inner: &'a mut [Fut], +} + +/// Creates a new future which will select over a slice of futures. +/// +/// The returned future will wait for any future to be ready. Upon +/// completion the item resolved will be returned, along with the index of the +/// future that was ready. +/// +/// If the slice is empty, the resulting future will be Pending forever. +pub fn select_slice<'a, Fut: Future>(slice: &'a mut [Fut]) -> SelectSlice<'a, Fut> { + SelectSlice { inner: slice } +} + +impl<'a, Fut: Future> Future for SelectSlice<'a, Fut> { type Output = (Fut::Output, usize); fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { diff --git a/embassy-futures/src/yield_now.rs b/embassy-futures/src/yield_now.rs index 1ebecb916..bb3c67d17 100644 --- a/embassy-futures/src/yield_now.rs +++ b/embassy-futures/src/yield_now.rs @@ -3,10 +3,28 @@ use core::pin::Pin; use core::task::{Context, Poll}; /// 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, } diff --git a/embassy-hal-common/Cargo.toml b/embassy-hal-common/Cargo.toml index 58f0af6ab..18c758d7b 100644 --- a/embassy-hal-common/Cargo.toml +++ b/embassy-hal-common/Cargo.toml @@ -2,11 +2,28 @@ name = "embassy-hal-common" version = "0.1.0" edition = "2021" +license = "MIT OR Apache-2.0" [features] +# Define the number of NVIC priority bits. +prio-bits-0 = [] +prio-bits-1 = [] +prio-bits-2 = [] +prio-bits-3 = [] +prio-bits-4 = [] +prio-bits-5 = [] +prio-bits-6 = [] +prio-bits-7 = [] +prio-bits-8 = [] + +cortex-m = ["dep:cortex-m", "dep:critical-section"] + [dependencies] defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } num-traits = { version = "0.2.14", default-features = false } + +cortex-m = { version = "0.7.6", optional = true } +critical-section = { version = "1", optional = true } \ No newline at end of file diff --git a/embassy-cortex-m/build.rs b/embassy-hal-common/build.rs similarity index 100% rename from embassy-cortex-m/build.rs rename to embassy-hal-common/build.rs diff --git a/embassy-hal-common/src/atomic_ring_buffer.rs b/embassy-hal-common/src/atomic_ring_buffer.rs new file mode 100644 index 000000000..ea84925c4 --- /dev/null +++ b/embassy-hal-common/src/atomic_ring_buffer.rs @@ -0,0 +1,556 @@ +use core::slice; +use core::sync::atomic::{AtomicPtr, AtomicUsize, Ordering}; + +/// Atomic reusable ringbuffer +/// +/// This ringbuffer implementation is designed to be stored in a `static`, +/// therefore all methods take `&self` and not `&mut self`. +/// +/// It is "reusable": when created it has no backing buffer, you can give it +/// one with `init` and take it back with `deinit`, and init it again in the +/// future if needed. This is very non-idiomatic, but helps a lot when storing +/// it in a `static`. +/// +/// One concurrent writer and one concurrent reader are supported, even at +/// different execution priorities (like main and irq). +pub struct RingBuffer { + pub buf: AtomicPtr, + pub len: AtomicUsize, + + // start and end wrap at len*2, not at len. + // This allows distinguishing "full" and "empty". + // full is when start+len == end (modulo len*2) + // empty is when start == end + // + // This avoids having to consider the ringbuffer "full" at len-1 instead of len. + // The usual solution is adding a "full" flag, but that can't be made atomic + pub start: AtomicUsize, + pub end: AtomicUsize, +} + +pub struct Reader<'a>(&'a RingBuffer); +pub struct Writer<'a>(&'a RingBuffer); + +impl RingBuffer { + /// Create a new empty ringbuffer. + pub const fn new() -> Self { + Self { + buf: AtomicPtr::new(core::ptr::null_mut()), + len: AtomicUsize::new(0), + start: AtomicUsize::new(0), + end: AtomicUsize::new(0), + } + } + + /// Initialize the ring buffer with a buffer. + /// + /// # Safety + /// - The buffer (`buf .. buf+len`) must be valid memory until `deinit` is called. + /// - Must not be called concurrently with any other methods. + pub unsafe fn init(&self, buf: *mut u8, len: usize) { + // Ordering: it's OK to use `Relaxed` because this is not called + // concurrently with other methods. + self.buf.store(buf, Ordering::Relaxed); + self.len.store(len, Ordering::Relaxed); + self.start.store(0, Ordering::Relaxed); + self.end.store(0, Ordering::Relaxed); + } + + /// Deinitialize the ringbuffer. + /// + /// After calling this, the ringbuffer becomes empty, as if it was + /// just created with `new()`. + /// + /// # Safety + /// - Must not be called concurrently with any other methods. + pub unsafe fn deinit(&self) { + // Ordering: it's OK to use `Relaxed` because this is not called + // concurrently with other methods. + self.len.store(0, Ordering::Relaxed); + self.start.store(0, Ordering::Relaxed); + self.end.store(0, Ordering::Relaxed); + } + + /// Create a reader. + /// + /// # Safety + /// + /// Only one reader can exist at a time. + pub unsafe fn reader(&self) -> Reader<'_> { + Reader(self) + } + + /// Create a writer. + /// + /// # Safety + /// + /// Only one writer can exist at a time. + pub unsafe fn writer(&self) -> Writer<'_> { + Writer(self) + } + + pub fn len(&self) -> usize { + self.len.load(Ordering::Relaxed) + } + + pub fn is_full(&self) -> bool { + let len = self.len.load(Ordering::Relaxed); + let start = self.start.load(Ordering::Relaxed); + let end = self.end.load(Ordering::Relaxed); + + self.wrap(start + len) == end + } + + pub fn is_empty(&self) -> bool { + let start = self.start.load(Ordering::Relaxed); + let end = self.end.load(Ordering::Relaxed); + + start == end + } + + fn wrap(&self, mut n: usize) -> usize { + let len = self.len.load(Ordering::Relaxed); + + if n >= len * 2 { + n -= len * 2 + } + n + } +} + +impl<'a> Writer<'a> { + /// Push data into the buffer in-place. + /// + /// The closure `f` is called with a free part of the buffer, it must write + /// some data to it and return the amount of bytes written. + pub fn push(&mut self, f: impl FnOnce(&mut [u8]) -> usize) -> usize { + let (p, n) = self.push_buf(); + let buf = unsafe { slice::from_raw_parts_mut(p, n) }; + let n = f(buf); + self.push_done(n); + n + } + + /// Push one data byte. + /// + /// Returns true if pushed successfully. + pub fn push_one(&mut self, val: u8) -> bool { + let n = self.push(|f| match f { + [] => 0, + [x, ..] => { + *x = val; + 1 + } + }); + n != 0 + } + + /// Get a buffer where data can be pushed to. + /// + /// Equivalent to [`Self::push_buf`] but returns a slice. + pub fn push_slice(&mut self) -> &mut [u8] { + let (data, len) = self.push_buf(); + unsafe { slice::from_raw_parts_mut(data, len) } + } + + /// Get up to two buffers where data can be pushed to. + /// + /// Equivalent to [`Self::push_bufs`] but returns slices. + pub fn push_slices(&mut self) -> [&mut [u8]; 2] { + let [(d0, l0), (d1, l1)] = self.push_bufs(); + unsafe { [slice::from_raw_parts_mut(d0, l0), slice::from_raw_parts_mut(d1, l1)] } + } + + /// Get a buffer where data can be pushed to. + /// + /// Write data to the start of the buffer, then call `push_done` with + /// however many bytes you've pushed. + /// + /// The buffer is suitable to DMA to. + /// + /// If the ringbuf is full, size=0 will be returned. + /// + /// The buffer stays valid as long as no other `Writer` method is called + /// and `init`/`deinit` aren't called on the ringbuf. + pub fn push_buf(&mut self) -> (*mut u8, usize) { + // Ordering: popping writes `start` last, so we read `start` first. + // Read it with Acquire ordering, so that the next accesses can't be reordered up past it. + let mut start = self.0.start.load(Ordering::Acquire); + let buf = self.0.buf.load(Ordering::Relaxed); + let len = self.0.len.load(Ordering::Relaxed); + let mut end = self.0.end.load(Ordering::Relaxed); + + let empty = start == end; + + if start >= len { + start -= len + } + if end >= len { + end -= len + } + + if start == end && !empty { + // full + return (buf, 0); + } + let n = if start > end { start - end } else { len - end }; + + trace!(" ringbuf: push_buf {:?}..{:?}", end, end + n); + (unsafe { buf.add(end) }, n) + } + + /// Get up to two buffers where data can be pushed to. + /// + /// Write data starting at the beginning of the first buffer, then call + /// `push_done` with however many bytes you've pushed. + /// + /// The buffers are suitable to DMA to. + /// + /// If the ringbuf is full, both buffers will be zero length. + /// If there is only area available, the second buffer will be zero length. + /// + /// The buffer stays valid as long as no other `Writer` method is called + /// and `init`/`deinit` aren't called on the ringbuf. + pub fn push_bufs(&mut self) -> [(*mut u8, usize); 2] { + // Ordering: as per push_buf() + let mut start = self.0.start.load(Ordering::Acquire); + let buf = self.0.buf.load(Ordering::Relaxed); + let len = self.0.len.load(Ordering::Relaxed); + let mut end = self.0.end.load(Ordering::Relaxed); + + let empty = start == end; + + if start >= len { + start -= len + } + if end >= len { + end -= len + } + + if start == end && !empty { + // full + return [(buf, 0), (buf, 0)]; + } + let n0 = if start > end { start - end } else { len - end }; + let n1 = if start <= end { start } else { 0 }; + + trace!(" ringbuf: push_bufs [{:?}..{:?}, {:?}..{:?}]", end, end + n0, 0, n1); + [(unsafe { buf.add(end) }, n0), (buf, n1)] + } + + pub fn push_done(&mut self, n: usize) { + trace!(" ringbuf: push {:?}", n); + let end = self.0.end.load(Ordering::Relaxed); + + // Ordering: write `end` last, with Release ordering. + // The ordering ensures no preceding memory accesses (such as writing + // the actual data in the buffer) can be reordered down past it, which + // will guarantee the reader sees them after reading from `end`. + self.0.end.store(self.0.wrap(end + n), Ordering::Release); + } +} + +impl<'a> Reader<'a> { + /// Pop data from the buffer in-place. + /// + /// The closure `f` is called with the next data, it must process + /// some data from it and return the amount of bytes processed. + pub fn pop(&mut self, f: impl FnOnce(&[u8]) -> usize) -> usize { + let (p, n) = self.pop_buf(); + let buf = unsafe { slice::from_raw_parts(p, n) }; + let n = f(buf); + self.pop_done(n); + n + } + + /// Pop one data byte. + /// + /// Returns true if popped successfully. + pub fn pop_one(&mut self) -> Option { + let mut res = None; + self.pop(|f| match f { + &[] => 0, + &[x, ..] => { + res = Some(x); + 1 + } + }); + res + } + + /// Get a buffer where data can be popped from. + /// + /// Equivalent to [`Self::pop_buf`] but returns a slice. + pub fn pop_slice(&mut self) -> &mut [u8] { + let (data, len) = self.pop_buf(); + unsafe { slice::from_raw_parts_mut(data, len) } + } + + /// Get a buffer where data can be popped from. + /// + /// Read data from the start of the buffer, then call `pop_done` with + /// however many bytes you've processed. + /// + /// The buffer is suitable to DMA from. + /// + /// If the ringbuf is empty, size=0 will be returned. + /// + /// The buffer stays valid as long as no other `Reader` method is called + /// and `init`/`deinit` aren't called on the ringbuf. + pub fn pop_buf(&mut self) -> (*mut u8, usize) { + // Ordering: pushing writes `end` last, so we read `end` first. + // Read it with Acquire ordering, so that the next accesses can't be reordered up past it. + // This is needed to guarantee we "see" the data written by the writer. + let mut end = self.0.end.load(Ordering::Acquire); + let buf = self.0.buf.load(Ordering::Relaxed); + let len = self.0.len.load(Ordering::Relaxed); + let mut start = self.0.start.load(Ordering::Relaxed); + + if start == end { + return (buf, 0); + } + + if start >= len { + start -= len + } + if end >= len { + end -= len + } + + let n = if end > start { end - start } else { len - start }; + + trace!(" ringbuf: pop_buf {:?}..{:?}", start, start + n); + (unsafe { buf.add(start) }, n) + } + + pub fn pop_done(&mut self, n: usize) { + trace!(" ringbuf: pop {:?}", n); + + let start = self.0.start.load(Ordering::Relaxed); + + // Ordering: write `start` last, with Release ordering. + // The ordering ensures no preceding memory accesses (such as reading + // the actual data) can be reordered down past it. This is necessary + // because writing to `start` is effectively freeing the read part of the + // buffer, which "gives permission" to the writer to write to it again. + // Therefore, all buffer accesses must be completed before this. + self.0.start.store(self.0.wrap(start + n), Ordering::Release); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn push_pop() { + let mut b = [0; 4]; + let rb = RingBuffer::new(); + unsafe { + rb.init(b.as_mut_ptr(), 4); + + assert_eq!(rb.is_empty(), true); + assert_eq!(rb.is_full(), false); + + rb.writer().push(|buf| { + assert_eq!(4, buf.len()); + buf[0] = 1; + buf[1] = 2; + buf[2] = 3; + buf[3] = 4; + 4 + }); + + assert_eq!(rb.is_empty(), false); + assert_eq!(rb.is_full(), true); + + rb.writer().push(|buf| { + // If it's full, we can push 0 bytes. + assert_eq!(0, buf.len()); + 0 + }); + + assert_eq!(rb.is_empty(), false); + assert_eq!(rb.is_full(), true); + + rb.reader().pop(|buf| { + assert_eq!(4, buf.len()); + assert_eq!(1, buf[0]); + 1 + }); + + assert_eq!(rb.is_empty(), false); + assert_eq!(rb.is_full(), false); + + rb.reader().pop(|buf| { + assert_eq!(3, buf.len()); + 0 + }); + + assert_eq!(rb.is_empty(), false); + assert_eq!(rb.is_full(), false); + + rb.reader().pop(|buf| { + assert_eq!(3, buf.len()); + assert_eq!(2, buf[0]); + assert_eq!(3, buf[1]); + 2 + }); + rb.reader().pop(|buf| { + assert_eq!(1, buf.len()); + assert_eq!(4, buf[0]); + 1 + }); + + assert_eq!(rb.is_empty(), true); + assert_eq!(rb.is_full(), false); + + rb.reader().pop(|buf| { + assert_eq!(0, buf.len()); + 0 + }); + + rb.writer().push(|buf| { + assert_eq!(4, buf.len()); + buf[0] = 10; + 1 + }); + + rb.writer().push(|buf| { + assert_eq!(3, buf.len()); + buf[0] = 11; + buf[1] = 12; + 2 + }); + + assert_eq!(rb.is_empty(), false); + assert_eq!(rb.is_full(), false); + + rb.writer().push(|buf| { + assert_eq!(1, buf.len()); + buf[0] = 13; + 1 + }); + + assert_eq!(rb.is_empty(), false); + assert_eq!(rb.is_full(), true); + } + } + + #[test] + fn zero_len() { + let rb = RingBuffer::new(); + unsafe { + assert_eq!(rb.is_empty(), true); + assert_eq!(rb.is_full(), true); + + rb.writer().push(|buf| { + assert_eq!(0, buf.len()); + 0 + }); + + rb.reader().pop(|buf| { + assert_eq!(0, buf.len()); + 0 + }); + } + } + + #[test] + fn push_slices() { + let mut b = [0; 4]; + let rb = RingBuffer::new(); + unsafe { + rb.init(b.as_mut_ptr(), 4); + + /* push 3 -> [1 2 3 x] */ + let mut w = rb.writer(); + let ps = w.push_slices(); + assert_eq!(4, ps[0].len()); + assert_eq!(0, ps[1].len()); + ps[0][0] = 1; + ps[0][1] = 2; + ps[0][2] = 3; + w.push_done(3); + drop(w); + + /* pop 2 -> [x x 3 x] */ + rb.reader().pop(|buf| { + assert_eq!(3, buf.len()); + assert_eq!(1, buf[0]); + assert_eq!(2, buf[1]); + assert_eq!(3, buf[2]); + 2 + }); + + /* push 3 -> [5 6 3 4] */ + let mut w = rb.writer(); + let ps = w.push_slices(); + assert_eq!(1, ps[0].len()); + assert_eq!(2, ps[1].len()); + ps[0][0] = 4; + ps[1][0] = 5; + ps[1][1] = 6; + w.push_done(3); + drop(w); + + /* buf is now full */ + let mut w = rb.writer(); + let ps = w.push_slices(); + assert_eq!(0, ps[0].len()); + assert_eq!(0, ps[1].len()); + + /* pop 2 -> [5 6 x x] */ + rb.reader().pop(|buf| { + assert_eq!(2, buf.len()); + assert_eq!(3, buf[0]); + assert_eq!(4, buf[1]); + 2 + }); + + /* should now have one push slice again */ + let mut w = rb.writer(); + let ps = w.push_slices(); + assert_eq!(2, ps[0].len()); + assert_eq!(0, ps[1].len()); + drop(w); + + /* pop 2 -> [x x x x] */ + rb.reader().pop(|buf| { + assert_eq!(2, buf.len()); + assert_eq!(5, buf[0]); + assert_eq!(6, buf[1]); + 2 + }); + + /* should now have two push slices */ + let mut w = rb.writer(); + let ps = w.push_slices(); + assert_eq!(2, ps[0].len()); + assert_eq!(2, ps[1].len()); + drop(w); + + /* make sure we exercise all wrap around cases properly */ + for _ in 0..10 { + /* should be empty, push 1 */ + let mut w = rb.writer(); + let ps = w.push_slices(); + assert_eq!(4, ps[0].len() + ps[1].len()); + w.push_done(1); + drop(w); + + /* should have 1 element */ + let mut w = rb.writer(); + let ps = w.push_slices(); + assert_eq!(3, ps[0].len() + ps[1].len()); + drop(w); + + /* pop 1 */ + rb.reader().pop(|buf| { + assert_eq!(1, buf.len()); + 1 + }); + } + } + } +} diff --git a/embassy-hal-common/src/drop.rs b/embassy-hal-common/src/drop.rs index 6ef716f99..7cd16aaec 100644 --- a/embassy-hal-common/src/drop.rs +++ b/embassy-hal-common/src/drop.rs @@ -1,6 +1,7 @@ use core::mem; use core::mem::MaybeUninit; +#[must_use = "to delay the drop handler invokation to the end of the scope"] pub struct OnDrop { f: MaybeUninit, } @@ -27,6 +28,7 @@ impl Drop for OnDrop { /// /// To correctly dispose of this device, call the [defuse](struct.DropBomb.html#method.defuse) /// method before this object is dropped. +#[must_use = "to delay the drop bomb invokation to the end of the scope"] pub struct DropBomb { _private: (), } diff --git a/embassy-cortex-m/src/interrupt.rs b/embassy-hal-common/src/interrupt.rs similarity index 67% rename from embassy-cortex-m/src/interrupt.rs rename to embassy-hal-common/src/interrupt.rs index 1df8671b9..b970aa2cd 100644 --- a/embassy-cortex-m/src/interrupt.rs +++ b/embassy-hal-common/src/interrupt.rs @@ -1,185 +1,209 @@ //! Interrupt handling for cortex-m devices. -use core::{mem, ptr}; +use core::mem; +use core::sync::atomic::{compiler_fence, Ordering}; -use atomic_polyfill::{compiler_fence, AtomicPtr, Ordering}; +use cortex_m::interrupt::InterruptNumber; use cortex_m::peripheral::NVIC; -use embassy_hal_common::Peripheral; -pub use embassy_macros::cortex_m_interrupt_take as take; -/// Do not use. Used for macros and HALs only. Not covered by semver guarantees. -#[doc(hidden)] -pub mod _export { - pub use atomic_polyfill as atomic; - pub use embassy_macros::{cortex_m_interrupt as interrupt, cortex_m_interrupt_declare as declare}; -} +/// Generate a standard `mod interrupt` for a HAL. +#[macro_export] +macro_rules! interrupt_mod { + ($($irqs:ident),* $(,)?) => { + #[cfg(feature = "rt")] + pub use cortex_m_rt::interrupt; -/// Implementation detail, do not use outside embassy crates. -#[doc(hidden)] -pub struct Handler { - pub func: AtomicPtr<()>, - pub ctx: AtomicPtr<()>, -} + /// Interrupt definitions. + pub mod interrupt { + pub use $crate::interrupt::{InterruptExt, Priority}; + pub use crate::pac::Interrupt::*; + pub use crate::pac::Interrupt; -impl Handler { - pub const fn new() -> Self { - Self { - func: AtomicPtr::new(ptr::null_mut()), - ctx: AtomicPtr::new(ptr::null_mut()), + /// Type-level interrupt infrastructure. + /// + /// This module contains one *type* per interrupt. This is used for checking at compile time that + /// the interrupts are correctly bound to HAL drivers. + /// + /// As an end user, you shouldn't need to use this module directly. Use the [`crate::bind_interrupts!`] macro + /// to bind interrupts, and the [`crate::interrupt`] module to manually register interrupt handlers and manipulate + /// interrupts directly (pending/unpending, enabling/disabling, setting the priority, etc...) + pub mod typelevel { + use super::InterruptExt; + + mod sealed { + pub trait Interrupt {} + } + + /// Type-level interrupt. + /// + /// This trait is implemented for all typelevel interrupt types in this module. + pub trait Interrupt: sealed::Interrupt { + + /// Interrupt enum variant. + /// + /// This allows going from typelevel interrupts (one type per interrupt) to + /// non-typelevel interrupts (a single `Interrupt` enum type, with one variant per interrupt). + const IRQ: super::Interrupt; + + /// Enable the interrupt. + #[inline] + unsafe fn enable() { + Self::IRQ.enable() + } + + /// Disable the interrupt. + #[inline] + fn disable() { + Self::IRQ.disable() + } + + /// Check if interrupt is enabled. + #[inline] + fn is_enabled() -> bool { + Self::IRQ.is_enabled() + } + + /// Check if interrupt is pending. + #[inline] + fn is_pending() -> bool { + Self::IRQ.is_pending() + } + + /// Set interrupt pending. + #[inline] + fn pend() { + Self::IRQ.pend() + } + + /// Unset interrupt pending. + #[inline] + fn unpend() { + Self::IRQ.unpend() + } + + /// Get the priority of the interrupt. + #[inline] + fn get_priority() -> crate::interrupt::Priority { + Self::IRQ.get_priority() + } + + /// Set the interrupt priority. + #[inline] + fn set_priority(prio: crate::interrupt::Priority) { + Self::IRQ.set_priority(prio) + } + } + + $( + #[allow(non_camel_case_types)] + #[doc=stringify!($irqs)] + #[doc=" typelevel interrupt."] + pub enum $irqs {} + impl sealed::Interrupt for $irqs{} + impl Interrupt for $irqs { + const IRQ: super::Interrupt = super::Interrupt::$irqs; + } + )* + + /// Interrupt handler trait. + /// + /// Drivers that need to handle interrupts implement this trait. + /// The user must ensure `on_interrupt()` is called every time the interrupt fires. + /// Drivers must use use [`Binding`] to assert at compile time that the user has done so. + pub trait Handler { + /// Interrupt handler function. + /// + /// Must be called every time the `I` interrupt fires, synchronously from + /// the interrupt handler context. + /// + /// # Safety + /// + /// This function must ONLY be called from the interrupt handler for `I`. + unsafe fn on_interrupt(); + } + + /// Compile-time assertion that an interrupt has been bound to a handler. + /// + /// For the vast majority of cases, you should use the `bind_interrupts!` + /// macro instead of writing `unsafe impl`s of this trait. + /// + /// # Safety + /// + /// By implementing this trait, you are asserting that you have arranged for `H::on_interrupt()` + /// to be called every time the `I` interrupt fires. + /// + /// This allows drivers to check bindings at compile-time. + pub unsafe trait Binding> {} + } } - } -} - -#[derive(Clone, Copy)] -pub(crate) struct NrWrap(pub(crate) u16); -unsafe impl cortex_m::interrupt::InterruptNumber for NrWrap { - fn number(self) -> u16 { - self.0 - } + }; } /// Represents an interrupt type that can be configured by embassy to handle /// interrupts. -pub unsafe trait Interrupt: Peripheral

{ - /// Return the NVIC interrupt number for this interrupt. - fn number(&self) -> u16; - /// Steal an instance of this interrupt - /// - /// # Safety - /// - /// This may panic if the interrupt has already been stolen and configured. - unsafe fn steal() -> Self; - - /// Implementation detail, do not use outside embassy crates. - #[doc(hidden)] - unsafe fn __handler(&self) -> &'static Handler; -} - -/// Represents additional behavior for all interrupts. -pub trait InterruptExt: Interrupt { - /// Configure the interrupt handler for this interrupt. - /// - /// # Safety - /// - /// It is the responsibility of the caller to ensure the handler - /// points to a valid handler as long as interrupts are enabled. - fn set_handler(&self, func: unsafe fn(*mut ())); - - /// Remove the interrupt handler for this interrupt. - fn remove_handler(&self); - - /// Set point to a context that is passed to the interrupt handler when - /// an interrupt is pending. - /// - /// # Safety - /// - /// It is the responsibility of the caller to ensure the context - /// points to a valid handler as long as interrupts are enabled. - fn set_handler_context(&self, ctx: *mut ()); - - /// Enable the interrupt. Once enabled, the interrupt handler may - /// be called "any time". - fn enable(&self); +pub unsafe trait InterruptExt: InterruptNumber + Copy { + /// Enable the interrupt. + #[inline] + unsafe fn enable(self) { + compiler_fence(Ordering::SeqCst); + NVIC::unmask(self) + } /// Disable the interrupt. - fn disable(&self); + #[inline] + fn disable(self) { + NVIC::mask(self); + compiler_fence(Ordering::SeqCst); + } /// Check if interrupt is being handled. + #[inline] #[cfg(not(armv6m))] - fn is_active(&self) -> bool; + fn is_active(self) -> bool { + NVIC::is_active(self) + } /// Check if interrupt is enabled. - fn is_enabled(&self) -> bool; + #[inline] + fn is_enabled(self) -> bool { + NVIC::is_enabled(self) + } /// Check if interrupt is pending. - fn is_pending(&self) -> bool; + #[inline] + fn is_pending(self) -> bool { + NVIC::is_pending(self) + } /// Set interrupt pending. - fn pend(&self); + #[inline] + fn pend(self) { + NVIC::pend(self) + } /// Unset interrupt pending. - fn unpend(&self); + #[inline] + fn unpend(self) { + NVIC::unpend(self) + } /// Get the priority of the interrupt. - fn get_priority(&self) -> Priority; + #[inline] + fn get_priority(self) -> Priority { + Priority::from(NVIC::get_priority(self)) + } /// Set the interrupt priority. - fn set_priority(&self, prio: Priority); -} - -impl InterruptExt for T { - fn set_handler(&self, func: unsafe fn(*mut ())) { - compiler_fence(Ordering::SeqCst); - let handler = unsafe { self.__handler() }; - handler.func.store(func as *mut (), Ordering::Relaxed); - compiler_fence(Ordering::SeqCst); - } - - fn remove_handler(&self) { - compiler_fence(Ordering::SeqCst); - let handler = unsafe { self.__handler() }; - handler.func.store(ptr::null_mut(), Ordering::Relaxed); - compiler_fence(Ordering::SeqCst); - } - - fn set_handler_context(&self, ctx: *mut ()) { - let handler = unsafe { self.__handler() }; - handler.ctx.store(ctx, Ordering::Relaxed); - } - #[inline] - fn enable(&self) { - compiler_fence(Ordering::SeqCst); - unsafe { - NVIC::unmask(NrWrap(self.number())); - } - } - - #[inline] - fn disable(&self) { - NVIC::mask(NrWrap(self.number())); - compiler_fence(Ordering::SeqCst); - } - - #[inline] - #[cfg(not(armv6m))] - fn is_active(&self) -> bool { - NVIC::is_active(NrWrap(self.number())) - } - - #[inline] - fn is_enabled(&self) -> bool { - NVIC::is_enabled(NrWrap(self.number())) - } - - #[inline] - fn is_pending(&self) -> bool { - NVIC::is_pending(NrWrap(self.number())) - } - - #[inline] - fn pend(&self) { - NVIC::pend(NrWrap(self.number())) - } - - #[inline] - fn unpend(&self) { - NVIC::unpend(NrWrap(self.number())) - } - - #[inline] - fn get_priority(&self) -> Priority { - Priority::from(NVIC::get_priority(NrWrap(self.number()))) - } - - #[inline] - fn set_priority(&self, prio: Priority) { - unsafe { + fn set_priority(self, prio: Priority) { + critical_section::with(|_| unsafe { let mut nvic: cortex_m::peripheral::NVIC = mem::transmute(()); - nvic.set_priority(NrWrap(self.number()), prio.into()) - } + nvic.set_priority(self, prio.into()) + }) } } +unsafe impl InterruptExt for T {} + impl From for Priority { fn from(priority: u8) -> Self { unsafe { mem::transmute(priority & PRIO_MASK) } diff --git a/embassy-hal-common/src/lib.rs b/embassy-hal-common/src/lib.rs index 5d2649d02..235964aa4 100644 --- a/embassy-hal-common/src/lib.rs +++ b/embassy-hal-common/src/lib.rs @@ -4,9 +4,13 @@ // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; +pub mod atomic_ring_buffer; pub mod drop; mod macros; mod peripheral; pub mod ratio; pub mod ring_buffer; pub use peripheral::{Peripheral, PeripheralRef}; + +#[cfg(feature = "cortex-m")] +pub mod interrupt; diff --git a/embassy-hal-common/src/macros.rs b/embassy-hal-common/src/macros.rs index 7af85f782..f06b46002 100644 --- a/embassy-hal-common/src/macros.rs +++ b/embassy-hal-common/src/macros.rs @@ -1,10 +1,12 @@ #[macro_export] -macro_rules! peripherals { +macro_rules! peripherals_definition { ($($(#[$cfg:meta])? $name:ident),*$(,)?) => { + /// Types for the peripheral singletons. pub mod peripherals { $( $(#[$cfg])? #[allow(non_camel_case_types)] + #[doc = concat!(stringify!($name), " peripheral")] pub struct $name { _private: () } $(#[$cfg])? @@ -24,10 +26,19 @@ macro_rules! peripherals { $crate::impl_peripheral!($name); )* } + }; +} +#[macro_export] +macro_rules! peripherals_struct { + ($($(#[$cfg:meta])? $name:ident),*$(,)?) => { + /// Struct containing all the peripheral singletons. + /// + /// To obtain the peripherals, you must initialize the HAL, by calling [`crate::init`]. #[allow(non_snake_case)] pub struct Peripherals { $( + #[doc = concat!(stringify!($name), " peripheral")] $(#[$cfg])? pub $name: peripherals::$name, )* @@ -70,6 +81,24 @@ macro_rules! peripherals { }; } +#[macro_export] +macro_rules! peripherals { + ($($(#[$cfg:meta])? $name:ident),*$(,)?) => { + $crate::peripherals_definition!( + $( + $(#[$cfg])? + $name, + )* + ); + $crate::peripherals_struct!( + $( + $(#[$cfg])? + $name, + )* + ); + }; +} + #[macro_export] macro_rules! into_ref { ($($name:ident),*) => { @@ -86,7 +115,7 @@ macro_rules! impl_peripheral { type P = $type; #[inline] - unsafe fn clone_unchecked(&mut self) -> Self::P { + unsafe fn clone_unchecked(&self) -> Self::P { $type { ..*self } } } diff --git a/embassy-hal-common/src/peripheral.rs b/embassy-hal-common/src/peripheral.rs index 038cebb5e..38b4c452e 100644 --- a/embassy-hal-common/src/peripheral.rs +++ b/embassy-hal-common/src/peripheral.rs @@ -3,16 +3,17 @@ use core::ops::{Deref, DerefMut}; /// An exclusive reference to a peripheral. /// -/// This is functionally the same as a `&'a mut T`. The reason for having a -/// dedicated struct is memory efficiency: +/// This is functionally the same as a `&'a mut T`. There's a few advantages in having +/// a dedicated struct instead: /// -/// Peripheral singletons are typically either zero-sized (for concrete peripehrals -/// like `PA9` or `Spi4`) or very small (for example `AnyPin` which is 1 byte). -/// However `&mut T` is always 4 bytes for 32-bit targets, even if T is zero-sized. -/// PeripheralRef stores a copy of `T` instead, so it's the same size. -/// -/// but it is the size of `T` not the size -/// of a pointer. This is useful if T is a zero sized type. +/// - Memory efficiency: Peripheral singletons are typically either zero-sized (for concrete +/// peripherals like `PA9` or `SPI4`) or very small (for example `AnyPin`, which is 1 byte). +/// However `&mut T` is always 4 bytes for 32-bit targets, even if T is zero-sized. +/// PeripheralRef stores a copy of `T` instead, so it's the same size. +/// - Code size efficiency. If the user uses the same driver with both `SPI4` and `&mut SPI4`, +/// the driver code would be monomorphized two times. With PeripheralRef, the driver is generic +/// over a lifetime only. `SPI4` becomes `PeripheralRef<'static, SPI4>`, and `&mut SPI4` becomes +/// `PeripheralRef<'a, SPI4>`. Lifetimes don't cause monomorphization. pub struct PeripheralRef<'a, T> { inner: T, _lifetime: PhantomData<&'a mut T>, @@ -38,7 +39,7 @@ impl<'a, T> PeripheralRef<'a, T> { /// You should strongly prefer using `reborrow()` instead. It returns a /// `PeripheralRef` that borrows `self`, which allows the borrow checker /// to enforce this at compile time. - pub unsafe fn clone_unchecked(&mut self) -> PeripheralRef<'a, T> + pub unsafe fn clone_unchecked(&self) -> PeripheralRef<'a, T> where T: Peripheral

, { @@ -145,14 +146,14 @@ pub trait Peripheral: Sized { /// /// You should strongly prefer using `into_ref()` instead. It returns a /// `PeripheralRef`, which allows the borrow checker to enforce this at compile time. - unsafe fn clone_unchecked(&mut self) -> Self::P; + unsafe fn clone_unchecked(&self) -> Self::P; /// Convert a value into a `PeripheralRef`. /// /// When called on an owned `T`, yields a `PeripheralRef<'static, T>`. /// When called on an `&'a mut T`, yields a `PeripheralRef<'a, T>`. #[inline] - fn into_ref<'a>(mut self) -> PeripheralRef<'a, Self::P> + fn into_ref<'a>(self) -> PeripheralRef<'a, Self::P> where Self: 'a, { @@ -167,7 +168,7 @@ where type P = ::P; #[inline] - unsafe fn clone_unchecked(&mut self) -> Self::P { - self.deref_mut().clone_unchecked() + unsafe fn clone_unchecked(&self) -> Self::P { + self.deref().clone_unchecked() } } diff --git a/embassy-lora/Cargo.toml b/embassy-lora/Cargo.toml index 9d5e7aed2..e4524af5b 100644 --- a/embassy-lora/Cargo.toml +++ b/embassy-lora/Cargo.toml @@ -2,37 +2,36 @@ name = "embassy-lora" version = "0.1.0" edition = "2021" +license = "MIT OR Apache-2.0" [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-lora-v$VERSION/embassy-lora/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-lora/src/" -features = ["time", "defmt"] -flavors = [ - { name = "sx127x", target = "thumbv7em-none-eabihf", features = ["sx127x", "embassy-stm32/stm32wl55jc-cm4", "embassy-stm32/time-driver-any", "embassy-time/tick-32768hz"] }, - { name = "stm32wl", target = "thumbv7em-none-eabihf", features = ["stm32wl", "embassy-stm32/stm32wl55jc-cm4", "embassy-stm32/time-driver-any", "embassy-time/tick-32768hz"] }, -] - -[lib] +features = ["stm32wl", "embassy-stm32?/stm32wl55jc-cm4", "embassy-stm32?/unstable-pac", "time", "defmt"] +target = "thumbv7em-none-eabi" [features] -sx127x = [] -stm32wl = ["embassy-stm32", "embassy-stm32/subghz"] +stm32wl = ["dep:embassy-stm32"] time = [] +defmt = ["dep:defmt", "lorawan-device/defmt"] [dependencies] defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } -embassy-time = { version = "0.1.0", path = "../embassy-time" } -embassy-sync = { version = "0.1.0", path = "../embassy-sync" } +embassy-time = { version = "0.1.2", path = "../embassy-time" } +embassy-sync = { version = "0.2.0", path = "../embassy-sync" } embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32", default-features = false, optional = true } -embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.8" } -embedded-hal-async = { version = "0.1.0-alpha.1" } +embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11" } +embedded-hal-async = { version = "=0.2.0-alpha.2" } embassy-hal-common = { version = "0.1.0", path = "../embassy-hal-common", default-features = false } futures = { version = "0.3.17", default-features = false, features = [ "async-await" ] } embedded-hal = { version = "0.2", features = ["unproven"] } bit_field = { version = "0.10" } -lorawan-device = { version = "0.7.1", default-features = false, features = ["async"] } -lorawan = { version = "0.7.1", default-features = false } +lora-phy = { version = "1" } +lorawan-device = { version = "0.10.0", default-features = false, features = ["async"] } + +[patch.crates-io] +lora-phy = { git = "https://github.com/embassy-rs/lora-phy", rev = "ad289428fd44b02788e2fa2116445cc8f640a265" } diff --git a/embassy-lora/src/iv.rs b/embassy-lora/src/iv.rs new file mode 100644 index 000000000..136973fe3 --- /dev/null +++ b/embassy-lora/src/iv.rs @@ -0,0 +1,325 @@ +#[cfg(feature = "stm32wl")] +use embassy_stm32::interrupt; +#[cfg(feature = "stm32wl")] +use embassy_stm32::interrupt::InterruptExt; +#[cfg(feature = "stm32wl")] +use embassy_stm32::pac; +#[cfg(feature = "stm32wl")] +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +#[cfg(feature = "stm32wl")] +use embassy_sync::signal::Signal; +use embedded_hal::digital::v2::OutputPin; +use embedded_hal_async::delay::DelayUs; +use embedded_hal_async::digital::Wait; +use lora_phy::mod_params::RadioError::*; +use lora_phy::mod_params::{BoardType, RadioError}; +use lora_phy::mod_traits::InterfaceVariant; + +/// Interrupt handler. +#[cfg(feature = "stm32wl")] +pub struct InterruptHandler {} + +#[cfg(feature = "stm32wl")] +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + interrupt::SUBGHZ_RADIO.disable(); + IRQ_SIGNAL.signal(()); + } +} + +#[cfg(feature = "stm32wl")] +static IRQ_SIGNAL: Signal = Signal::new(); + +#[cfg(feature = "stm32wl")] +/// Base for the InterfaceVariant implementation for an stm32wl/sx1262 combination +pub struct Stm32wlInterfaceVariant { + board_type: BoardType, + rf_switch_rx: Option, + rf_switch_tx: Option, +} + +#[cfg(feature = "stm32wl")] +impl<'a, CTRL> Stm32wlInterfaceVariant +where + CTRL: OutputPin, +{ + /// Create an InterfaceVariant instance for an stm32wl/sx1262 combination + pub fn new( + _irq: impl interrupt::typelevel::Binding, + rf_switch_rx: Option, + rf_switch_tx: Option, + ) -> Result { + interrupt::SUBGHZ_RADIO.disable(); + Ok(Self { + board_type: BoardType::Stm32wlSx1262, // updated when associated with a specific LoRa board + rf_switch_rx, + rf_switch_tx, + }) + } +} + +#[cfg(feature = "stm32wl")] +impl InterfaceVariant for Stm32wlInterfaceVariant +where + CTRL: OutputPin, +{ + fn set_board_type(&mut self, board_type: BoardType) { + self.board_type = board_type; + } + async fn set_nss_low(&mut self) -> Result<(), RadioError> { + let pwr = pac::PWR; + pwr.subghzspicr().modify(|w| w.set_nss(pac::pwr::vals::Nss::LOW)); + Ok(()) + } + async fn set_nss_high(&mut self) -> Result<(), RadioError> { + let pwr = pac::PWR; + pwr.subghzspicr().modify(|w| w.set_nss(pac::pwr::vals::Nss::HIGH)); + Ok(()) + } + async fn reset(&mut self, _delay: &mut impl DelayUs) -> Result<(), RadioError> { + let rcc = pac::RCC; + rcc.csr().modify(|w| w.set_rfrst(true)); + rcc.csr().modify(|w| w.set_rfrst(false)); + Ok(()) + } + async fn wait_on_busy(&mut self) -> Result<(), RadioError> { + let pwr = pac::PWR; + while pwr.sr2().read().rfbusys() == pac::pwr::vals::Rfbusys::BUSY {} + Ok(()) + } + + async fn await_irq(&mut self) -> Result<(), RadioError> { + unsafe { interrupt::SUBGHZ_RADIO.enable() }; + IRQ_SIGNAL.wait().await; + Ok(()) + } + + async fn enable_rf_switch_rx(&mut self) -> Result<(), RadioError> { + match &mut self.rf_switch_tx { + Some(pin) => pin.set_low().map_err(|_| RfSwitchTx)?, + None => (), + }; + match &mut self.rf_switch_rx { + Some(pin) => pin.set_high().map_err(|_| RfSwitchRx), + None => Ok(()), + } + } + async fn enable_rf_switch_tx(&mut self) -> Result<(), RadioError> { + match &mut self.rf_switch_rx { + Some(pin) => pin.set_low().map_err(|_| RfSwitchRx)?, + None => (), + }; + match &mut self.rf_switch_tx { + Some(pin) => pin.set_high().map_err(|_| RfSwitchTx), + None => Ok(()), + } + } + async fn disable_rf_switch(&mut self) -> Result<(), RadioError> { + match &mut self.rf_switch_rx { + Some(pin) => pin.set_low().map_err(|_| RfSwitchRx)?, + None => (), + }; + match &mut self.rf_switch_tx { + Some(pin) => pin.set_low().map_err(|_| RfSwitchTx), + None => Ok(()), + } + } +} + +/// Base for the InterfaceVariant implementation for an stm32l0/sx1276 combination +pub struct Stm32l0InterfaceVariant { + board_type: BoardType, + nss: CTRL, + reset: CTRL, + irq: WAIT, + rf_switch_rx: Option, + rf_switch_tx: Option, +} + +impl Stm32l0InterfaceVariant +where + CTRL: OutputPin, + WAIT: Wait, +{ + /// Create an InterfaceVariant instance for an stm32l0/sx1276 combination + pub fn new( + nss: CTRL, + reset: CTRL, + irq: WAIT, + rf_switch_rx: Option, + rf_switch_tx: Option, + ) -> Result { + Ok(Self { + board_type: BoardType::Stm32l0Sx1276, // updated when associated with a specific LoRa board + nss, + reset, + irq, + rf_switch_rx, + rf_switch_tx, + }) + } +} + +impl InterfaceVariant for Stm32l0InterfaceVariant +where + CTRL: OutputPin, + WAIT: Wait, +{ + fn set_board_type(&mut self, board_type: BoardType) { + self.board_type = board_type; + } + async fn set_nss_low(&mut self) -> Result<(), RadioError> { + self.nss.set_low().map_err(|_| NSS) + } + async fn set_nss_high(&mut self) -> Result<(), RadioError> { + self.nss.set_high().map_err(|_| NSS) + } + async fn reset(&mut self, delay: &mut impl DelayUs) -> Result<(), RadioError> { + delay.delay_ms(10).await; + self.reset.set_low().map_err(|_| Reset)?; + delay.delay_ms(10).await; + self.reset.set_high().map_err(|_| Reset)?; + delay.delay_ms(10).await; + Ok(()) + } + async fn wait_on_busy(&mut self) -> Result<(), RadioError> { + Ok(()) + } + async fn await_irq(&mut self) -> Result<(), RadioError> { + self.irq.wait_for_high().await.map_err(|_| Irq) + } + + async fn enable_rf_switch_rx(&mut self) -> Result<(), RadioError> { + match &mut self.rf_switch_tx { + Some(pin) => pin.set_low().map_err(|_| RfSwitchTx)?, + None => (), + }; + match &mut self.rf_switch_rx { + Some(pin) => pin.set_high().map_err(|_| RfSwitchRx), + None => Ok(()), + } + } + async fn enable_rf_switch_tx(&mut self) -> Result<(), RadioError> { + match &mut self.rf_switch_rx { + Some(pin) => pin.set_low().map_err(|_| RfSwitchRx)?, + None => (), + }; + match &mut self.rf_switch_tx { + Some(pin) => pin.set_high().map_err(|_| RfSwitchTx), + None => Ok(()), + } + } + async fn disable_rf_switch(&mut self) -> Result<(), RadioError> { + match &mut self.rf_switch_rx { + Some(pin) => pin.set_low().map_err(|_| RfSwitchRx)?, + None => (), + }; + match &mut self.rf_switch_tx { + Some(pin) => pin.set_low().map_err(|_| RfSwitchTx), + None => Ok(()), + } + } +} + +/// Base for the InterfaceVariant implementation for a generic Sx126x LoRa board +pub struct GenericSx126xInterfaceVariant { + board_type: BoardType, + nss: CTRL, + reset: CTRL, + dio1: WAIT, + busy: WAIT, + rf_switch_rx: Option, + rf_switch_tx: Option, +} + +impl GenericSx126xInterfaceVariant +where + CTRL: OutputPin, + WAIT: Wait, +{ + /// Create an InterfaceVariant instance for an nrf52840/sx1262 combination + pub fn new( + nss: CTRL, + reset: CTRL, + dio1: WAIT, + busy: WAIT, + rf_switch_rx: Option, + rf_switch_tx: Option, + ) -> Result { + Ok(Self { + board_type: BoardType::Rak4631Sx1262, // updated when associated with a specific LoRa board + nss, + reset, + dio1, + busy, + rf_switch_rx, + rf_switch_tx, + }) + } +} + +impl InterfaceVariant for GenericSx126xInterfaceVariant +where + CTRL: OutputPin, + WAIT: Wait, +{ + fn set_board_type(&mut self, board_type: BoardType) { + self.board_type = board_type; + } + async fn set_nss_low(&mut self) -> Result<(), RadioError> { + self.nss.set_low().map_err(|_| NSS) + } + async fn set_nss_high(&mut self) -> Result<(), RadioError> { + self.nss.set_high().map_err(|_| NSS) + } + async fn reset(&mut self, delay: &mut impl DelayUs) -> Result<(), RadioError> { + delay.delay_ms(10).await; + self.reset.set_low().map_err(|_| Reset)?; + delay.delay_ms(20).await; + self.reset.set_high().map_err(|_| Reset)?; + delay.delay_ms(10).await; + Ok(()) + } + async fn wait_on_busy(&mut self) -> Result<(), RadioError> { + self.busy.wait_for_low().await.map_err(|_| Busy) + } + async fn await_irq(&mut self) -> Result<(), RadioError> { + if self.board_type != BoardType::RpPicoWaveshareSx1262 { + self.dio1.wait_for_high().await.map_err(|_| DIO1)?; + } else { + self.dio1.wait_for_rising_edge().await.map_err(|_| DIO1)?; + } + Ok(()) + } + + async fn enable_rf_switch_rx(&mut self) -> Result<(), RadioError> { + match &mut self.rf_switch_tx { + Some(pin) => pin.set_low().map_err(|_| RfSwitchTx)?, + None => (), + }; + match &mut self.rf_switch_rx { + Some(pin) => pin.set_high().map_err(|_| RfSwitchRx), + None => Ok(()), + } + } + async fn enable_rf_switch_tx(&mut self) -> Result<(), RadioError> { + match &mut self.rf_switch_rx { + Some(pin) => pin.set_low().map_err(|_| RfSwitchRx)?, + None => (), + }; + match &mut self.rf_switch_tx { + Some(pin) => pin.set_high().map_err(|_| RfSwitchTx), + None => Ok(()), + } + } + async fn disable_rf_switch(&mut self) -> Result<(), RadioError> { + match &mut self.rf_switch_rx { + Some(pin) => pin.set_low().map_err(|_| RfSwitchRx)?, + None => (), + }; + match &mut self.rf_switch_tx { + Some(pin) => pin.set_low().map_err(|_| RfSwitchTx), + None => Ok(()), + } + } +} diff --git a/embassy-lora/src/lib.rs b/embassy-lora/src/lib.rs index 1b2dd45c2..c23d1d0dd 100644 --- a/embassy-lora/src/lib.rs +++ b/embassy-lora/src/lib.rs @@ -1,23 +1,39 @@ #![no_std] -#![feature(type_alias_impl_trait)] -#![feature(generic_associated_types)] -//! embassy-lora is a collection of async radio drivers that integrate with the lorawan-device -//! crate's async LoRaWAN MAC implementation. +#![feature(async_fn_in_trait, impl_trait_projections)] +//! embassy-lora holds LoRa-specific functionality. pub(crate) mod fmt; -#[cfg(feature = "stm32wl")] -pub mod stm32wl; -#[cfg(feature = "sx127x")] -pub mod sx127x; +/// interface variants required by the external lora physical layer crate (lora-phy) +pub mod iv; + +#[cfg(feature = "time")] +use embassy_time::{Duration, Instant, Timer}; /// A convenience timer to use with the LoRaWAN crate -pub struct LoraTimer; +#[cfg(feature = "time")] +pub struct LoraTimer { + start: Instant, +} + +#[cfg(feature = "time")] +impl LoraTimer { + pub fn new() -> Self { + Self { start: Instant::now() } + } +} #[cfg(feature = "time")] impl lorawan_device::async_device::radio::Timer for LoraTimer { - type DelayFuture<'m> = impl core::future::Future + 'm; - fn delay_ms<'m>(&'m mut self, millis: u64) -> Self::DelayFuture<'m> { - embassy_time::Timer::after(embassy_time::Duration::from_millis(millis)) + fn reset(&mut self) { + self.start = Instant::now(); + } + + async fn at(&mut self, millis: u64) { + Timer::at(self.start + Duration::from_millis(millis)).await + } + + async fn delay_ms(&mut self, millis: u64) { + Timer::after(Duration::from_millis(millis)).await } } diff --git a/embassy-lora/src/stm32wl/mod.rs b/embassy-lora/src/stm32wl/mod.rs deleted file mode 100644 index 7822d0153..000000000 --- a/embassy-lora/src/stm32wl/mod.rs +++ /dev/null @@ -1,339 +0,0 @@ -//! A radio driver integration for the radio found on STM32WL family devices. -use core::future::Future; -use core::mem::MaybeUninit; - -use embassy_hal_common::{into_ref, PeripheralRef}; -use embassy_stm32::dma::NoDma; -use embassy_stm32::gpio::{AnyPin, Output}; -use embassy_stm32::interrupt::{InterruptExt, SUBGHZ_RADIO}; -use embassy_stm32::subghz::{ - CalibrateImage, CfgIrq, CodingRate, Error, HeaderType, Irq, LoRaBandwidth, LoRaModParams, LoRaPacketParams, - LoRaSyncWord, Ocp, PaConfig, PaSel, PacketType, RampTime, RegMode, RfFreq, SpreadingFactor as SF, StandbyClk, - Status, SubGhz, TcxoMode, TcxoTrim, Timeout, TxParams, -}; -use embassy_stm32::Peripheral; -use embassy_sync::signal::Signal; -use lorawan_device::async_device::radio::{Bandwidth, PhyRxTx, RfConfig, RxQuality, SpreadingFactor, TxConfig}; -use lorawan_device::async_device::Timings; - -#[derive(Debug, Copy, Clone)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum State { - Idle, - Txing, - Rxing, -} - -#[derive(Debug, Copy, Clone)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct RadioError; - -static IRQ: Signal<(Status, u16)> = Signal::new(); - -struct StateInner<'d> { - radio: SubGhz<'d, NoDma, NoDma>, - switch: RadioSwitch<'d>, -} - -/// External state storage for the radio state -pub struct SubGhzState<'a>(MaybeUninit>); -impl<'d> SubGhzState<'d> { - pub const fn new() -> Self { - Self(MaybeUninit::uninit()) - } -} - -/// The radio peripheral keeping the radio state and owning the radio IRQ. -pub struct SubGhzRadio<'d> { - state: *mut StateInner<'d>, - _irq: PeripheralRef<'d, SUBGHZ_RADIO>, -} - -impl<'d> SubGhzRadio<'d> { - /// Create a new instance of a SubGhz radio for LoRaWAN. - /// - /// # Safety - /// Do not leak self or futures - pub unsafe fn new( - state: &'d mut SubGhzState<'d>, - radio: SubGhz<'d, NoDma, NoDma>, - switch: RadioSwitch<'d>, - irq: impl Peripheral

+ 'd, - ) -> Self { - into_ref!(irq); - - let mut inner = StateInner { radio, switch }; - inner.radio.reset(); - - let state_ptr = state.0.as_mut_ptr(); - state_ptr.write(inner); - - irq.disable(); - irq.set_handler(|p| { - // This is safe because we only get interrupts when configured for, so - // the radio will be awaiting on the signal at this point. If not, the ISR will - // anyway only adjust the state in the IRQ signal state. - let state = &mut *(p as *mut StateInner<'d>); - state.on_interrupt(); - }); - irq.set_handler_context(state_ptr as *mut ()); - irq.enable(); - - Self { - state: state_ptr, - _irq: irq, - } - } -} - -impl<'d> StateInner<'d> { - /// Configure radio settings in preparation for TX or RX - pub(crate) fn configure(&mut self) -> Result<(), RadioError> { - trace!("Configuring STM32WL SUBGHZ radio"); - self.radio.set_standby(StandbyClk::Rc)?; - let tcxo_mode = TcxoMode::new() - .set_txco_trim(TcxoTrim::Volts1pt7) - .set_timeout(Timeout::from_duration_sat(core::time::Duration::from_millis(40))); - - self.radio.set_tcxo_mode(&tcxo_mode)?; - self.radio.set_regulator_mode(RegMode::Ldo)?; - - self.radio.calibrate_image(CalibrateImage::ISM_863_870)?; - - self.radio.set_buffer_base_address(0, 0)?; - - self.radio - .set_pa_config(&PaConfig::new().set_pa_duty_cycle(0x1).set_hp_max(0x0).set_pa(PaSel::Lp))?; - - self.radio.set_pa_ocp(Ocp::Max140m)?; - - // let tx_params = TxParams::LP_14.set_ramp_time(RampTime::Micros40); - self.radio - .set_tx_params(&TxParams::new().set_ramp_time(RampTime::Micros40).set_power(0x0A))?; - - self.radio.set_packet_type(PacketType::LoRa)?; - self.radio.set_lora_sync_word(LoRaSyncWord::Public)?; - trace!("Done initializing STM32WL SUBGHZ radio"); - Ok(()) - } - - /// Perform a transmission with the given parameters and payload. Returns any time adjustements needed form - /// the upcoming RX window start. - async fn do_tx(&mut self, config: TxConfig, buf: &[u8]) -> Result { - //trace!("TX Request: {}", config); - trace!("TX START"); - self.switch.set_tx_lp(); - self.configure()?; - - self.radio - .set_rf_frequency(&RfFreq::from_frequency(config.rf.frequency))?; - - self.set_lora_mod_params(config.rf)?; - - let packet_params = LoRaPacketParams::new() - .set_preamble_len(8) - .set_header_type(HeaderType::Variable) - .set_payload_len(buf.len() as u8) - .set_crc_en(true) - .set_invert_iq(false); - - self.radio.set_lora_packet_params(&packet_params)?; - - let irq_cfg = CfgIrq::new() - .irq_enable_all(Irq::TxDone) - .irq_enable_all(Irq::RxDone) - .irq_enable_all(Irq::Timeout); - self.radio.set_irq_cfg(&irq_cfg)?; - - self.radio.set_buffer_base_address(0, 0)?; - self.radio.write_buffer(0, buf)?; - - self.radio.set_tx(Timeout::DISABLED)?; - - loop { - let (_status, irq_status) = IRQ.wait().await; - IRQ.reset(); - - if irq_status & Irq::TxDone.mask() != 0 { - let stats = self.radio.lora_stats()?; - let (status, error_mask) = self.radio.op_error()?; - trace!( - "TX done. Stats: {:?}. OP error: {:?}, mask {:?}", - stats, - status, - error_mask - ); - - return Ok(0); - } else if irq_status & Irq::Timeout.mask() != 0 { - trace!("TX timeout"); - return Err(RadioError); - } - } - } - - fn set_lora_mod_params(&mut self, config: RfConfig) -> Result<(), Error> { - let mod_params = LoRaModParams::new() - .set_sf(convert_spreading_factor(config.spreading_factor)) - .set_bw(convert_bandwidth(config.bandwidth)) - .set_cr(CodingRate::Cr45) - .set_ldro_en(true); - self.radio.set_lora_mod_params(&mod_params) - } - - /// Perform a radio receive operation with the radio config and receive buffer. The receive buffer must - /// be able to hold a single LoRaWAN packet. - async fn do_rx(&mut self, config: RfConfig, buf: &mut [u8]) -> Result<(usize, RxQuality), RadioError> { - assert!(buf.len() >= 255); - trace!("RX START"); - // trace!("Starting RX: {}", config); - self.switch.set_rx(); - self.configure()?; - - self.radio.set_rf_frequency(&RfFreq::from_frequency(config.frequency))?; - - self.set_lora_mod_params(config)?; - - let packet_params = LoRaPacketParams::new() - .set_preamble_len(8) - .set_header_type(HeaderType::Variable) - .set_payload_len(0xFF) - .set_crc_en(true) - .set_invert_iq(true); - self.radio.set_lora_packet_params(&packet_params)?; - - let irq_cfg = CfgIrq::new() - .irq_enable_all(Irq::RxDone) - .irq_enable_all(Irq::PreambleDetected) - .irq_enable_all(Irq::HeaderErr) - .irq_enable_all(Irq::Timeout) - .irq_enable_all(Irq::Err); - self.radio.set_irq_cfg(&irq_cfg)?; - - self.radio.set_rx(Timeout::DISABLED)?; - trace!("RX started"); - - loop { - let (status, irq_status) = IRQ.wait().await; - IRQ.reset(); - trace!("RX IRQ {:?}, {:?}", status, irq_status); - if irq_status & Irq::RxDone.mask() != 0 { - let (status, len, ptr) = self.radio.rx_buffer_status()?; - - let packet_status = self.radio.lora_packet_status()?; - let rssi = packet_status.rssi_pkt().to_integer(); - let snr = packet_status.snr_pkt().to_integer(); - trace!( - "RX done. Received {} bytes. RX status: {:?}. Pkt status: {:?}", - len, - status.cmd(), - packet_status, - ); - self.radio.read_buffer(ptr, &mut buf[..len as usize])?; - self.radio.set_standby(StandbyClk::Rc)?; - return Ok((len as usize, RxQuality::new(rssi, snr as i8))); - } else if irq_status & (Irq::Timeout.mask() | Irq::TxDone.mask()) != 0 { - return Err(RadioError); - } - } - } - - /// Read interrupt status and store in global signal - fn on_interrupt(&mut self) { - let (status, irq_status) = self.radio.irq_status().expect("error getting irq status"); - self.radio - .clear_irq_status(irq_status) - .expect("error clearing irq status"); - if irq_status & Irq::PreambleDetected.mask() != 0 { - trace!("Preamble detected, ignoring"); - } else { - IRQ.signal((status, irq_status)); - } - } -} - -impl PhyRxTx for SubGhzRadio<'static> { - type PhyError = RadioError; - - type TxFuture<'m> = impl Future> + 'm; - fn tx<'m>(&'m mut self, config: TxConfig, buf: &'m [u8]) -> Self::TxFuture<'m> { - async move { - let inner = unsafe { &mut *self.state }; - inner.do_tx(config, buf).await - } - } - - type RxFuture<'m> = impl Future> + 'm; - fn rx<'m>(&'m mut self, config: RfConfig, buf: &'m mut [u8]) -> Self::RxFuture<'m> { - async move { - let inner = unsafe { &mut *self.state }; - inner.do_rx(config, buf).await - } - } -} - -impl From for RadioError { - fn from(_: embassy_stm32::spi::Error) -> Self { - RadioError - } -} - -impl<'d> Timings for SubGhzRadio<'d> { - fn get_rx_window_offset_ms(&self) -> i32 { - -200 - } - fn get_rx_window_duration_ms(&self) -> u32 { - 800 - } -} - -/// Represents the radio switch found on STM32WL based boards, used to control the radio for transmission or reception. -pub struct RadioSwitch<'d> { - ctrl1: Output<'d, AnyPin>, - ctrl2: Output<'d, AnyPin>, - ctrl3: Output<'d, AnyPin>, -} - -impl<'d> RadioSwitch<'d> { - pub fn new(ctrl1: Output<'d, AnyPin>, ctrl2: Output<'d, AnyPin>, ctrl3: Output<'d, AnyPin>) -> Self { - Self { ctrl1, ctrl2, ctrl3 } - } - - pub(crate) fn set_rx(&mut self) { - self.ctrl1.set_high(); - self.ctrl2.set_low(); - self.ctrl3.set_high(); - } - - pub(crate) fn set_tx_lp(&mut self) { - self.ctrl1.set_high(); - self.ctrl2.set_high(); - self.ctrl3.set_high(); - } - - #[allow(dead_code)] - pub(crate) fn set_tx_hp(&mut self) { - self.ctrl2.set_high(); - self.ctrl1.set_low(); - self.ctrl3.set_high(); - } -} - -fn convert_spreading_factor(sf: SpreadingFactor) -> SF { - match sf { - SpreadingFactor::_7 => SF::Sf7, - SpreadingFactor::_8 => SF::Sf8, - SpreadingFactor::_9 => SF::Sf9, - SpreadingFactor::_10 => SF::Sf10, - SpreadingFactor::_11 => SF::Sf11, - SpreadingFactor::_12 => SF::Sf12, - } -} - -fn convert_bandwidth(bw: Bandwidth) -> LoRaBandwidth { - match bw { - Bandwidth::_125KHz => LoRaBandwidth::Bw125, - Bandwidth::_250KHz => LoRaBandwidth::Bw250, - Bandwidth::_500KHz => LoRaBandwidth::Bw500, - } -} diff --git a/embassy-lora/src/sx127x/mod.rs b/embassy-lora/src/sx127x/mod.rs deleted file mode 100644 index f47a9eb55..000000000 --- a/embassy-lora/src/sx127x/mod.rs +++ /dev/null @@ -1,217 +0,0 @@ -use core::future::Future; - -use embedded_hal::digital::v2::OutputPin; -use embedded_hal_async::digital::Wait; -use embedded_hal_async::spi::*; -use lorawan_device::async_device::radio::{Bandwidth, PhyRxTx, RfConfig, RxQuality, SpreadingFactor, TxConfig}; -use lorawan_device::async_device::Timings; - -mod sx127x_lora; -use sx127x_lora::{Error as RadioError, LoRa, RadioMode, IRQ}; - -/// Trait representing a radio switch for boards using the Sx127x radio. One some -/// boards, this will be a dummy implementation that does nothing. -pub trait RadioSwitch { - fn set_tx(&mut self); - fn set_rx(&mut self); -} - -/// Semtech Sx127x radio peripheral -pub struct Sx127xRadio -where - SPI: SpiBus + 'static, - E: 'static, - CS: OutputPin + 'static, - RESET: OutputPin + 'static, - I: Wait + 'static, - RFS: RadioSwitch + 'static, -{ - radio: LoRa, - rfs: RFS, - irq: I, -} - -#[derive(Debug, Copy, Clone)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum State { - Idle, - Txing, - Rxing, -} - -impl Sx127xRadio -where - SPI: SpiBus + 'static, - CS: OutputPin + 'static, - RESET: OutputPin + 'static, - I: Wait + 'static, - RFS: RadioSwitch + 'static, - E: 'static, -{ - pub async fn new( - spi: SPI, - cs: CS, - reset: RESET, - irq: I, - rfs: RFS, - ) -> Result> { - let mut radio = LoRa::new(spi, cs, reset); - radio.reset().await?; - Ok(Self { radio, irq, rfs }) - } -} - -impl Timings for Sx127xRadio -where - SPI: SpiBus + 'static, - CS: OutputPin + 'static, - RESET: OutputPin + 'static, - I: Wait + 'static, - RFS: RadioSwitch + 'static, -{ - fn get_rx_window_offset_ms(&self) -> i32 { - -500 - } - fn get_rx_window_duration_ms(&self) -> u32 { - 800 - } -} - -impl PhyRxTx for Sx127xRadio -where - SPI: SpiBus + 'static, - CS: OutputPin + 'static, - E: 'static, - RESET: OutputPin + 'static, - I: Wait + 'static, - RFS: RadioSwitch + 'static, -{ - type PhyError = Sx127xError; - - type TxFuture<'m> = impl Future> + 'm - where - SPI: 'm, - CS: 'm, - RESET: 'm, - E: 'm, - I: 'm, - RFS: 'm; - - fn tx<'m>(&'m mut self, config: TxConfig, buf: &'m [u8]) -> Self::TxFuture<'m> { - trace!("TX START"); - async move { - self.radio.set_mode(RadioMode::Stdby).await.ok().unwrap(); - self.rfs.set_tx(); - self.radio.set_tx_power(14, 0).await?; - self.radio.set_frequency(config.rf.frequency).await?; - // TODO: Modify radio to support other coding rates - self.radio.set_coding_rate_4(5).await?; - self.radio - .set_signal_bandwidth(bandwidth_to_i64(config.rf.bandwidth)) - .await?; - self.radio - .set_spreading_factor(spreading_factor_to_u8(config.rf.spreading_factor)) - .await?; - - self.radio.set_preamble_length(8).await?; - self.radio.set_lora_pa_ramp().await?; - self.radio.set_lora_sync_word().await?; - self.radio.set_invert_iq(false).await?; - self.radio.set_crc(true).await?; - - self.radio.set_dio0_tx_done().await?; - - self.radio.transmit_start(buf).await?; - - loop { - self.irq.wait_for_rising_edge().await.unwrap(); - self.radio.set_mode(RadioMode::Stdby).await.ok().unwrap(); - let irq = self.radio.clear_irq().await.ok().unwrap(); - if (irq & IRQ::IrqTxDoneMask.addr()) != 0 { - trace!("TX DONE"); - return Ok(0); - } - } - } - } - - type RxFuture<'m> = impl Future> + 'm - where - SPI: 'm, - CS: 'm, - RESET: 'm, - E: 'm, - I: 'm, - RFS: 'm; - - fn rx<'m>(&'m mut self, config: RfConfig, buf: &'m mut [u8]) -> Self::RxFuture<'m> { - trace!("RX START"); - async move { - self.rfs.set_rx(); - self.radio.reset_payload_length().await?; - self.radio.set_frequency(config.frequency).await?; - // TODO: Modify radio to support other coding rates - self.radio.set_coding_rate_4(5).await?; - self.radio - .set_signal_bandwidth(bandwidth_to_i64(config.bandwidth)) - .await?; - self.radio - .set_spreading_factor(spreading_factor_to_u8(config.spreading_factor)) - .await?; - - self.radio.set_preamble_length(8).await?; - self.radio.set_lora_sync_word().await?; - self.radio.set_invert_iq(true).await?; - self.radio.set_crc(true).await?; - - self.radio.set_dio0_rx_done().await?; - self.radio.set_mode(RadioMode::RxContinuous).await?; - - loop { - self.irq.wait_for_rising_edge().await.unwrap(); - self.radio.set_mode(RadioMode::Stdby).await.ok().unwrap(); - let irq = self.radio.clear_irq().await.ok().unwrap(); - if (irq & IRQ::IrqRxDoneMask.addr()) != 0 { - let rssi = self.radio.get_packet_rssi().await.unwrap_or(0) as i16; - let snr = self.radio.get_packet_snr().await.unwrap_or(0.0) as i8; - let response = if let Ok(size) = self.radio.read_packet_size().await { - self.radio.read_packet(buf).await?; - Ok((size, RxQuality::new(rssi, snr))) - } else { - Ok((0, RxQuality::new(rssi, snr))) - }; - trace!("RX DONE"); - return response; - } - } - } - } -} - -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct Sx127xError; - -impl From> for Sx127xError { - fn from(_: sx127x_lora::Error) -> Self { - Sx127xError - } -} - -fn spreading_factor_to_u8(sf: SpreadingFactor) -> u8 { - match sf { - SpreadingFactor::_7 => 7, - SpreadingFactor::_8 => 8, - SpreadingFactor::_9 => 9, - SpreadingFactor::_10 => 10, - SpreadingFactor::_11 => 11, - SpreadingFactor::_12 => 12, - } -} - -fn bandwidth_to_i64(bw: Bandwidth) -> i64 { - match bw { - Bandwidth::_125KHz => 125_000, - Bandwidth::_250KHz => 250_000, - Bandwidth::_500KHz => 500_000, - } -} diff --git a/embassy-lora/src/sx127x/sx127x_lora/mod.rs b/embassy-lora/src/sx127x/sx127x_lora/mod.rs deleted file mode 100644 index aacc9da22..000000000 --- a/embassy-lora/src/sx127x/sx127x_lora/mod.rs +++ /dev/null @@ -1,539 +0,0 @@ -// Copyright Charles Wade (https://github.com/mr-glt/sx127x_lora). Licensed under the Apache 2.0 -// license -// -// Modifications made to make the driver work with the rust-lorawan link layer. - -#![allow(dead_code)] - -use bit_field::BitField; -use embassy_time::{Duration, Timer}; -use embedded_hal::digital::v2::OutputPin; -use embedded_hal_async::spi::SpiBus; - -mod register; -pub use self::register::IRQ; -use self::register::{PaConfig, Register}; - -/// Provides high-level access to Semtech SX1276/77/78/79 based boards connected to a Raspberry Pi -pub struct LoRa { - spi: SPI, - cs: CS, - reset: RESET, - pub explicit_header: bool, - pub mode: RadioMode, -} - -#[allow(clippy::upper_case_acronyms)] -#[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Error { - Uninformative, - VersionMismatch(u8), - CS(CS), - Reset(RESET), - SPI(SPI), - Transmitting, -} - -use Error::*; - -use super::sx127x_lora::register::{FskDataModulationShaping, FskRampUpRamDown}; - -#[cfg(not(feature = "version_0x09"))] -const VERSION_CHECK: u8 = 0x12; - -#[cfg(feature = "version_0x09")] -const VERSION_CHECK: u8 = 0x09; - -impl LoRa -where - SPI: SpiBus, - CS: OutputPin, - RESET: OutputPin, -{ - /// Builds and returns a new instance of the radio. Only one instance of the radio should exist at a time. - /// This also preforms a hardware reset of the module and then puts it in standby. - pub fn new(spi: SPI, cs: CS, reset: RESET) -> Self { - Self { - spi, - cs, - reset, - explicit_header: true, - mode: RadioMode::Sleep, - } - } - - pub async fn reset(&mut self) -> Result<(), Error> { - self.reset.set_low().map_err(Reset)?; - Timer::after(Duration::from_millis(10)).await; - self.reset.set_high().map_err(Reset)?; - Timer::after(Duration::from_millis(10)).await; - let version = self.read_register(Register::RegVersion.addr()).await?; - if version == VERSION_CHECK { - self.set_mode(RadioMode::Sleep).await?; - self.write_register(Register::RegFifoTxBaseAddr.addr(), 0).await?; - self.write_register(Register::RegFifoRxBaseAddr.addr(), 0).await?; - let lna = self.read_register(Register::RegLna.addr()).await?; - self.write_register(Register::RegLna.addr(), lna | 0x03).await?; - self.write_register(Register::RegModemConfig3.addr(), 0x04).await?; - self.set_tcxo(true).await?; - self.set_mode(RadioMode::Stdby).await?; - self.cs.set_high().map_err(CS)?; - Ok(()) - } else { - Err(Error::VersionMismatch(version)) - } - } - - pub async fn set_dio0_tx_done(&mut self) -> Result<(), Error> { - self.write_register(Register::RegIrqFlagsMask.addr(), 0b1111_0111) - .await?; - let mapping = self.read_register(Register::RegDioMapping1.addr()).await?; - self.write_register(Register::RegDioMapping1.addr(), (mapping & 0x3F) | 0x40) - .await - } - - pub async fn set_dio0_rx_done(&mut self) -> Result<(), Error> { - self.write_register(Register::RegIrqFlagsMask.addr(), 0b0001_1111) - .await?; - let mapping = self.read_register(Register::RegDioMapping1.addr()).await?; - self.write_register(Register::RegDioMapping1.addr(), mapping & 0x3F) - .await - } - - pub async fn transmit_start(&mut self, buffer: &[u8]) -> Result<(), Error> { - assert!(buffer.len() < 255); - if self.transmitting().await? { - //trace!("ALREADY TRANSMNITTING"); - Err(Transmitting) - } else { - self.set_mode(RadioMode::Stdby).await?; - if self.explicit_header { - self.set_explicit_header_mode().await?; - } else { - self.set_implicit_header_mode().await?; - } - - self.write_register(Register::RegIrqFlags.addr(), 0).await?; - self.write_register(Register::RegFifoAddrPtr.addr(), 0).await?; - self.write_register(Register::RegPayloadLength.addr(), 0).await?; - for byte in buffer.iter() { - self.write_register(Register::RegFifo.addr(), *byte).await?; - } - self.write_register(Register::RegPayloadLength.addr(), buffer.len() as u8) - .await?; - self.set_mode(RadioMode::Tx).await?; - Ok(()) - } - } - - pub async fn packet_ready(&mut self) -> Result> { - Ok(self.read_register(Register::RegIrqFlags.addr()).await?.get_bit(6)) - } - - pub async fn irq_flags_mask(&mut self) -> Result> { - Ok(self.read_register(Register::RegIrqFlagsMask.addr()).await? as u8) - } - - pub async fn irq_flags(&mut self) -> Result> { - Ok(self.read_register(Register::RegIrqFlags.addr()).await? as u8) - } - - pub async fn read_packet_size(&mut self) -> Result> { - let size = self.read_register(Register::RegRxNbBytes.addr()).await?; - Ok(size as usize) - } - - /// Returns the contents of the fifo as a fixed 255 u8 array. This should only be called is there is a - /// new packet ready to be read. - pub async fn read_packet(&mut self, buffer: &mut [u8]) -> Result<(), Error> { - self.clear_irq().await?; - let size = self.read_register(Register::RegRxNbBytes.addr()).await?; - assert!(size as usize <= buffer.len()); - let fifo_addr = self.read_register(Register::RegFifoRxCurrentAddr.addr()).await?; - self.write_register(Register::RegFifoAddrPtr.addr(), fifo_addr).await?; - for i in 0..size { - let byte = self.read_register(Register::RegFifo.addr()).await?; - buffer[i as usize] = byte; - } - self.write_register(Register::RegFifoAddrPtr.addr(), 0).await?; - Ok(()) - } - - /// Returns true if the radio is currently transmitting a packet. - pub async fn transmitting(&mut self) -> Result> { - if (self.read_register(Register::RegOpMode.addr()).await?) & RadioMode::Tx.addr() == RadioMode::Tx.addr() { - Ok(true) - } else { - if (self.read_register(Register::RegIrqFlags.addr()).await? & IRQ::IrqTxDoneMask.addr()) == 1 { - self.write_register(Register::RegIrqFlags.addr(), IRQ::IrqTxDoneMask.addr()) - .await?; - } - Ok(false) - } - } - - /// Clears the radio's IRQ registers. - pub async fn clear_irq(&mut self) -> Result> { - let irq_flags = self.read_register(Register::RegIrqFlags.addr()).await?; - self.write_register(Register::RegIrqFlags.addr(), 0xFF).await?; - Ok(irq_flags) - } - - /// Sets the transmit power and pin. Levels can range from 0-14 when the output - /// pin = 0(RFO), and form 0-20 when output pin = 1(PaBoost). Power is in dB. - /// Default value is `17`. - pub async fn set_tx_power( - &mut self, - mut level: i32, - output_pin: u8, - ) -> Result<(), Error> { - if PaConfig::PaOutputRfoPin.addr() == output_pin { - // RFO - if level < 0 { - level = 0; - } else if level > 14 { - level = 14; - } - self.write_register(Register::RegPaConfig.addr(), (0x70 | level) as u8) - .await - } else { - // PA BOOST - if level > 17 { - if level > 20 { - level = 20; - } - // subtract 3 from level, so 18 - 20 maps to 15 - 17 - level -= 3; - - // High Power +20 dBm Operation (Semtech SX1276/77/78/79 5.4.3.) - self.write_register(Register::RegPaDac.addr(), 0x87).await?; - self.set_ocp(140).await?; - } else { - if level < 2 { - level = 2; - } - //Default value PA_HF/LF or +17dBm - self.write_register(Register::RegPaDac.addr(), 0x84).await?; - self.set_ocp(100).await?; - } - level -= 2; - self.write_register(Register::RegPaConfig.addr(), PaConfig::PaBoost.addr() | level as u8) - .await - } - } - - pub async fn get_modem_stat(&mut self) -> Result> { - Ok(self.read_register(Register::RegModemStat.addr()).await? as u8) - } - - /// Sets the over current protection on the radio(mA). - pub async fn set_ocp(&mut self, ma: u8) -> Result<(), Error> { - let mut ocp_trim: u8 = 27; - - if ma <= 120 { - ocp_trim = (ma - 45) / 5; - } else if ma <= 240 { - ocp_trim = (ma + 30) / 10; - } - self.write_register(Register::RegOcp.addr(), 0x20 | (0x1F & ocp_trim)) - .await - } - - /// Sets the state of the radio. Default mode after initiation is `Standby`. - pub async fn set_mode(&mut self, mode: RadioMode) -> Result<(), Error> { - if self.explicit_header { - self.set_explicit_header_mode().await?; - } else { - self.set_implicit_header_mode().await?; - } - self.write_register( - Register::RegOpMode.addr(), - RadioMode::LongRangeMode.addr() | mode.addr(), - ) - .await?; - - self.mode = mode; - Ok(()) - } - - pub async fn reset_payload_length(&mut self) -> Result<(), Error> { - self.write_register(Register::RegPayloadLength.addr(), 0xFF).await - } - - /// Sets the frequency of the radio. Values are in megahertz. - /// I.E. 915 MHz must be used for North America. Check regulation for your area. - pub async fn set_frequency(&mut self, freq: u32) -> Result<(), Error> { - const FREQ_STEP: f64 = 61.03515625; - // calculate register values - let frf = (freq as f64 / FREQ_STEP) as u32; - // write registers - self.write_register(Register::RegFrfMsb.addr(), ((frf & 0x00FF_0000) >> 16) as u8) - .await?; - self.write_register(Register::RegFrfMid.addr(), ((frf & 0x0000_FF00) >> 8) as u8) - .await?; - self.write_register(Register::RegFrfLsb.addr(), (frf & 0x0000_00FF) as u8) - .await - } - - /// Sets the radio to use an explicit header. Default state is `ON`. - async fn set_explicit_header_mode(&mut self) -> Result<(), Error> { - let reg_modem_config_1 = self.read_register(Register::RegModemConfig1.addr()).await?; - self.write_register(Register::RegModemConfig1.addr(), reg_modem_config_1 & 0xfe) - .await?; - self.explicit_header = true; - Ok(()) - } - - /// Sets the radio to use an implicit header. Default state is `OFF`. - async fn set_implicit_header_mode(&mut self) -> Result<(), Error> { - let reg_modem_config_1 = self.read_register(Register::RegModemConfig1.addr()).await?; - self.write_register(Register::RegModemConfig1.addr(), reg_modem_config_1 & 0x01) - .await?; - self.explicit_header = false; - Ok(()) - } - - /// Sets the spreading factor of the radio. Supported values are between 6 and 12. - /// If a spreading factor of 6 is set, implicit header mode must be used to transmit - /// and receive packets. Default value is `7`. - pub async fn set_spreading_factor(&mut self, mut sf: u8) -> Result<(), Error> { - if sf < 6 { - sf = 6; - } else if sf > 12 { - sf = 12; - } - - if sf == 6 { - self.write_register(Register::RegDetectionOptimize.addr(), 0xc5).await?; - self.write_register(Register::RegDetectionThreshold.addr(), 0x0c) - .await?; - } else { - self.write_register(Register::RegDetectionOptimize.addr(), 0xc3).await?; - self.write_register(Register::RegDetectionThreshold.addr(), 0x0a) - .await?; - } - let modem_config_2 = self.read_register(Register::RegModemConfig2.addr()).await?; - self.write_register( - Register::RegModemConfig2.addr(), - (modem_config_2 & 0x0f) | ((sf << 4) & 0xf0), - ) - .await?; - self.set_ldo_flag().await?; - - self.write_register(Register::RegSymbTimeoutLsb.addr(), 0x05).await?; - - Ok(()) - } - - pub async fn set_tcxo(&mut self, external: bool) -> Result<(), Error> { - if external { - self.write_register(Register::RegTcxo.addr(), 0x10).await - } else { - self.write_register(Register::RegTcxo.addr(), 0x00).await - } - } - - /// Sets the signal bandwidth of the radio. Supported values are: `7800 Hz`, `10400 Hz`, - /// `15600 Hz`, `20800 Hz`, `31250 Hz`,`41700 Hz` ,`62500 Hz`,`125000 Hz` and `250000 Hz` - /// Default value is `125000 Hz` - pub async fn set_signal_bandwidth(&mut self, sbw: i64) -> Result<(), Error> { - let bw: i64 = match sbw { - 7_800 => 0, - 10_400 => 1, - 15_600 => 2, - 20_800 => 3, - 31_250 => 4, - 41_700 => 5, - 62_500 => 6, - 125_000 => 7, - 250_000 => 8, - _ => 9, - }; - let modem_config_1 = self.read_register(Register::RegModemConfig1.addr()).await?; - self.write_register( - Register::RegModemConfig1.addr(), - (modem_config_1 & 0x0f) | ((bw << 4) as u8), - ) - .await?; - self.set_ldo_flag().await?; - Ok(()) - } - - /// Sets the coding rate of the radio with the numerator fixed at 4. Supported values - /// are between `5` and `8`, these correspond to coding rates of `4/5` and `4/8`. - /// Default value is `5`. - pub async fn set_coding_rate_4(&mut self, mut denominator: u8) -> Result<(), Error> { - if denominator < 5 { - denominator = 5; - } else if denominator > 8 { - denominator = 8; - } - let cr = denominator - 4; - let modem_config_1 = self.read_register(Register::RegModemConfig1.addr()).await?; - self.write_register(Register::RegModemConfig1.addr(), (modem_config_1 & 0xf1) | (cr << 1)) - .await - } - - /// Sets the preamble length of the radio. Values are between 6 and 65535. - /// Default value is `8`. - pub async fn set_preamble_length(&mut self, length: i64) -> Result<(), Error> { - self.write_register(Register::RegPreambleMsb.addr(), (length >> 8) as u8) - .await?; - self.write_register(Register::RegPreambleLsb.addr(), length as u8).await - } - - /// Enables are disables the radio's CRC check. Default value is `false`. - pub async fn set_crc(&mut self, value: bool) -> Result<(), Error> { - let modem_config_2 = self.read_register(Register::RegModemConfig2.addr()).await?; - if value { - self.write_register(Register::RegModemConfig2.addr(), modem_config_2 | 0x04) - .await - } else { - self.write_register(Register::RegModemConfig2.addr(), modem_config_2 & 0xfb) - .await - } - } - - /// Inverts the radio's IQ signals. Default value is `false`. - pub async fn set_invert_iq(&mut self, value: bool) -> Result<(), Error> { - if value { - self.write_register(Register::RegInvertiq.addr(), 0x66).await?; - self.write_register(Register::RegInvertiq2.addr(), 0x19).await - } else { - self.write_register(Register::RegInvertiq.addr(), 0x27).await?; - self.write_register(Register::RegInvertiq2.addr(), 0x1d).await - } - } - - /// Returns the spreading factor of the radio. - pub async fn get_spreading_factor(&mut self) -> Result> { - Ok(self.read_register(Register::RegModemConfig2.addr()).await? >> 4) - } - - /// Returns the signal bandwidth of the radio. - pub async fn get_signal_bandwidth(&mut self) -> Result> { - let bw = self.read_register(Register::RegModemConfig1.addr()).await? >> 4; - let bw = match bw { - 0 => 7_800, - 1 => 10_400, - 2 => 15_600, - 3 => 20_800, - 4 => 31_250, - 5 => 41_700, - 6 => 62_500, - 7 => 125_000, - 8 => 250_000, - 9 => 500_000, - _ => -1, - }; - Ok(bw) - } - - /// Returns the RSSI of the last received packet. - pub async fn get_packet_rssi(&mut self) -> Result> { - Ok(i32::from(self.read_register(Register::RegPktRssiValue.addr()).await?) - 157) - } - - /// Returns the signal to noise radio of the the last received packet. - pub async fn get_packet_snr(&mut self) -> Result> { - Ok(f64::from(self.read_register(Register::RegPktSnrValue.addr()).await?)) - } - - /// Returns the frequency error of the last received packet in Hz. - pub async fn get_packet_frequency_error(&mut self) -> Result> { - let mut freq_error: i32; - freq_error = i32::from(self.read_register(Register::RegFreqErrorMsb.addr()).await? & 0x7); - freq_error <<= 8i64; - freq_error += i32::from(self.read_register(Register::RegFreqErrorMid.addr()).await?); - freq_error <<= 8i64; - freq_error += i32::from(self.read_register(Register::RegFreqErrorLsb.addr()).await?); - - let f_xtal = 32_000_000; // FXOSC: crystal oscillator (XTAL) frequency (2.5. Chip Specification, p. 14) - let f_error = ((f64::from(freq_error) * (1i64 << 24) as f64) / f64::from(f_xtal)) - * (self.get_signal_bandwidth().await? as f64 / 500_000.0f64); // p. 37 - Ok(f_error as i64) - } - - async fn set_ldo_flag(&mut self) -> Result<(), Error> { - let sw = self.get_signal_bandwidth().await?; - // Section 4.1.1.5 - let symbol_duration = 1000 / (sw / ((1_i64) << self.get_spreading_factor().await?)); - - // Section 4.1.1.6 - let ldo_on = symbol_duration > 16; - - let mut config_3 = self.read_register(Register::RegModemConfig3.addr()).await?; - config_3.set_bit(3, ldo_on); - //config_3.set_bit(2, true); - self.write_register(Register::RegModemConfig3.addr(), config_3).await - } - - async fn read_register(&mut self, reg: u8) -> Result> { - let mut buffer = [reg & 0x7f, 0]; - self.cs.set_low().map_err(CS)?; - - let _ = self.spi.transfer(&mut buffer, &[reg & 0x7f, 0]).await.map_err(SPI)?; - - self.cs.set_high().map_err(CS)?; - Ok(buffer[1]) - } - - async fn write_register(&mut self, reg: u8, byte: u8) -> Result<(), Error> { - self.cs.set_low().map_err(CS)?; - let buffer = [reg | 0x80, byte]; - self.spi.write(&buffer).await.map_err(SPI)?; - self.cs.set_high().map_err(CS)?; - Ok(()) - } - - pub async fn put_in_fsk_mode(&mut self) -> Result<(), Error> { - // Put in FSK mode - let mut op_mode = 0; - op_mode - .set_bit(7, false) // FSK mode - .set_bits(5..6, 0x00) // FSK modulation - .set_bit(3, false) //Low freq registers - .set_bits(0..2, 0b011); // Mode - - self.write_register(Register::RegOpMode as u8, op_mode).await - } - - pub async fn set_fsk_pa_ramp( - &mut self, - modulation_shaping: FskDataModulationShaping, - ramp: FskRampUpRamDown, - ) -> Result<(), Error> { - let mut pa_ramp = 0; - pa_ramp - .set_bits(5..6, modulation_shaping as u8) - .set_bits(0..3, ramp as u8); - - self.write_register(Register::RegPaRamp as u8, pa_ramp).await - } - - pub async fn set_lora_pa_ramp(&mut self) -> Result<(), Error> { - self.write_register(Register::RegPaRamp as u8, 0b1000).await - } - - pub async fn set_lora_sync_word(&mut self) -> Result<(), Error> { - self.write_register(Register::RegSyncWord as u8, 0x34).await - } -} -/// Modes of the radio and their corresponding register values. -#[derive(Clone, Copy)] -pub enum RadioMode { - LongRangeMode = 0x80, - Sleep = 0x00, - Stdby = 0x01, - Tx = 0x03, - RxContinuous = 0x05, - RxSingle = 0x06, -} - -impl RadioMode { - /// Returns the address of the mode. - pub fn addr(self) -> u8 { - self as u8 - } -} diff --git a/embassy-lora/src/sx127x/sx127x_lora/register.rs b/embassy-lora/src/sx127x/sx127x_lora/register.rs deleted file mode 100644 index 2445e21b1..000000000 --- a/embassy-lora/src/sx127x/sx127x_lora/register.rs +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright Charles Wade (https://github.com/mr-glt/sx127x_lora). Licensed under the Apache 2.0 -// license -// -// Modifications made to make the driver work with the rust-lorawan link layer. -#![allow(dead_code, clippy::enum_variant_names)] - -#[derive(Clone, Copy)] -pub enum Register { - RegFifo = 0x00, - RegOpMode = 0x01, - RegFrfMsb = 0x06, - RegFrfMid = 0x07, - RegFrfLsb = 0x08, - RegPaConfig = 0x09, - RegPaRamp = 0x0a, - RegOcp = 0x0b, - RegLna = 0x0c, - RegFifoAddrPtr = 0x0d, - RegFifoTxBaseAddr = 0x0e, - RegFifoRxBaseAddr = 0x0f, - RegFifoRxCurrentAddr = 0x10, - RegIrqFlagsMask = 0x11, - RegIrqFlags = 0x12, - RegRxNbBytes = 0x13, - RegPktSnrValue = 0x19, - RegModemStat = 0x18, - RegPktRssiValue = 0x1a, - RegModemConfig1 = 0x1d, - RegModemConfig2 = 0x1e, - RegSymbTimeoutLsb = 0x1f, - RegPreambleMsb = 0x20, - RegPreambleLsb = 0x21, - RegPayloadLength = 0x22, - RegMaxPayloadLength = 0x23, - RegModemConfig3 = 0x26, - RegFreqErrorMsb = 0x28, - RegFreqErrorMid = 0x29, - RegFreqErrorLsb = 0x2a, - RegRssiWideband = 0x2c, - RegDetectionOptimize = 0x31, - RegInvertiq = 0x33, - RegDetectionThreshold = 0x37, - RegSyncWord = 0x39, - RegInvertiq2 = 0x3b, - RegDioMapping1 = 0x40, - RegVersion = 0x42, - RegTcxo = 0x4b, - RegPaDac = 0x4d, -} -#[derive(Clone, Copy)] -pub enum PaConfig { - PaBoost = 0x80, - PaOutputRfoPin = 0, -} - -#[derive(Clone, Copy)] -pub enum IRQ { - IrqTxDoneMask = 0x08, - IrqPayloadCrcErrorMask = 0x20, - IrqRxDoneMask = 0x40, -} - -impl Register { - pub fn addr(self) -> u8 { - self as u8 - } -} - -impl PaConfig { - pub fn addr(self) -> u8 { - self as u8 - } -} - -impl IRQ { - pub fn addr(self) -> u8 { - self as u8 - } -} - -#[derive(Clone, Copy)] -pub enum FskDataModulationShaping { - None = 1, - GaussianBt1d0 = 2, - GaussianBt0d5 = 10, - GaussianBt0d3 = 11, -} - -#[derive(Clone, Copy)] -pub enum FskRampUpRamDown { - _3d4ms = 0b000, - _2ms = 0b0001, - _1ms = 0b0010, - _500us = 0b0011, - _250us = 0b0100, - _125us = 0b0101, - _100us = 0b0110, - _62us = 0b0111, - _50us = 0b1000, - _40us = 0b1001, - _31us = 0b1010, - _25us = 0b1011, - _20us = 0b1100, - _15us = 0b1101, - _12us = 0b1110, - _10us = 0b1111, -} diff --git a/embassy-macros/Cargo.toml b/embassy-macros/Cargo.toml index 19a3e9de2..3b8fe8b44 100644 --- a/embassy-macros/Cargo.toml +++ b/embassy-macros/Cargo.toml @@ -1,17 +1,25 @@ [package] name = "embassy-macros" -version = "0.1.0" +version = "0.2.0" edition = "2021" +license = "MIT OR Apache-2.0" +description = "macros for creating the entry point and tasks for embassy-executor" +repository = "https://github.com/embassy-rs/embassy" +categories = [ + "embedded", + "no-std", + "asynchronous", +] [dependencies] -syn = { version = "1.0.76", features = ["full", "extra-traits"] } +syn = { version = "2.0.15", features = ["full", "extra-traits"] } quote = "1.0.9" -darling = "0.13.0" +darling = "0.20.1" proc-macro2 = "1.0.29" [lib] proc-macro = true [features] -std = [] -wasm = [] +# Enabling this cause interrupt::take! to require embassy-executor +rtos-trace-interrupt = [] diff --git a/embassy-macros/README.md b/embassy-macros/README.md new file mode 100644 index 000000000..d1d6f4cc4 --- /dev/null +++ b/embassy-macros/README.md @@ -0,0 +1,21 @@ +# embassy-macros + +An [Embassy](https://embassy.dev) project. + +Macros for creating the main entry point and tasks that can be spawned by `embassy-executor`. + +NOTE: The macros are re-exported by the `embassy-executor` crate which should be used instead of adding a direct dependency on the `embassy-macros` crate. + +## Minimum supported Rust version (MSRV) + +The `task` and `main` macros require the type alias impl trait (TAIT) nightly feature in order to compile. + +## License + +This work is licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. diff --git a/embassy-macros/src/lib.rs b/embassy-macros/src/lib.rs index ec8498f9f..c9d58746a 100644 --- a/embassy-macros/src/lib.rs +++ b/embassy-macros/src/lib.rs @@ -1,46 +1,175 @@ +#![doc = include_str!("../README.md")] extern crate proc_macro; +use darling::ast::NestedMeta; use proc_macro::TokenStream; mod macros; mod util; use macros::*; +use syn::parse::{Parse, ParseBuffer}; +use syn::punctuated::Punctuated; +use syn::Token; +struct Args { + meta: Vec, +} + +impl Parse for Args { + fn parse(input: &ParseBuffer) -> syn::Result { + let meta = Punctuated::::parse_terminated(input)?; + Ok(Args { + meta: meta.into_iter().collect(), + }) + } +} + +/// Declares an async task that can be run by `embassy-executor`. The optional `pool_size` parameter can be used to specify how +/// many concurrent tasks can be spawned (default is 1) for the function. +/// +/// +/// The following restrictions apply: +/// +/// * The function must be declared `async`. +/// * The function must not use generics. +/// * The optional `pool_size` attribute must be 1 or greater. +/// +/// +/// ## Examples +/// +/// Declaring a task taking no arguments: +/// +/// ``` rust +/// #[embassy_executor::task] +/// async fn mytask() { +/// // Function body +/// } +/// ``` +/// +/// Declaring a task with a given pool size: +/// +/// ``` rust +/// #[embassy_executor::task(pool_size = 4)] +/// async fn mytask() { +/// // Function body +/// } +/// ``` #[proc_macro_attribute] pub fn task(args: TokenStream, item: TokenStream) -> TokenStream { - let args = syn::parse_macro_input!(args as syn::AttributeArgs); + let args = syn::parse_macro_input!(args as Args); let f = syn::parse_macro_input!(item as syn::ItemFn); - task::run(args, f).unwrap_or_else(|x| x).into() + task::run(&args.meta, f).unwrap_or_else(|x| x).into() } -#[proc_macro_attribute] -pub fn main(args: TokenStream, item: TokenStream) -> TokenStream { - let args = syn::parse_macro_input!(args as syn::AttributeArgs); - let f = syn::parse_macro_input!(item as syn::ItemFn); - main::run(args, f).unwrap_or_else(|x| x).into() -} - -#[proc_macro_attribute] -pub fn cortex_m_interrupt(args: TokenStream, item: TokenStream) -> TokenStream { - let args = syn::parse_macro_input!(args as syn::AttributeArgs); - let f = syn::parse_macro_input!(item as syn::ItemFn); - cortex_m_interrupt::run(args, f).unwrap_or_else(|x| x).into() -} - -#[proc_macro] -pub fn cortex_m_interrupt_declare(item: TokenStream) -> TokenStream { - let name = syn::parse_macro_input!(item as syn::Ident); - cortex_m_interrupt_declare::run(name).unwrap_or_else(|x| x).into() -} - -/// # interrupt_take procedural macro +/// Creates a new `executor` instance and declares an application entry point for Cortex-M spawning the corresponding function body as an async task. /// -/// core::panic! is used as a default way to panic in this macro as there is no sensible way of enabling/disabling defmt for macro generation. -/// We are aware that this brings bloat in the form of core::fmt, but the bloat is already included with e.g. array indexing panics. -/// To get rid of this bloat, use the compiler flags `-Zbuild-std=core -Zbuild-std-features=panic_immediate_abort`. -#[proc_macro] -pub fn cortex_m_interrupt_take(item: TokenStream) -> TokenStream { - let name = syn::parse_macro_input!(item as syn::Ident); - cortex_m_interrupt_take::run(name).unwrap_or_else(|x| x).into() +/// The following restrictions apply: +/// +/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks. +/// * The function must be declared `async`. +/// * The function must not use generics. +/// * Only a single `main` task may be declared. +/// +/// ## Examples +/// Spawning a task: +/// +/// ``` rust +/// #[embassy_executor::main] +/// async fn main(_s: embassy_executor::Spawner) { +/// // Function body +/// } +/// ``` +#[proc_macro_attribute] +pub fn main_cortex_m(args: TokenStream, item: TokenStream) -> TokenStream { + let args = syn::parse_macro_input!(args as Args); + let f = syn::parse_macro_input!(item as syn::ItemFn); + main::run(&args.meta, f, main::cortex_m()).unwrap_or_else(|x| x).into() +} + +/// Creates a new `executor` instance and declares an application entry point for RISC-V spawning the corresponding function body as an async task. +/// +/// The following restrictions apply: +/// +/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks. +/// * The function must be declared `async`. +/// * The function must not use generics. +/// * Only a single `main` task may be declared. +/// +/// A user-defined entry macro can be optionally provided via the `entry` argument to override the default of `riscv_rt::entry`. +/// +/// ## Examples +/// Spawning a task: +/// +/// ``` rust +/// #[embassy_executor::main] +/// async fn main(_s: embassy_executor::Spawner) { +/// // Function body +/// } +/// ``` +/// +/// Spawning a task using a custom entry macro: +/// ``` rust +/// #[embassy_executor::main(entry = "esp_riscv_rt::entry")] +/// async fn main(_s: embassy_executor::Spawner) { +/// // Function body +/// } +/// ``` +#[proc_macro_attribute] +pub fn main_riscv(args: TokenStream, item: TokenStream) -> TokenStream { + let args = syn::parse_macro_input!(args as Args); + let f = syn::parse_macro_input!(item as syn::ItemFn); + main::run(&args.meta, f, main::riscv(&args.meta)) + .unwrap_or_else(|x| x) + .into() +} + +/// Creates a new `executor` instance and declares an application entry point for STD spawning the corresponding function body as an async task. +/// +/// The following restrictions apply: +/// +/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks. +/// * The function must be declared `async`. +/// * The function must not use generics. +/// * Only a single `main` task may be declared. +/// +/// ## Examples +/// Spawning a task: +/// +/// ``` rust +/// #[embassy_executor::main] +/// async fn main(_s: embassy_executor::Spawner) { +/// // Function body +/// } +/// ``` +#[proc_macro_attribute] +pub fn main_std(args: TokenStream, item: TokenStream) -> TokenStream { + let args = syn::parse_macro_input!(args as Args); + let f = syn::parse_macro_input!(item as syn::ItemFn); + main::run(&args.meta, f, main::std()).unwrap_or_else(|x| x).into() +} + +/// Creates a new `executor` instance and declares an application entry point for WASM spawning the corresponding function body as an async task. +/// +/// The following restrictions apply: +/// +/// * The function must accept exactly 1 parameter, an `embassy_executor::Spawner` handle that it can use to spawn additional tasks. +/// * The function must be declared `async`. +/// * The function must not use generics. +/// * Only a single `main` task may be declared. +/// +/// ## Examples +/// Spawning a task: +/// +/// ``` rust +/// #[embassy_executor::main] +/// async fn main(_s: embassy_executor::Spawner) { +/// // Function body +/// } +/// ``` +#[proc_macro_attribute] +pub fn main_wasm(args: TokenStream, item: TokenStream) -> TokenStream { + let args = syn::parse_macro_input!(args as Args); + let f = syn::parse_macro_input!(item as syn::ItemFn); + main::run(&args.meta, f, main::wasm()).unwrap_or_else(|x| x).into() } diff --git a/embassy-macros/src/macros/cortex_m_interrupt.rs b/embassy-macros/src/macros/cortex_m_interrupt.rs deleted file mode 100644 index 13af8ca07..000000000 --- a/embassy-macros/src/macros/cortex_m_interrupt.rs +++ /dev/null @@ -1,66 +0,0 @@ -use std::iter; - -use darling::FromMeta; -use proc_macro2::TokenStream; -use quote::quote; -use syn::{ReturnType, Type, Visibility}; - -use crate::util::ctxt::Ctxt; - -#[derive(Debug, FromMeta)] -struct Args {} - -pub fn run(args: syn::AttributeArgs, mut f: syn::ItemFn) -> Result { - let _args = Args::from_list(&args).map_err(|e| e.write_errors())?; - - let ident = f.sig.ident.clone(); - let ident_s = ident.to_string(); - - // XXX should we blacklist other attributes? - - let valid_signature = f.sig.constness.is_none() - && f.vis == Visibility::Inherited - && f.sig.abi.is_none() - && f.sig.inputs.is_empty() - && f.sig.generics.params.is_empty() - && f.sig.generics.where_clause.is_none() - && f.sig.variadic.is_none() - && match f.sig.output { - ReturnType::Default => true, - ReturnType::Type(_, ref ty) => match **ty { - Type::Tuple(ref tuple) => tuple.elems.is_empty(), - Type::Never(..) => true, - _ => false, - }, - }; - - let ctxt = Ctxt::new(); - - if !valid_signature { - ctxt.error_spanned_by( - &f.sig, - "`#[interrupt]` handlers must have signature `[unsafe] fn() [-> !]`", - ); - } - - ctxt.check()?; - - f.block.stmts = iter::once( - syn::parse2(quote! {{ - // Check that this interrupt actually exists - let __irq_exists_check: interrupt::#ident; - }}) - .unwrap(), - ) - .chain(f.block.stmts) - .collect(); - - let result = quote!( - #[doc(hidden)] - #[export_name = #ident_s] - #[allow(non_snake_case)] - #f - ); - - Ok(result) -} diff --git a/embassy-macros/src/macros/cortex_m_interrupt_declare.rs b/embassy-macros/src/macros/cortex_m_interrupt_declare.rs deleted file mode 100644 index ab61ad5da..000000000 --- a/embassy-macros/src/macros/cortex_m_interrupt_declare.rs +++ /dev/null @@ -1,31 +0,0 @@ -use proc_macro2::TokenStream; -use quote::{format_ident, quote}; - -pub fn run(name: syn::Ident) -> Result { - let name = format_ident!("{}", name); - let name_interrupt = format_ident!("{}", name); - let name_handler = format!("__EMBASSY_{}_HANDLER", name); - - let result = quote! { - #[allow(non_camel_case_types)] - pub struct #name_interrupt(()); - unsafe impl ::embassy_cortex_m::interrupt::Interrupt for #name_interrupt { - fn number(&self) -> u16 { - use cortex_m::interrupt::InterruptNumber; - let irq = InterruptEnum::#name; - irq.number() as u16 - } - unsafe fn steal() -> Self { - Self(()) - } - unsafe fn __handler(&self) -> &'static ::embassy_cortex_m::interrupt::Handler { - #[export_name = #name_handler] - static HANDLER: ::embassy_cortex_m::interrupt::Handler = ::embassy_cortex_m::interrupt::Handler::new(); - &HANDLER - } - } - - ::embassy_hal_common::impl_peripheral!(#name_interrupt); - }; - Ok(result) -} diff --git a/embassy-macros/src/macros/cortex_m_interrupt_take.rs b/embassy-macros/src/macros/cortex_m_interrupt_take.rs deleted file mode 100644 index f6e41bcb4..000000000 --- a/embassy-macros/src/macros/cortex_m_interrupt_take.rs +++ /dev/null @@ -1,42 +0,0 @@ -use proc_macro2::TokenStream; -use quote::{format_ident, quote}; - -pub fn run(name: syn::Ident) -> Result { - let name = format!("{}", name); - let name_interrupt = format_ident!("{}", name); - let name_handler = format!("__EMBASSY_{}_HANDLER", name); - - let result = quote! { - { - #[allow(non_snake_case)] - #[export_name = #name] - pub unsafe extern "C" fn trampoline() { - extern "C" { - #[link_name = #name_handler] - static HANDLER: interrupt::Handler; - } - - let func = HANDLER.func.load(interrupt::_export::atomic::Ordering::Relaxed); - let ctx = HANDLER.ctx.load(interrupt::_export::atomic::Ordering::Relaxed); - let func: fn(*mut ()) = ::core::mem::transmute(func); - ::embassy_executor::rtos_trace_interrupt! { - ::embassy_executor::export::trace::isr_enter(); - } - func(ctx); - ::embassy_executor::rtos_trace_interrupt! { - ::embassy_executor::export::trace::isr_exit(); - } - } - - static TAKEN: interrupt::_export::atomic::AtomicBool = interrupt::_export::atomic::AtomicBool::new(false); - - if TAKEN.compare_exchange(false, true, interrupt::_export::atomic::Ordering::AcqRel, interrupt::_export::atomic::Ordering::Acquire).is_err() { - core::panic!("IRQ Already taken"); - } - - let irq: interrupt::#name_interrupt = unsafe { ::core::mem::transmute(()) }; - irq - } - }; - Ok(result) -} diff --git a/embassy-macros/src/macros/main.rs b/embassy-macros/src/macros/main.rs index afe9bd3e2..7c4d55163 100644 --- a/embassy-macros/src/macros/main.rs +++ b/embassy-macros/src/macros/main.rs @@ -1,37 +1,56 @@ +use darling::export::NestedMeta; use darling::FromMeta; use proc_macro2::TokenStream; use quote::quote; +use syn::{Expr, ReturnType, Type}; use crate::util::ctxt::Ctxt; #[derive(Debug, FromMeta)] -struct Args {} +struct Args { + #[darling(default)] + entry: Option, +} -pub fn run(args: syn::AttributeArgs, f: syn::ItemFn) -> Result { - #[allow(unused_variables)] - let args = Args::from_list(&args).map_err(|e| e.write_errors())?; +pub fn riscv(args: &[NestedMeta]) -> TokenStream { + let maybe_entry = match Args::from_list(args) { + Ok(args) => args.entry, + Err(e) => return e.write_errors(), + }; - let fargs = f.sig.inputs.clone(); + let entry = maybe_entry.unwrap_or("riscv_rt::entry".into()); + let entry = match Expr::from_string(&entry) { + Ok(expr) => expr, + Err(e) => return e.write_errors(), + }; - let ctxt = Ctxt::new(); - - if f.sig.asyncness.is_none() { - ctxt.error_spanned_by(&f.sig, "main function must be async"); + quote! { + #[#entry] + fn main() -> ! { + let mut executor = ::embassy_executor::Executor::new(); + let executor = unsafe { __make_static(&mut executor) }; + executor.run(|spawner| { + spawner.must_spawn(__embassy_main(spawner)); + }) + } } - if !f.sig.generics.params.is_empty() { - ctxt.error_spanned_by(&f.sig, "main function must not be generic"); +} + +pub fn cortex_m() -> TokenStream { + quote! { + #[cortex_m_rt::entry] + fn main() -> ! { + let mut executor = ::embassy_executor::Executor::new(); + let executor = unsafe { __make_static(&mut executor) }; + executor.run(|spawner| { + spawner.must_spawn(__embassy_main(spawner)); + }) + } } +} - if fargs.len() != 1 { - ctxt.error_spanned_by(&f.sig, "main function must have 1 argument: the spawner."); - } - - ctxt.check()?; - - let f_body = f.block; - - #[cfg(feature = "wasm")] - let main = quote! { +pub fn wasm() -> TokenStream { + quote! { #[wasm_bindgen::prelude::wasm_bindgen(start)] pub fn main() -> Result<(), wasm_bindgen::JsValue> { static EXECUTOR: ::embassy_executor::_export::StaticCell<::embassy_executor::Executor> = ::embassy_executor::_export::StaticCell::new(); @@ -43,10 +62,11 @@ pub fn run(args: syn::AttributeArgs, f: syn::ItemFn) -> Result TokenStream { + quote! { fn main() -> ! { let mut executor = ::embassy_executor::Executor::new(); let executor = unsafe { __make_static(&mut executor) }; @@ -55,24 +75,56 @@ pub fn run(args: syn::AttributeArgs, f: syn::ItemFn) -> Result ! { - let mut executor = ::embassy_executor::Executor::new(); - let executor = unsafe { __make_static(&mut executor) }; +pub fn run(args: &[NestedMeta], f: syn::ItemFn, main: TokenStream) -> Result { + #[allow(unused_variables)] + let args = Args::from_list(args).map_err(|e| e.write_errors())?; - executor.run(|spawner| { - spawner.must_spawn(__embassy_main(spawner)); - }) - } - }; + let fargs = f.sig.inputs.clone(); + + let ctxt = Ctxt::new(); + + if f.sig.asyncness.is_none() { + ctxt.error_spanned_by(&f.sig, "main function must be async"); + } + if !f.sig.generics.params.is_empty() { + ctxt.error_spanned_by(&f.sig, "main function must not be generic"); + } + if !f.sig.generics.where_clause.is_none() { + ctxt.error_spanned_by(&f.sig, "main function must not have `where` clauses"); + } + if !f.sig.abi.is_none() { + ctxt.error_spanned_by(&f.sig, "main function must not have an ABI qualifier"); + } + if !f.sig.variadic.is_none() { + ctxt.error_spanned_by(&f.sig, "main function must not be variadic"); + } + match &f.sig.output { + ReturnType::Default => {} + ReturnType::Type(_, ty) => match &**ty { + Type::Tuple(tuple) if tuple.elems.is_empty() => {} + Type::Never(_) => {} + _ => ctxt.error_spanned_by( + &f.sig, + "main function must either not return a value, return `()` or return `!`", + ), + }, + } + + if fargs.len() != 1 { + ctxt.error_spanned_by(&f.sig, "main function must have 1 argument: the spawner."); + } + + ctxt.check()?; + + let f_body = f.block; + let out = &f.sig.output; let result = quote! { #[::embassy_executor::task()] - async fn __embassy_main(#fargs) { + async fn __embassy_main(#fargs) #out { #f_body } diff --git a/embassy-macros/src/macros/mod.rs b/embassy-macros/src/macros/mod.rs index e547736fc..572094ca6 100644 --- a/embassy-macros/src/macros/mod.rs +++ b/embassy-macros/src/macros/mod.rs @@ -1,5 +1,2 @@ -pub mod cortex_m_interrupt; -pub mod cortex_m_interrupt_declare; -pub mod cortex_m_interrupt_take; pub mod main; pub mod task; diff --git a/embassy-macros/src/macros/task.rs b/embassy-macros/src/macros/task.rs index 573776f8c..1d30434e9 100644 --- a/embassy-macros/src/macros/task.rs +++ b/embassy-macros/src/macros/task.rs @@ -1,19 +1,24 @@ +use darling::export::NestedMeta; use darling::FromMeta; -use proc_macro2::TokenStream; +use proc_macro2::{Span, TokenStream}; use quote::{format_ident, quote}; +use syn::{parse_quote, Expr, ExprLit, ItemFn, Lit, LitInt, ReturnType, Type}; use crate::util::ctxt::Ctxt; #[derive(Debug, FromMeta)] struct Args { #[darling(default)] - pool_size: Option, + pool_size: Option, } -pub fn run(args: syn::AttributeArgs, f: syn::ItemFn) -> Result { - let args = Args::from_list(&args).map_err(|e| e.write_errors())?; +pub fn run(args: &[NestedMeta], f: syn::ItemFn) -> Result { + let args = Args::from_list(args).map_err(|e| e.write_errors())?; - let pool_size: usize = args.pool_size.unwrap_or(1); + let pool_size = args.pool_size.unwrap_or(Expr::Lit(ExprLit { + attrs: vec![], + lit: Lit::Int(LitInt::new("1", Span::call_site())), + })); let ctxt = Ctxt::new(); @@ -23,8 +28,25 @@ pub fn run(args: syn::AttributeArgs, f: syn::ItemFn) -> Result {} + ReturnType::Type(_, ty) => match &**ty { + Type::Tuple(tuple) if tuple.elems.is_empty() => {} + Type::Never(_) => {} + _ => ctxt.error_spanned_by( + &f.sig, + "task functions must either not return a value, return `()` or return `!`", + ), + }, } let mut arg_names = Vec::new(); @@ -57,18 +79,26 @@ pub fn run(args: syn::AttributeArgs, f: syn::ItemFn) -> Result ::embassy_executor::SpawnToken { + type Fut = impl ::core::future::Future + 'static; + const POOL_SIZE: usize = #pool_size; + static POOL: ::embassy_executor::raw::TaskPool = ::embassy_executor::raw::TaskPool::new(); + unsafe { POOL._spawn_async_fn(move || #task_inner_ident(#(#arg_names,)*)) } + } + }; + + task_outer.attrs.append(&mut task_inner.attrs.clone()); + let result = quote! { // This is the user's task function, renamed. // We put it outside the #task_ident fn below, because otherwise // the items defined there (such as POOL) would be in scope // in the user's code. + #[doc(hidden)] #task_inner - #visibility fn #task_ident(#fargs) -> ::embassy_executor::SpawnToken { - type Fut = impl ::core::future::Future + 'static; - static POOL: ::embassy_executor::raw::TaskPool = ::embassy_executor::raw::TaskPool::new(); - unsafe { POOL._spawn_async_fn(move || #task_inner_ident(#(#arg_names,)*)) } - } + #task_outer }; Ok(result) diff --git a/embassy-net-driver-channel/Cargo.toml b/embassy-net-driver-channel/Cargo.toml new file mode 100644 index 000000000..bee2e3021 --- /dev/null +++ b/embassy-net-driver-channel/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "embassy-net-driver-channel" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "High-level channel-based driver for the `embassy-net` async TCP/IP network stack." +repository = "https://github.com/embassy-rs/embassy" +categories = [ + "embedded", + "no-std", + "asynchronous", +] + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-driver-channel-v$VERSION/embassy-net-driver-channel/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-driver-channel/src/" +features = ["defmt"] +target = "thumbv7em-none-eabi" + +[package.metadata.docs.rs] +features = ["defmt"] + +[dependencies] +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } + +embassy-sync = { version = "0.2.0", path = "../embassy-sync" } +embassy-futures = { version = "0.1.0", path = "../embassy-futures" } +embassy-net-driver = { version = "0.1.0", path = "../embassy-net-driver" } diff --git a/embassy-net-driver-channel/README.md b/embassy-net-driver-channel/README.md new file mode 100644 index 000000000..dd90e7ad2 --- /dev/null +++ b/embassy-net-driver-channel/README.md @@ -0,0 +1,96 @@ +# embassy-net-driver-channel + +This crate provides a toolkit for implementing [`embassy-net`](https://crates.io/crates/embassy-net) drivers in a +higher level way than implementing the [`embassy-net-driver`](https://crates.io/crates/embassy-net-driver) trait directly. + +The `embassy-net-driver` trait is polling-based. To implement it, you must write the packet receive/transmit state machines by +hand, and hook up the `Waker`s provided by `embassy-net` to the right interrupt handlers so that `embassy-net` +knows when to poll your driver again to make more progress. + +With `embassy-net-driver-channel` + +## A note about deadlocks + +When implementing a driver using this crate, it might be tempting to write it in the most straightforward way: + +```rust,ignore +loop { + // Wait for either.. + match select( + // ... the chip signaling an interrupt, indicating a packet is available to receive, or + irq_pin.wait_for_low(), + // ... a TX buffer becoming available, i.e. embassy-net wants to send a packet + tx_chan.tx_buf(), + ).await { + Either::First(_) => { + // a packet is ready to be received! + let buf = rx_chan.rx_buf().await; // allocate a rx buf from the packet queue + let n = receive_packet_over_spi(buf).await; + rx_chan.rx_done(n); + } + Either::Second(buf) => { + // a packet is ready to be sent! + send_packet_over_spi(buf).await; + tx_chan.tx_done(); + } + } +} +``` + +However, this code has a latent deadlock bug. The symptom is it can hang at `rx_chan.rx_buf().await` under load. + +The reason is that, under load, both the TX and RX queues can get full at the same time. When this happens, the `embassy-net` task stalls trying to send because the TX queue is full, therefore it stops processing packets in the RX queue. Your driver task also stalls because the RX queue is full, therefore it stops processing packets in the TX queue. + +The fix is to make sure to always service the TX queue while you're waiting for space to become available in the TX queue. For example, select on either "tx_chan.tx_buf() available" or "INT is low AND rx_chan.rx_buf() available": + +```rust,ignore +loop { + // Wait for either.. + match select( + async { + // ... the chip signaling an interrupt, indicating a packet is available to receive + irq_pin.wait_for_low().await; + // *AND* the buffer is ready... + rx_chan.rx_buf().await + }, + // ... or a TX buffer becoming available, i.e. embassy-net wants to send a packet + tx_chan.tx_buf(), + ).await { + Either::First(buf) => { + // a packet is ready to be received! + let n = receive_packet_over_spi(buf).await; + rx_chan.rx_done(n); + } + Either::Second(buf) => { + // a packet is ready to be sent! + send_packet_over_spi(buf).await; + tx_chan.tx_done(); + } + } +} +``` + +## Examples + +These `embassy-net` drivers are implemented using this crate. You can look at them for inspiration. + +- [`cyw43`](https://github.com/embassy-rs/embassy/tree/main/cyw43) for WiFi on CYW43xx chips, used in the Raspberry Pi Pico W +- [`embassy-usb`](https://github.com/embassy-rs/embassy/tree/main/embassy-usb) for Ethernet-over-USB (CDC NCM) support. +- [`embassy-net-w5500`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-w5500) for Wiznet W5500 SPI Ethernet MAC+PHY chip. +- [`embassy-net-esp-hosted`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-esp-hosted) for using ESP32 chips with the [`esp-hosted`](https://github.com/espressif/esp-hosted) firmware as WiFi adapters for another non-ESP32 MCU. + + +## Interoperability + +This crate can run on any executor. + + +## License + +This work is licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + http://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. diff --git a/embassy-usb-ncm/src/fmt.rs b/embassy-net-driver-channel/src/fmt.rs similarity index 100% rename from embassy-usb-ncm/src/fmt.rs rename to embassy-net-driver-channel/src/fmt.rs diff --git a/embassy-net-driver-channel/src/lib.rs b/embassy-net-driver-channel/src/lib.rs new file mode 100644 index 000000000..02a4c00d6 --- /dev/null +++ b/embassy-net-driver-channel/src/lib.rs @@ -0,0 +1,550 @@ +#![no_std] +#![doc = include_str!("../README.md")] + +// must go first! +mod fmt; + +use core::cell::RefCell; +use core::mem::MaybeUninit; +use core::task::{Context, Poll}; + +pub use embassy_net_driver as driver; +use embassy_net_driver::{Capabilities, LinkState, Medium}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::blocking_mutex::Mutex; +use embassy_sync::waitqueue::WakerRegistration; + +pub struct State { + rx: [PacketBuf; N_RX], + tx: [PacketBuf; N_TX], + inner: MaybeUninit>, +} + +impl State { + const NEW_PACKET: PacketBuf = PacketBuf::new(); + + pub const fn new() -> Self { + Self { + rx: [Self::NEW_PACKET; N_RX], + tx: [Self::NEW_PACKET; N_TX], + inner: MaybeUninit::uninit(), + } + } +} + +struct StateInner<'d, const MTU: usize> { + rx: zerocopy_channel::Channel<'d, NoopRawMutex, PacketBuf>, + tx: zerocopy_channel::Channel<'d, NoopRawMutex, PacketBuf>, + shared: Mutex>, +} + +/// State of the LinkState +struct Shared { + link_state: LinkState, + waker: WakerRegistration, + ethernet_address: [u8; 6], +} + +pub struct Runner<'d, const MTU: usize> { + tx_chan: zerocopy_channel::Receiver<'d, NoopRawMutex, PacketBuf>, + rx_chan: zerocopy_channel::Sender<'d, NoopRawMutex, PacketBuf>, + shared: &'d Mutex>, +} + +#[derive(Clone, Copy)] +pub struct StateRunner<'d> { + shared: &'d Mutex>, +} + +pub struct RxRunner<'d, const MTU: usize> { + rx_chan: zerocopy_channel::Sender<'d, NoopRawMutex, PacketBuf>, +} + +pub struct TxRunner<'d, const MTU: usize> { + tx_chan: zerocopy_channel::Receiver<'d, NoopRawMutex, PacketBuf>, +} + +impl<'d, const MTU: usize> Runner<'d, MTU> { + pub fn split(self) -> (StateRunner<'d>, RxRunner<'d, MTU>, TxRunner<'d, MTU>) { + ( + StateRunner { shared: self.shared }, + RxRunner { rx_chan: self.rx_chan }, + TxRunner { tx_chan: self.tx_chan }, + ) + } + + pub fn state_runner(&self) -> StateRunner<'d> { + StateRunner { shared: self.shared } + } + + pub fn set_link_state(&mut self, state: LinkState) { + self.shared.lock(|s| { + let s = &mut *s.borrow_mut(); + s.link_state = state; + s.waker.wake(); + }); + } + + pub fn set_ethernet_address(&mut self, address: [u8; 6]) { + self.shared.lock(|s| { + let s = &mut *s.borrow_mut(); + s.ethernet_address = address; + s.waker.wake(); + }); + } + + pub async fn rx_buf(&mut self) -> &mut [u8] { + let p = self.rx_chan.send().await; + &mut p.buf + } + + pub fn try_rx_buf(&mut self) -> Option<&mut [u8]> { + let p = self.rx_chan.try_send()?; + Some(&mut p.buf) + } + + pub fn poll_rx_buf(&mut self, cx: &mut Context) -> Poll<&mut [u8]> { + match self.rx_chan.poll_send(cx) { + Poll::Ready(p) => Poll::Ready(&mut p.buf), + Poll::Pending => Poll::Pending, + } + } + + pub fn rx_done(&mut self, len: usize) { + let p = self.rx_chan.try_send().unwrap(); + p.len = len; + self.rx_chan.send_done(); + } + + pub async fn tx_buf(&mut self) -> &mut [u8] { + let p = self.tx_chan.recv().await; + &mut p.buf[..p.len] + } + + pub fn try_tx_buf(&mut self) -> Option<&mut [u8]> { + let p = self.tx_chan.try_recv()?; + Some(&mut p.buf[..p.len]) + } + + pub fn poll_tx_buf(&mut self, cx: &mut Context) -> Poll<&mut [u8]> { + match self.tx_chan.poll_recv(cx) { + Poll::Ready(p) => Poll::Ready(&mut p.buf[..p.len]), + Poll::Pending => Poll::Pending, + } + } + + pub fn tx_done(&mut self) { + self.tx_chan.recv_done(); + } +} + +impl<'d> StateRunner<'d> { + pub fn set_link_state(&self, state: LinkState) { + self.shared.lock(|s| { + let s = &mut *s.borrow_mut(); + s.link_state = state; + s.waker.wake(); + }); + } + + pub fn set_ethernet_address(&self, address: [u8; 6]) { + self.shared.lock(|s| { + let s = &mut *s.borrow_mut(); + s.ethernet_address = address; + s.waker.wake(); + }); + } +} + +impl<'d, const MTU: usize> RxRunner<'d, MTU> { + pub async fn rx_buf(&mut self) -> &mut [u8] { + let p = self.rx_chan.send().await; + &mut p.buf + } + + pub fn try_rx_buf(&mut self) -> Option<&mut [u8]> { + let p = self.rx_chan.try_send()?; + Some(&mut p.buf) + } + + pub fn poll_rx_buf(&mut self, cx: &mut Context) -> Poll<&mut [u8]> { + match self.rx_chan.poll_send(cx) { + Poll::Ready(p) => Poll::Ready(&mut p.buf), + Poll::Pending => Poll::Pending, + } + } + + pub fn rx_done(&mut self, len: usize) { + let p = self.rx_chan.try_send().unwrap(); + p.len = len; + self.rx_chan.send_done(); + } +} + +impl<'d, const MTU: usize> TxRunner<'d, MTU> { + pub async fn tx_buf(&mut self) -> &mut [u8] { + let p = self.tx_chan.recv().await; + &mut p.buf[..p.len] + } + + pub fn try_tx_buf(&mut self) -> Option<&mut [u8]> { + let p = self.tx_chan.try_recv()?; + Some(&mut p.buf[..p.len]) + } + + pub fn poll_tx_buf(&mut self, cx: &mut Context) -> Poll<&mut [u8]> { + match self.tx_chan.poll_recv(cx) { + Poll::Ready(p) => Poll::Ready(&mut p.buf[..p.len]), + Poll::Pending => Poll::Pending, + } + } + + pub fn tx_done(&mut self) { + self.tx_chan.recv_done(); + } +} + +pub fn new<'d, const MTU: usize, const N_RX: usize, const N_TX: usize>( + state: &'d mut State, + ethernet_address: [u8; 6], +) -> (Runner<'d, MTU>, Device<'d, MTU>) { + let mut caps = Capabilities::default(); + caps.max_transmission_unit = MTU; + caps.medium = Medium::Ethernet; + + // safety: this is a self-referential struct, however: + // - it can't move while the `'d` borrow is active. + // - when the borrow ends, the dangling references inside the MaybeUninit will never be used again. + let state_uninit: *mut MaybeUninit> = + (&mut state.inner as *mut MaybeUninit>).cast(); + let state = unsafe { &mut *state_uninit }.write(StateInner { + rx: zerocopy_channel::Channel::new(&mut state.rx[..]), + tx: zerocopy_channel::Channel::new(&mut state.tx[..]), + shared: Mutex::new(RefCell::new(Shared { + link_state: LinkState::Down, + ethernet_address, + waker: WakerRegistration::new(), + })), + }); + + let (rx_sender, rx_receiver) = state.rx.split(); + let (tx_sender, tx_receiver) = state.tx.split(); + + ( + Runner { + tx_chan: tx_receiver, + rx_chan: rx_sender, + shared: &state.shared, + }, + Device { + caps, + shared: &state.shared, + rx: rx_receiver, + tx: tx_sender, + }, + ) +} + +pub struct PacketBuf { + len: usize, + buf: [u8; MTU], +} + +impl PacketBuf { + pub const fn new() -> Self { + Self { len: 0, buf: [0; MTU] } + } +} + +pub struct Device<'d, const MTU: usize> { + rx: zerocopy_channel::Receiver<'d, NoopRawMutex, PacketBuf>, + tx: zerocopy_channel::Sender<'d, NoopRawMutex, PacketBuf>, + shared: &'d Mutex>, + caps: Capabilities, +} + +impl<'d, const MTU: usize> embassy_net_driver::Driver for Device<'d, MTU> { + type RxToken<'a> = RxToken<'a, MTU> where Self: 'a ; + type TxToken<'a> = TxToken<'a, MTU> where Self: 'a ; + + fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + if self.rx.poll_recv(cx).is_ready() && self.tx.poll_send(cx).is_ready() { + Some((RxToken { rx: self.rx.borrow() }, TxToken { tx: self.tx.borrow() })) + } else { + None + } + } + + /// Construct a transmit token. + fn transmit(&mut self, cx: &mut Context) -> Option> { + if self.tx.poll_send(cx).is_ready() { + Some(TxToken { tx: self.tx.borrow() }) + } else { + None + } + } + + /// Get a description of device capabilities. + fn capabilities(&self) -> Capabilities { + self.caps.clone() + } + + fn ethernet_address(&self) -> [u8; 6] { + self.shared.lock(|s| s.borrow().ethernet_address) + } + + fn link_state(&mut self, cx: &mut Context) -> LinkState { + self.shared.lock(|s| { + let s = &mut *s.borrow_mut(); + s.waker.register(cx.waker()); + s.link_state + }) + } +} + +pub struct RxToken<'a, const MTU: usize> { + rx: zerocopy_channel::Receiver<'a, NoopRawMutex, PacketBuf>, +} + +impl<'a, const MTU: usize> embassy_net_driver::RxToken for RxToken<'a, MTU> { + fn consume(mut self, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + // NOTE(unwrap): we checked the queue wasn't full when creating the token. + let pkt = unwrap!(self.rx.try_recv()); + let r = f(&mut pkt.buf[..pkt.len]); + self.rx.recv_done(); + r + } +} + +pub struct TxToken<'a, const MTU: usize> { + tx: zerocopy_channel::Sender<'a, NoopRawMutex, PacketBuf>, +} + +impl<'a, const MTU: usize> embassy_net_driver::TxToken for TxToken<'a, MTU> { + fn consume(mut self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + // NOTE(unwrap): we checked the queue wasn't full when creating the token. + let pkt = unwrap!(self.tx.try_send()); + let r = f(&mut pkt.buf[..len]); + pkt.len = len; + self.tx.send_done(); + r + } +} + +mod zerocopy_channel { + use core::cell::RefCell; + use core::future::poll_fn; + use core::marker::PhantomData; + use core::task::{Context, Poll}; + + use embassy_sync::blocking_mutex::raw::RawMutex; + use embassy_sync::blocking_mutex::Mutex; + use embassy_sync::waitqueue::WakerRegistration; + + pub struct Channel<'a, M: RawMutex, T> { + buf: *mut T, + phantom: PhantomData<&'a mut T>, + state: Mutex>, + } + + impl<'a, M: RawMutex, T> Channel<'a, M, T> { + pub fn new(buf: &'a mut [T]) -> Self { + let len = buf.len(); + assert!(len != 0); + + Self { + buf: buf.as_mut_ptr(), + phantom: PhantomData, + state: Mutex::new(RefCell::new(State { + len, + front: 0, + back: 0, + full: false, + send_waker: WakerRegistration::new(), + recv_waker: WakerRegistration::new(), + })), + } + } + + pub fn split(&mut self) -> (Sender<'_, M, T>, Receiver<'_, M, T>) { + (Sender { channel: self }, Receiver { channel: self }) + } + } + + pub struct Sender<'a, M: RawMutex, T> { + channel: &'a Channel<'a, M, T>, + } + + impl<'a, M: RawMutex, T> Sender<'a, M, T> { + pub fn borrow(&mut self) -> Sender<'_, M, T> { + Sender { channel: self.channel } + } + + pub fn try_send(&mut self) -> Option<&mut T> { + self.channel.state.lock(|s| { + let s = &mut *s.borrow_mut(); + match s.push_index() { + Some(i) => Some(unsafe { &mut *self.channel.buf.add(i) }), + None => None, + } + }) + } + + pub fn poll_send(&mut self, cx: &mut Context) -> Poll<&mut T> { + self.channel.state.lock(|s| { + let s = &mut *s.borrow_mut(); + match s.push_index() { + Some(i) => Poll::Ready(unsafe { &mut *self.channel.buf.add(i) }), + None => { + s.recv_waker.register(cx.waker()); + Poll::Pending + } + } + }) + } + + pub async fn send(&mut self) -> &mut T { + let i = poll_fn(|cx| { + self.channel.state.lock(|s| { + let s = &mut *s.borrow_mut(); + match s.push_index() { + Some(i) => Poll::Ready(i), + None => { + s.recv_waker.register(cx.waker()); + Poll::Pending + } + } + }) + }) + .await; + unsafe { &mut *self.channel.buf.add(i) } + } + + pub fn send_done(&mut self) { + self.channel.state.lock(|s| s.borrow_mut().push_done()) + } + } + pub struct Receiver<'a, M: RawMutex, T> { + channel: &'a Channel<'a, M, T>, + } + + impl<'a, M: RawMutex, T> Receiver<'a, M, T> { + pub fn borrow(&mut self) -> Receiver<'_, M, T> { + Receiver { channel: self.channel } + } + + pub fn try_recv(&mut self) -> Option<&mut T> { + self.channel.state.lock(|s| { + let s = &mut *s.borrow_mut(); + match s.pop_index() { + Some(i) => Some(unsafe { &mut *self.channel.buf.add(i) }), + None => None, + } + }) + } + + pub fn poll_recv(&mut self, cx: &mut Context) -> Poll<&mut T> { + self.channel.state.lock(|s| { + let s = &mut *s.borrow_mut(); + match s.pop_index() { + Some(i) => Poll::Ready(unsafe { &mut *self.channel.buf.add(i) }), + None => { + s.send_waker.register(cx.waker()); + Poll::Pending + } + } + }) + } + + pub async fn recv(&mut self) -> &mut T { + let i = poll_fn(|cx| { + self.channel.state.lock(|s| { + let s = &mut *s.borrow_mut(); + match s.pop_index() { + Some(i) => Poll::Ready(i), + None => { + s.send_waker.register(cx.waker()); + Poll::Pending + } + } + }) + }) + .await; + unsafe { &mut *self.channel.buf.add(i) } + } + + pub fn recv_done(&mut self) { + self.channel.state.lock(|s| s.borrow_mut().pop_done()) + } + } + + struct State { + len: usize, + + /// Front index. Always 0..=(N-1) + front: usize, + /// Back index. Always 0..=(N-1). + back: usize, + + /// Used to distinguish "empty" and "full" cases when `front == back`. + /// May only be `true` if `front == back`, always `false` otherwise. + full: bool, + + send_waker: WakerRegistration, + recv_waker: WakerRegistration, + } + + impl State { + fn increment(&self, i: usize) -> usize { + if i + 1 == self.len { + 0 + } else { + i + 1 + } + } + + fn is_full(&self) -> bool { + self.full + } + + fn is_empty(&self) -> bool { + self.front == self.back && !self.full + } + + fn push_index(&mut self) -> Option { + match self.is_full() { + true => None, + false => Some(self.back), + } + } + + fn push_done(&mut self) { + assert!(!self.is_full()); + self.back = self.increment(self.back); + if self.back == self.front { + self.full = true; + } + self.send_waker.wake(); + } + + fn pop_index(&mut self) -> Option { + match self.is_empty() { + true => None, + false => Some(self.front), + } + } + + fn pop_done(&mut self) { + assert!(!self.is_empty()); + self.front = self.increment(self.front); + self.full = false; + self.recv_waker.wake(); + } + } +} diff --git a/embassy-net-driver/Cargo.toml b/embassy-net-driver/Cargo.toml new file mode 100644 index 000000000..da6d9ad62 --- /dev/null +++ b/embassy-net-driver/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "embassy-net-driver" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" +description = "Driver trait for the `embassy-net` async TCP/IP network stack." +repository = "https://github.com/embassy-rs/embassy" +categories = [ + "embedded", + "no-std", + "asynchronous", +] + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-driver-v$VERSION/embassy-net-driver/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-driver/src/" +features = ["defmt"] +target = "thumbv7em-none-eabi" + +[package.metadata.docs.rs] +features = ["defmt"] + +[dependencies] +defmt = { version = "0.3", optional = true } \ No newline at end of file diff --git a/embassy-net-driver/README.md b/embassy-net-driver/README.md new file mode 100644 index 000000000..6a757380d --- /dev/null +++ b/embassy-net-driver/README.md @@ -0,0 +1,28 @@ +# embassy-net-driver + +This crate contains the driver trait necessary for adding [`embassy-net`](https://crates.io/crates/embassy-net) support +for a new hardware platform. + +If you want to *use* `embassy-net` with already made drivers, you should depend on the main `embassy-net` crate, not on this crate. + +If you are writing a driver, you should depend only on this crate, not on the main `embassy-net` crate. +This will allow your driver to continue working for newer `embassy-net` major versions, without needing an update, +if the driver trait has not had breaking changes. + +See also [`embassy-net-driver-channel`](https://crates.io/crates/embassy-net-driver-channel), which provides a higer-level API +to construct a driver that processes packets in its own background task and communicates with the `embassy-net` task via +packet queues for RX and TX. + +## Interoperability + +This crate can run on any executor. + +## License + +This work is licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + http://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. diff --git a/embassy-net-driver/src/lib.rs b/embassy-net-driver/src/lib.rs new file mode 100644 index 000000000..4149bf4a4 --- /dev/null +++ b/embassy-net-driver/src/lib.rs @@ -0,0 +1,220 @@ +#![no_std] +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] + +use core::task::Context; + +/// Main `embassy-net` driver API. +/// +/// This is essentially an interface for sending and receiving raw network frames. +/// +/// The interface is based on _tokens_, which are types that allow to receive/transmit a +/// single packet. The `receive` and `transmit` functions only construct such tokens, the +/// real sending/receiving operation are performed when the tokens are consumed. +pub trait Driver { + /// A token to receive a single network packet. + type RxToken<'a>: RxToken + where + Self: 'a; + + /// A token to transmit a single network packet. + type TxToken<'a>: TxToken + where + Self: 'a; + + /// Construct a token pair consisting of one receive token and one transmit token. + /// + /// If there is a packet ready to be received, this function must return `Some`. + /// If there isn't, it must return `None`, and wake `cx.waker()` when a packet is ready. + /// + /// The additional transmit token makes it possible to generate a reply packet based + /// on the contents of the received packet. For example, this makes it possible to + /// handle arbitrarily large ICMP echo ("ping") requests, where the all received bytes + /// need to be sent back, without heap allocation. + fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)>; + + /// Construct a transmit token. + /// + /// If there is free space in the transmit buffer to transmit a packet, this function must return `Some`. + /// If there isn't, it must return `None`, and wake `cx.waker()` when space becomes available. + /// + /// Note that [`TxToken::consume`] is infallible, so it is not allowed to return a token + /// if there is no free space and fail later. + fn transmit(&mut self, cx: &mut Context) -> Option>; + + /// Get the link state. + /// + /// This function must return the current link state of the device, and wake `cx.waker()` when + /// the link state changes. + fn link_state(&mut self, cx: &mut Context) -> LinkState; + + /// Get a description of device capabilities. + fn capabilities(&self) -> Capabilities; + + /// Get the device's Ethernet address. + fn ethernet_address(&self) -> [u8; 6]; +} + +impl Driver for &mut T { + type RxToken<'a> = T::RxToken<'a> + where + Self: 'a; + type TxToken<'a> = T::TxToken<'a> + where + Self: 'a; + + fn transmit(&mut self, cx: &mut Context) -> Option> { + T::transmit(self, cx) + } + fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + T::receive(self, cx) + } + fn capabilities(&self) -> Capabilities { + T::capabilities(self) + } + fn link_state(&mut self, cx: &mut Context) -> LinkState { + T::link_state(self, cx) + } + fn ethernet_address(&self) -> [u8; 6] { + T::ethernet_address(self) + } +} + +/// A token to receive a single network packet. +pub trait RxToken { + /// Consumes the token to receive a single network packet. + /// + /// This method receives a packet and then calls the given closure `f` with the raw + /// packet bytes as argument. + fn consume(self, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R; +} + +/// A token to transmit a single network packet. +pub trait TxToken { + /// Consumes the token to send a single network packet. + /// + /// This method constructs a transmit buffer of size `len` and calls the passed + /// closure `f` with a mutable reference to that buffer. The closure should construct + /// a valid network packet (e.g. an ethernet packet) in the buffer. When the closure + /// returns, the transmit buffer is sent out. + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R; +} + +/// A description of device capabilities. +/// +/// Higher-level protocols may achieve higher throughput or lower latency if they consider +/// the bandwidth or packet size limitations. +#[derive(Debug, Clone, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct Capabilities { + /// Medium of the device. + /// + /// This indicates what kind of packet the sent/received bytes are, and determines + /// some behaviors of Interface. For example, ARP/NDISC address resolution is only done + /// for Ethernet mediums. + pub medium: Medium, + + /// Maximum transmission unit. + /// + /// The network device is unable to send or receive frames larger than the value returned + /// by this function. + /// + /// For Ethernet devices, this is the maximum Ethernet frame size, including the Ethernet header (14 octets), but + /// *not* including the Ethernet FCS (4 octets). Therefore, Ethernet MTU = IP MTU + 14. + /// + /// Note that in Linux and other OSes, "MTU" is the IP MTU, not the Ethernet MTU, even for Ethernet + /// devices. This is a common source of confusion. + /// + /// Most common IP MTU is 1500. Minimum is 576 (for IPv4) or 1280 (for IPv6). Maximum is 9216 octets. + pub max_transmission_unit: usize, + + /// Maximum burst size, in terms of MTU. + /// + /// The network device is unable to send or receive bursts large than the value returned + /// by this function. + /// + /// If `None`, there is no fixed limit on burst size, e.g. if network buffers are + /// dynamically allocated. + pub max_burst_size: Option, + + /// Checksum behavior. + /// + /// If the network device is capable of verifying or computing checksums for some protocols, + /// it can request that the stack not do so in software to improve performance. + pub checksum: ChecksumCapabilities, +} + +/// Type of medium of a device. +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Medium { + /// Ethernet medium. Devices of this type send and receive Ethernet frames, + /// and interfaces using it must do neighbor discovery via ARP or NDISC. + /// + /// Examples of devices of this type are Ethernet, WiFi (802.11), Linux `tap`, and VPNs in tap (layer 2) mode. + Ethernet, + + /// IP medium. Devices of this type send and receive IP frames, without an + /// Ethernet header. MAC addresses are not used, and no neighbor discovery (ARP, NDISC) is done. + /// + /// Examples of devices of this type are the Linux `tun`, PPP interfaces, VPNs in tun (layer 3) mode. + Ip, +} + +impl Default for Medium { + fn default() -> Medium { + Medium::Ethernet + } +} + +/// A description of checksum behavior for every supported protocol. +#[derive(Debug, Clone, Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct ChecksumCapabilities { + /// Checksum behavior for IPv4. + pub ipv4: Checksum, + /// Checksum behavior for UDP. + pub udp: Checksum, + /// Checksum behavior for TCP. + pub tcp: Checksum, + /// Checksum behavior for ICMPv4. + pub icmpv4: Checksum, + /// Checksum behavior for ICMPv6. + pub icmpv6: Checksum, +} + +/// A description of checksum behavior for a particular protocol. +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Checksum { + /// Verify checksum when receiving and compute checksum when sending. + Both, + /// Verify checksum when receiving. + Rx, + /// Compute checksum before sending. + Tx, + /// Ignore checksum completely. + None, +} + +impl Default for Checksum { + fn default() -> Checksum { + Checksum::Both + } +} + +/// The link state of a network device. +#[derive(PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum LinkState { + /// The link is down. + Down, + /// The link is up. + Up, +} diff --git a/embassy-net-esp-hosted/Cargo.toml b/embassy-net-esp-hosted/Cargo.toml new file mode 100644 index 000000000..26f5b40bd --- /dev/null +++ b/embassy-net-esp-hosted/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "embassy-net-esp-hosted" +version = "0.1.0" +edition = "2021" + +[dependencies] +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true } + +embassy-time = { version = "0.1.2", path = "../embassy-time" } +embassy-sync = { version = "0.2.0", path = "../embassy-sync"} +embassy-futures = { version = "0.1.0", path = "../embassy-futures"} +embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel"} + +embedded-hal = { version = "1.0.0-alpha.11" } +embedded-hal-async = { version = "=0.2.0-alpha.2" } + +noproto = { git="https://github.com/embassy-rs/noproto", default-features = false, features = ["derive"] } +#noproto = { version = "0.1", path = "/home/dirbaio/noproto", default-features = false, features = ["derive"] } +heapless = "0.7.16" diff --git a/embassy-net-esp-hosted/src/control.rs b/embassy-net-esp-hosted/src/control.rs new file mode 100644 index 000000000..79f8cde7b --- /dev/null +++ b/embassy-net-esp-hosted/src/control.rs @@ -0,0 +1,145 @@ +use ch::driver::LinkState; +use defmt::Debug2Format; +use embassy_net_driver_channel as ch; +use heapless::String; + +use crate::ioctl::Shared; +use crate::proto::{self, CtrlMsg}; + +#[derive(Debug)] +pub struct Error { + pub status: u32, +} + +pub struct Control<'a> { + state_ch: ch::StateRunner<'a>, + shared: &'a Shared, +} + +#[allow(unused)] +enum WifiMode { + None = 0, + Sta = 1, + Ap = 2, + ApSta = 3, +} + +impl<'a> Control<'a> { + pub(crate) fn new(state_ch: ch::StateRunner<'a>, shared: &'a Shared) -> Self { + Self { state_ch, shared } + } + + pub async fn init(&mut self) { + debug!("wait for init event..."); + self.shared.init_wait().await; + + debug!("set wifi mode"); + self.set_wifi_mode(WifiMode::Sta as _).await; + + let mac_addr = self.get_mac_addr().await; + debug!("mac addr: {:02x}", mac_addr); + self.state_ch.set_ethernet_address(mac_addr); + } + + pub async fn join(&mut self, ssid: &str, password: &str) { + let req = proto::CtrlMsg { + msg_id: proto::CtrlMsgId::ReqConnectAp as _, + msg_type: proto::CtrlMsgType::Req as _, + payload: Some(proto::CtrlMsgPayload::ReqConnectAp(proto::CtrlMsgReqConnectAp { + ssid: String::from(ssid), + pwd: String::from(password), + bssid: String::new(), + listen_interval: 3, + is_wpa3_supported: false, + })), + }; + let resp = self.ioctl(req).await; + let proto::CtrlMsgPayload::RespConnectAp(resp) = resp.payload.unwrap() else { + panic!("unexpected resp") + }; + debug!("======= {:?}", Debug2Format(&resp)); + assert_eq!(resp.resp, 0); + self.state_ch.set_link_state(LinkState::Up); + } + + async fn get_mac_addr(&mut self) -> [u8; 6] { + let req = proto::CtrlMsg { + msg_id: proto::CtrlMsgId::ReqGetMacAddress as _, + msg_type: proto::CtrlMsgType::Req as _, + payload: Some(proto::CtrlMsgPayload::ReqGetMacAddress( + proto::CtrlMsgReqGetMacAddress { + mode: WifiMode::Sta as _, + }, + )), + }; + let resp = self.ioctl(req).await; + let proto::CtrlMsgPayload::RespGetMacAddress(resp) = resp.payload.unwrap() else { + panic!("unexpected resp") + }; + assert_eq!(resp.resp, 0); + + // WHY IS THIS A STRING? WHYYYY + fn nibble_from_hex(b: u8) -> u8 { + match b { + b'0'..=b'9' => b - b'0', + b'a'..=b'f' => b + 0xa - b'a', + b'A'..=b'F' => b + 0xa - b'A', + _ => panic!("invalid hex digit {}", b), + } + } + + let mac = resp.mac.as_bytes(); + let mut res = [0; 6]; + assert_eq!(mac.len(), 17); + for (i, b) in res.iter_mut().enumerate() { + *b = (nibble_from_hex(mac[i * 3]) << 4) | nibble_from_hex(mac[i * 3 + 1]) + } + res + } + + async fn set_wifi_mode(&mut self, mode: u32) { + let req = proto::CtrlMsg { + msg_id: proto::CtrlMsgId::ReqSetWifiMode as _, + msg_type: proto::CtrlMsgType::Req as _, + payload: Some(proto::CtrlMsgPayload::ReqSetWifiMode(proto::CtrlMsgReqSetMode { mode })), + }; + let resp = self.ioctl(req).await; + let proto::CtrlMsgPayload::RespSetWifiMode(resp) = resp.payload.unwrap() else { + panic!("unexpected resp") + }; + assert_eq!(resp.resp, 0); + } + + async fn ioctl(&mut self, req: CtrlMsg) -> CtrlMsg { + debug!("ioctl req: {:?}", &req); + + let mut buf = [0u8; 128]; + + let req_len = noproto::write(&req, &mut buf).unwrap(); + + struct CancelOnDrop<'a>(&'a Shared); + + impl CancelOnDrop<'_> { + fn defuse(self) { + core::mem::forget(self); + } + } + + impl Drop for CancelOnDrop<'_> { + fn drop(&mut self) { + self.0.ioctl_cancel(); + } + } + + let ioctl = CancelOnDrop(self.shared); + + let resp_len = ioctl.0.ioctl(&mut buf, req_len).await; + + ioctl.defuse(); + + let res = noproto::read(&buf[..resp_len]).unwrap(); + debug!("ioctl resp: {:?}", &res); + + res + } +} diff --git a/embassy-net-esp-hosted/src/esp_hosted_config.proto b/embassy-net-esp-hosted/src/esp_hosted_config.proto new file mode 100644 index 000000000..aa1bfde64 --- /dev/null +++ b/embassy-net-esp-hosted/src/esp_hosted_config.proto @@ -0,0 +1,432 @@ +syntax = "proto3"; + +/* Enums similar to ESP IDF */ +enum Ctrl_VendorIEType { + Beacon = 0; + Probe_req = 1; + Probe_resp = 2; + Assoc_req = 3; + Assoc_resp = 4; +} + +enum Ctrl_VendorIEID { + ID_0 = 0; + ID_1 = 1; +} + +enum Ctrl_WifiMode { + NONE = 0; + STA = 1; + AP = 2; + APSTA = 3; +} + +enum Ctrl_WifiBw { + BW_Invalid = 0; + HT20 = 1; + HT40 = 2; +} + +enum Ctrl_WifiPowerSave { + PS_Invalid = 0; + MIN_MODEM = 1; + MAX_MODEM = 2; +} + +enum Ctrl_WifiSecProt { + Open = 0; + WEP = 1; + WPA_PSK = 2; + WPA2_PSK = 3; + WPA_WPA2_PSK = 4; + WPA2_ENTERPRISE = 5; + WPA3_PSK = 6; + WPA2_WPA3_PSK = 7; +} + +/* enums for Control path */ +enum Ctrl_Status { + Connected = 0; + Not_Connected = 1; + No_AP_Found = 2; + Connection_Fail = 3; + Invalid_Argument = 4; + Out_Of_Range = 5; +} + + +enum CtrlMsgType { + MsgType_Invalid = 0; + Req = 1; + Resp = 2; + Event = 3; + MsgType_Max = 4; +} + +enum CtrlMsgId { + MsgId_Invalid = 0; + + /** Request Msgs **/ + Req_Base = 100; + + Req_GetMACAddress = 101; + Req_SetMacAddress = 102; + Req_GetWifiMode = 103; + Req_SetWifiMode = 104; + + Req_GetAPScanList = 105; + Req_GetAPConfig = 106; + Req_ConnectAP = 107; + Req_DisconnectAP = 108; + + Req_GetSoftAPConfig = 109; + Req_SetSoftAPVendorSpecificIE = 110; + Req_StartSoftAP = 111; + Req_GetSoftAPConnectedSTAList = 112; + Req_StopSoftAP = 113; + + Req_SetPowerSaveMode = 114; + Req_GetPowerSaveMode = 115; + + Req_OTABegin = 116; + Req_OTAWrite = 117; + Req_OTAEnd = 118; + + Req_SetWifiMaxTxPower = 119; + Req_GetWifiCurrTxPower = 120; + + Req_ConfigHeartbeat = 121; + /* Add new control path command response before Req_Max + * and update Req_Max */ + Req_Max = 122; + + /** Response Msgs **/ + Resp_Base = 200; + + Resp_GetMACAddress = 201; + Resp_SetMacAddress = 202; + Resp_GetWifiMode = 203; + Resp_SetWifiMode = 204; + + Resp_GetAPScanList = 205; + Resp_GetAPConfig = 206; + Resp_ConnectAP = 207; + Resp_DisconnectAP = 208; + + Resp_GetSoftAPConfig = 209; + Resp_SetSoftAPVendorSpecificIE = 210; + Resp_StartSoftAP = 211; + Resp_GetSoftAPConnectedSTAList = 212; + Resp_StopSoftAP = 213; + + Resp_SetPowerSaveMode = 214; + Resp_GetPowerSaveMode = 215; + + Resp_OTABegin = 216; + Resp_OTAWrite = 217; + Resp_OTAEnd = 218; + + Resp_SetWifiMaxTxPower = 219; + Resp_GetWifiCurrTxPower = 220; + + Resp_ConfigHeartbeat = 221; + /* Add new control path command response before Resp_Max + * and update Resp_Max */ + Resp_Max = 222; + + /** Event Msgs **/ + Event_Base = 300; + Event_ESPInit = 301; + Event_Heartbeat = 302; + Event_StationDisconnectFromAP = 303; + Event_StationDisconnectFromESPSoftAP = 304; + /* Add new control path command notification before Event_Max + * and update Event_Max */ + Event_Max = 305; +} + +/* internal supporting structures for CtrlMsg */ +message ScanResult { + bytes ssid = 1; + uint32 chnl = 2; + int32 rssi = 3; + bytes bssid = 4; + Ctrl_WifiSecProt sec_prot = 5; +} + +message ConnectedSTAList { + bytes mac = 1; + int32 rssi = 2; +} + + +/* Control path structures */ +/** Req/Resp structure **/ +message CtrlMsg_Req_GetMacAddress { + int32 mode = 1; +} + +message CtrlMsg_Resp_GetMacAddress { + bytes mac = 1; + int32 resp = 2; +} + +message CtrlMsg_Req_GetMode { +} + +message CtrlMsg_Resp_GetMode { + int32 mode = 1; + int32 resp = 2; +} + +message CtrlMsg_Req_SetMode { + int32 mode = 1; +} + +message CtrlMsg_Resp_SetMode { + int32 resp = 1; +} + +message CtrlMsg_Req_GetStatus { +} + +message CtrlMsg_Resp_GetStatus { + int32 resp = 1; +} + +message CtrlMsg_Req_SetMacAddress { + bytes mac = 1; + int32 mode = 2; +} + +message CtrlMsg_Resp_SetMacAddress { + int32 resp = 1; +} + +message CtrlMsg_Req_GetAPConfig { +} + +message CtrlMsg_Resp_GetAPConfig { + bytes ssid = 1; + bytes bssid = 2; + int32 rssi = 3; + int32 chnl = 4; + Ctrl_WifiSecProt sec_prot = 5; + int32 resp = 6; +} + +message CtrlMsg_Req_ConnectAP { + string ssid = 1; + string pwd = 2; + string bssid = 3; + bool is_wpa3_supported = 4; + int32 listen_interval = 5; +} + +message CtrlMsg_Resp_ConnectAP { + int32 resp = 1; + bytes mac = 2; +} + +message CtrlMsg_Req_GetSoftAPConfig { +} + +message CtrlMsg_Resp_GetSoftAPConfig { + bytes ssid = 1; + bytes pwd = 2; + int32 chnl = 3; + Ctrl_WifiSecProt sec_prot = 4; + int32 max_conn = 5; + bool ssid_hidden = 6; + int32 bw = 7; + int32 resp = 8; +} + +message CtrlMsg_Req_StartSoftAP { + string ssid = 1; + string pwd = 2; + int32 chnl = 3; + Ctrl_WifiSecProt sec_prot = 4; + int32 max_conn = 5; + bool ssid_hidden = 6; + int32 bw = 7; +} + +message CtrlMsg_Resp_StartSoftAP { + int32 resp = 1; + bytes mac = 2; +} + +message CtrlMsg_Req_ScanResult { +} + +message CtrlMsg_Resp_ScanResult { + uint32 count = 1; + repeated ScanResult entries = 2; + int32 resp = 3; +} + +message CtrlMsg_Req_SoftAPConnectedSTA { +} + +message CtrlMsg_Resp_SoftAPConnectedSTA { + uint32 num = 1; + repeated ConnectedSTAList stations = 2; + int32 resp = 3; +} + +message CtrlMsg_Req_OTABegin { +} + +message CtrlMsg_Resp_OTABegin { + int32 resp = 1; +} + +message CtrlMsg_Req_OTAWrite { + bytes ota_data = 1; +} + +message CtrlMsg_Resp_OTAWrite { + int32 resp = 1; +} + +message CtrlMsg_Req_OTAEnd { +} + +message CtrlMsg_Resp_OTAEnd { + int32 resp = 1; +} + +message CtrlMsg_Req_VendorIEData { + int32 element_id = 1; + int32 length = 2; + bytes vendor_oui = 3; + int32 vendor_oui_type = 4; + bytes payload = 5; +} + +message CtrlMsg_Req_SetSoftAPVendorSpecificIE { + bool enable = 1; + Ctrl_VendorIEType type = 2; + Ctrl_VendorIEID idx = 3; + CtrlMsg_Req_VendorIEData vendor_ie_data = 4; +} + +message CtrlMsg_Resp_SetSoftAPVendorSpecificIE { + int32 resp = 1; +} + +message CtrlMsg_Req_SetWifiMaxTxPower { + int32 wifi_max_tx_power = 1; +} + +message CtrlMsg_Resp_SetWifiMaxTxPower { + int32 resp = 1; +} + +message CtrlMsg_Req_GetWifiCurrTxPower { +} + +message CtrlMsg_Resp_GetWifiCurrTxPower { + int32 wifi_curr_tx_power = 1; + int32 resp = 2; +} + +message CtrlMsg_Req_ConfigHeartbeat { + bool enable = 1; + int32 duration = 2; +} + +message CtrlMsg_Resp_ConfigHeartbeat { + int32 resp = 1; +} + +/** Event structure **/ +message CtrlMsg_Event_ESPInit { + bytes init_data = 1; +} + +message CtrlMsg_Event_Heartbeat { + int32 hb_num = 1; +} + +message CtrlMsg_Event_StationDisconnectFromAP { + int32 resp = 1; +} + +message CtrlMsg_Event_StationDisconnectFromESPSoftAP { + int32 resp = 1; + bytes mac = 2; +} + +message CtrlMsg { + /* msg_type could be req, resp or Event */ + CtrlMsgType msg_type = 1; + + /* msg id */ + CtrlMsgId msg_id = 2; + + /* union of all msg ids */ + oneof payload { + /** Requests **/ + CtrlMsg_Req_GetMacAddress req_get_mac_address = 101; + CtrlMsg_Req_SetMacAddress req_set_mac_address = 102; + CtrlMsg_Req_GetMode req_get_wifi_mode = 103; + CtrlMsg_Req_SetMode req_set_wifi_mode = 104; + + CtrlMsg_Req_ScanResult req_scan_ap_list = 105; + CtrlMsg_Req_GetAPConfig req_get_ap_config = 106; + CtrlMsg_Req_ConnectAP req_connect_ap = 107; + CtrlMsg_Req_GetStatus req_disconnect_ap = 108; + + CtrlMsg_Req_GetSoftAPConfig req_get_softap_config = 109; + CtrlMsg_Req_SetSoftAPVendorSpecificIE req_set_softap_vendor_specific_ie = 110; + CtrlMsg_Req_StartSoftAP req_start_softap = 111; + CtrlMsg_Req_SoftAPConnectedSTA req_softap_connected_stas_list = 112; + CtrlMsg_Req_GetStatus req_stop_softap = 113; + + CtrlMsg_Req_SetMode req_set_power_save_mode = 114; + CtrlMsg_Req_GetMode req_get_power_save_mode = 115; + + CtrlMsg_Req_OTABegin req_ota_begin = 116; + CtrlMsg_Req_OTAWrite req_ota_write = 117; + CtrlMsg_Req_OTAEnd req_ota_end = 118; + + CtrlMsg_Req_SetWifiMaxTxPower req_set_wifi_max_tx_power = 119; + CtrlMsg_Req_GetWifiCurrTxPower req_get_wifi_curr_tx_power = 120; + CtrlMsg_Req_ConfigHeartbeat req_config_heartbeat = 121; + + /** Responses **/ + CtrlMsg_Resp_GetMacAddress resp_get_mac_address = 201; + CtrlMsg_Resp_SetMacAddress resp_set_mac_address = 202; + CtrlMsg_Resp_GetMode resp_get_wifi_mode = 203; + CtrlMsg_Resp_SetMode resp_set_wifi_mode = 204; + + CtrlMsg_Resp_ScanResult resp_scan_ap_list = 205; + CtrlMsg_Resp_GetAPConfig resp_get_ap_config = 206; + CtrlMsg_Resp_ConnectAP resp_connect_ap = 207; + CtrlMsg_Resp_GetStatus resp_disconnect_ap = 208; + + CtrlMsg_Resp_GetSoftAPConfig resp_get_softap_config = 209; + CtrlMsg_Resp_SetSoftAPVendorSpecificIE resp_set_softap_vendor_specific_ie = 210; + CtrlMsg_Resp_StartSoftAP resp_start_softap = 211; + CtrlMsg_Resp_SoftAPConnectedSTA resp_softap_connected_stas_list = 212; + CtrlMsg_Resp_GetStatus resp_stop_softap = 213; + + CtrlMsg_Resp_SetMode resp_set_power_save_mode = 214; + CtrlMsg_Resp_GetMode resp_get_power_save_mode = 215; + + CtrlMsg_Resp_OTABegin resp_ota_begin = 216; + CtrlMsg_Resp_OTAWrite resp_ota_write = 217; + CtrlMsg_Resp_OTAEnd resp_ota_end = 218; + CtrlMsg_Resp_SetWifiMaxTxPower resp_set_wifi_max_tx_power = 219; + CtrlMsg_Resp_GetWifiCurrTxPower resp_get_wifi_curr_tx_power = 220; + CtrlMsg_Resp_ConfigHeartbeat resp_config_heartbeat = 221; + + /** Notifications **/ + CtrlMsg_Event_ESPInit event_esp_init = 301; + CtrlMsg_Event_Heartbeat event_heartbeat = 302; + CtrlMsg_Event_StationDisconnectFromAP event_station_disconnect_from_AP = 303; + CtrlMsg_Event_StationDisconnectFromESPSoftAP event_station_disconnect_from_ESP_SoftAP = 304; + } +} diff --git a/embassy-net-esp-hosted/src/fmt.rs b/embassy-net-esp-hosted/src/fmt.rs new file mode 100644 index 000000000..91984bde1 --- /dev/null +++ b/embassy-net-esp-hosted/src/fmt.rs @@ -0,0 +1,257 @@ +#![macro_use] +#![allow(unused_macros)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[cfg(not(feature = "defmt"))] +macro_rules! unreachable { + ($($x:tt)*) => { + ::core::unreachable!($($x)*) + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*); + }; +} + +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +pub struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy-net-esp-hosted/src/ioctl.rs b/embassy-net-esp-hosted/src/ioctl.rs new file mode 100644 index 000000000..e2a6815aa --- /dev/null +++ b/embassy-net-esp-hosted/src/ioctl.rs @@ -0,0 +1,123 @@ +use core::cell::RefCell; +use core::future::poll_fn; +use core::task::Poll; + +use embassy_sync::waitqueue::WakerRegistration; + +use crate::fmt::Bytes; + +#[derive(Clone, Copy)] +pub struct PendingIoctl { + pub buf: *mut [u8], + pub req_len: usize, +} + +#[derive(Clone, Copy)] +enum IoctlState { + Pending(PendingIoctl), + Sent { buf: *mut [u8] }, + Done { resp_len: usize }, +} + +pub struct Shared(RefCell); + +struct SharedInner { + ioctl: IoctlState, + is_init: bool, + control_waker: WakerRegistration, + runner_waker: WakerRegistration, +} + +impl Shared { + pub fn new() -> Self { + Self(RefCell::new(SharedInner { + ioctl: IoctlState::Done { resp_len: 0 }, + is_init: false, + control_waker: WakerRegistration::new(), + runner_waker: WakerRegistration::new(), + })) + } + + pub async fn ioctl_wait_complete(&self) -> usize { + poll_fn(|cx| { + let mut this = self.0.borrow_mut(); + if let IoctlState::Done { resp_len } = this.ioctl { + Poll::Ready(resp_len) + } else { + this.control_waker.register(cx.waker()); + Poll::Pending + } + }) + .await + } + + pub async fn ioctl_wait_pending(&self) -> PendingIoctl { + let pending = poll_fn(|cx| { + let mut this = self.0.borrow_mut(); + if let IoctlState::Pending(pending) = this.ioctl { + Poll::Ready(pending) + } else { + this.runner_waker.register(cx.waker()); + Poll::Pending + } + }) + .await; + + self.0.borrow_mut().ioctl = IoctlState::Sent { buf: pending.buf }; + pending + } + + pub fn ioctl_cancel(&self) { + self.0.borrow_mut().ioctl = IoctlState::Done { resp_len: 0 }; + } + + pub async fn ioctl(&self, buf: &mut [u8], req_len: usize) -> usize { + trace!("ioctl req bytes: {:02x}", Bytes(&buf[..req_len])); + + { + let mut this = self.0.borrow_mut(); + this.ioctl = IoctlState::Pending(PendingIoctl { buf, req_len }); + this.runner_waker.wake(); + } + + self.ioctl_wait_complete().await + } + + pub fn ioctl_done(&self, response: &[u8]) { + let mut this = self.0.borrow_mut(); + if let IoctlState::Sent { buf } = this.ioctl { + trace!("ioctl resp bytes: {:02x}", Bytes(response)); + + // TODO fix this + (unsafe { &mut *buf }[..response.len()]).copy_from_slice(response); + + this.ioctl = IoctlState::Done { + resp_len: response.len(), + }; + this.control_waker.wake(); + } else { + warn!("IOCTL Response but no pending Ioctl"); + } + } + + // // // // // // // // // // // // // // // // // // // // + + pub fn init_done(&self) { + let mut this = self.0.borrow_mut(); + this.is_init = true; + this.control_waker.wake(); + } + + pub async fn init_wait(&self) { + poll_fn(|cx| { + let mut this = self.0.borrow_mut(); + if this.is_init { + Poll::Ready(()) + } else { + this.control_waker.register(cx.waker()); + Poll::Pending + } + }) + .await + } +} diff --git a/embassy-net-esp-hosted/src/lib.rs b/embassy-net-esp-hosted/src/lib.rs new file mode 100644 index 000000000..a35adfca0 --- /dev/null +++ b/embassy-net-esp-hosted/src/lib.rs @@ -0,0 +1,337 @@ +#![no_std] + +use control::Control; +use embassy_futures::select::{select3, Either3}; +use embassy_net_driver_channel as ch; +use embassy_time::{Duration, Instant, Timer}; +use embedded_hal::digital::{InputPin, OutputPin}; +use embedded_hal_async::digital::Wait; +use embedded_hal_async::spi::SpiDevice; +use ioctl::Shared; +use proto::CtrlMsg; + +use crate::ioctl::PendingIoctl; +use crate::proto::CtrlMsgPayload; + +mod proto; + +// must be first +mod fmt; + +mod control; +mod ioctl; + +const MTU: usize = 1514; + +macro_rules! impl_bytes { + ($t:ident) => { + impl $t { + pub const SIZE: usize = core::mem::size_of::(); + + #[allow(unused)] + pub fn to_bytes(&self) -> [u8; Self::SIZE] { + unsafe { core::mem::transmute(*self) } + } + + #[allow(unused)] + pub fn from_bytes(bytes: &[u8; Self::SIZE]) -> &Self { + let alignment = core::mem::align_of::(); + assert_eq!( + bytes.as_ptr().align_offset(alignment), + 0, + "{} is not aligned", + core::any::type_name::() + ); + unsafe { core::mem::transmute(bytes) } + } + + #[allow(unused)] + pub fn from_bytes_mut(bytes: &mut [u8; Self::SIZE]) -> &mut Self { + let alignment = core::mem::align_of::(); + assert_eq!( + bytes.as_ptr().align_offset(alignment), + 0, + "{} is not aligned", + core::any::type_name::() + ); + + unsafe { core::mem::transmute(bytes) } + } + } + }; +} + +#[repr(C, packed)] +#[derive(Clone, Copy, Debug, Default)] +struct PayloadHeader { + /// InterfaceType on lower 4 bits, number on higher 4 bits. + if_type_and_num: u8, + + /// Flags. + /// + /// bit 0: more fragments. + flags: u8, + + len: u16, + offset: u16, + checksum: u16, + seq_num: u16, + reserved2: u8, + + /// Packet type for HCI or PRIV interface, reserved otherwise + hci_priv_packet_type: u8, +} +impl_bytes!(PayloadHeader); + +#[allow(unused)] +#[repr(u8)] +enum InterfaceType { + Sta = 0, + Ap = 1, + Serial = 2, + Hci = 3, + Priv = 4, + Test = 5, +} + +const MAX_SPI_BUFFER_SIZE: usize = 1600; + +pub struct State { + shared: Shared, + ch: ch::State, +} + +impl State { + pub fn new() -> Self { + Self { + shared: Shared::new(), + ch: ch::State::new(), + } + } +} + +pub type NetDriver<'a> = ch::Device<'a, MTU>; + +pub async fn new<'a, SPI, IN, OUT>( + state: &'a mut State, + spi: SPI, + handshake: IN, + ready: IN, + reset: OUT, +) -> (NetDriver<'a>, Control<'a>, Runner<'a, SPI, IN, OUT>) +where + SPI: SpiDevice, + IN: InputPin + Wait, + OUT: OutputPin, +{ + let (ch_runner, device) = ch::new(&mut state.ch, [0; 6]); + let state_ch = ch_runner.state_runner(); + + let mut runner = Runner { + ch: ch_runner, + shared: &state.shared, + next_seq: 1, + handshake, + ready, + reset, + spi, + }; + runner.init().await; + + (device, Control::new(state_ch, &state.shared), runner) +} + +pub struct Runner<'a, SPI, IN, OUT> { + ch: ch::Runner<'a, MTU>, + shared: &'a Shared, + + next_seq: u16, + + spi: SPI, + handshake: IN, + ready: IN, + reset: OUT, +} + +impl<'a, SPI, IN, OUT> Runner<'a, SPI, IN, OUT> +where + SPI: SpiDevice, + IN: InputPin + Wait, + OUT: OutputPin, +{ + async fn init(&mut self) {} + + pub async fn run(mut self) -> ! { + debug!("resetting..."); + self.reset.set_low().unwrap(); + Timer::after(Duration::from_millis(100)).await; + self.reset.set_high().unwrap(); + Timer::after(Duration::from_millis(1000)).await; + + let mut tx_buf = [0u8; MAX_SPI_BUFFER_SIZE]; + let mut rx_buf = [0u8; MAX_SPI_BUFFER_SIZE]; + + loop { + self.handshake.wait_for_high().await.unwrap(); + + let ioctl = self.shared.ioctl_wait_pending(); + let tx = self.ch.tx_buf(); + let ev = async { self.ready.wait_for_high().await.unwrap() }; + + match select3(ioctl, tx, ev).await { + Either3::First(PendingIoctl { buf, req_len }) => { + tx_buf[12..24].copy_from_slice(b"\x01\x08\x00ctrlResp\x02"); + tx_buf[24..26].copy_from_slice(&(req_len as u16).to_le_bytes()); + tx_buf[26..][..req_len].copy_from_slice(&unsafe { &*buf }[..req_len]); + + let mut header = PayloadHeader { + if_type_and_num: InterfaceType::Serial as _, + len: (req_len + 14) as _, + offset: PayloadHeader::SIZE as _, + seq_num: self.next_seq, + ..Default::default() + }; + self.next_seq = self.next_seq.wrapping_add(1); + + // Calculate checksum + tx_buf[0..12].copy_from_slice(&header.to_bytes()); + header.checksum = checksum(&tx_buf[..26 + req_len]); + tx_buf[0..12].copy_from_slice(&header.to_bytes()); + } + Either3::Second(packet) => { + tx_buf[12..][..packet.len()].copy_from_slice(packet); + + let mut header = PayloadHeader { + if_type_and_num: InterfaceType::Sta as _, + len: packet.len() as _, + offset: PayloadHeader::SIZE as _, + seq_num: self.next_seq, + ..Default::default() + }; + self.next_seq = self.next_seq.wrapping_add(1); + + // Calculate checksum + tx_buf[0..12].copy_from_slice(&header.to_bytes()); + header.checksum = checksum(&tx_buf[..12 + packet.len()]); + tx_buf[0..12].copy_from_slice(&header.to_bytes()); + + self.ch.tx_done(); + } + Either3::Third(()) => { + tx_buf[..PayloadHeader::SIZE].fill(0); + } + } + + if tx_buf[0] != 0 { + trace!("tx: {:02x}", &tx_buf[..40]); + } + + self.spi.transfer(&mut rx_buf, &tx_buf).await.unwrap(); + + // The esp-hosted firmware deasserts the HANSHAKE pin a few us AFTER ending the SPI transfer + // If we check it again too fast, we'll see it's high from the previous transfer, and if we send it + // data it will get lost. + // Make sure we check it after 100us at minimum. + let delay_until = Instant::now() + Duration::from_micros(100); + self.handle_rx(&mut rx_buf); + Timer::at(delay_until).await; + } + } + + fn handle_rx(&mut self, buf: &mut [u8]) { + trace!("rx: {:02x}", &buf[..40]); + + let buf_len = buf.len(); + let h = PayloadHeader::from_bytes_mut((&mut buf[..PayloadHeader::SIZE]).try_into().unwrap()); + + if h.len == 0 || h.offset as usize != PayloadHeader::SIZE { + return; + } + + let payload_len = h.len as usize; + if buf_len < PayloadHeader::SIZE + payload_len { + warn!("rx: len too big"); + return; + } + + let if_type_and_num = h.if_type_and_num; + let want_checksum = h.checksum; + h.checksum = 0; + let got_checksum = checksum(&buf[..PayloadHeader::SIZE + payload_len]); + if want_checksum != got_checksum { + warn!("rx: bad checksum. Got {:04x}, want {:04x}", got_checksum, want_checksum); + return; + } + + let payload = &mut buf[PayloadHeader::SIZE..][..payload_len]; + + match if_type_and_num & 0x0f { + // STA + 0 => match self.ch.try_rx_buf() { + Some(buf) => { + buf[..payload.len()].copy_from_slice(payload); + self.ch.rx_done(payload.len()) + } + None => warn!("failed to push rxd packet to the channel."), + }, + // serial + 2 => { + trace!("serial rx: {:02x}", payload); + if payload.len() < 14 { + warn!("serial rx: too short"); + return; + } + + let is_event = match &payload[..12] { + b"\x01\x08\x00ctrlResp\x02" => false, + b"\x01\x08\x00ctrlEvnt\x02" => true, + _ => { + warn!("serial rx: bad tlv"); + return; + } + }; + + let len = u16::from_le_bytes(payload[12..14].try_into().unwrap()) as usize; + if payload.len() < 14 + len { + warn!("serial rx: too short 2"); + return; + } + let data = &payload[14..][..len]; + + if is_event { + self.handle_event(data); + } else { + self.shared.ioctl_done(data); + } + } + _ => warn!("unknown iftype {}", if_type_and_num), + } + } + + fn handle_event(&self, data: &[u8]) { + let Ok(event) = noproto::read::(data) else { + warn!("failed to parse event"); + return; + }; + + debug!("event: {:?}", &event); + + let Some(payload) = &event.payload else { + warn!("event without payload?"); + return; + }; + + match payload { + CtrlMsgPayload::EventEspInit(_) => self.shared.init_done(), + _ => {} + } + } +} + +fn checksum(buf: &[u8]) -> u16 { + let mut res = 0u16; + for &b in buf { + res = res.wrapping_add(b as _); + } + res +} diff --git a/embassy-net-esp-hosted/src/proto.rs b/embassy-net-esp-hosted/src/proto.rs new file mode 100644 index 000000000..8ceb1579d --- /dev/null +++ b/embassy-net-esp-hosted/src/proto.rs @@ -0,0 +1,652 @@ +use heapless::{String, Vec}; + +/// internal supporting structures for CtrlMsg + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ScanResult { + #[noproto(tag = "1")] + pub ssid: String<32>, + #[noproto(tag = "2")] + pub chnl: u32, + #[noproto(tag = "3")] + pub rssi: u32, + #[noproto(tag = "4")] + pub bssid: String<32>, + #[noproto(tag = "5")] + pub sec_prot: CtrlWifiSecProt, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ConnectedStaList { + #[noproto(tag = "1")] + pub mac: String<32>, + #[noproto(tag = "2")] + pub rssi: u32, +} +/// * Req/Resp structure * + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqGetMacAddress { + #[noproto(tag = "1")] + pub mode: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgRespGetMacAddress { + #[noproto(tag = "1")] + pub mac: String<32>, + #[noproto(tag = "2")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqGetMode {} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgRespGetMode { + #[noproto(tag = "1")] + pub mode: u32, + #[noproto(tag = "2")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqSetMode { + #[noproto(tag = "1")] + pub mode: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgRespSetMode { + #[noproto(tag = "1")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqGetStatus {} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgRespGetStatus { + #[noproto(tag = "1")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqSetMacAddress { + #[noproto(tag = "1")] + pub mac: String<32>, + #[noproto(tag = "2")] + pub mode: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgRespSetMacAddress { + #[noproto(tag = "1")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqGetApConfig {} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgRespGetApConfig { + #[noproto(tag = "1")] + pub ssid: String<32>, + #[noproto(tag = "2")] + pub bssid: String<32>, + #[noproto(tag = "3")] + pub rssi: u32, + #[noproto(tag = "4")] + pub chnl: u32, + #[noproto(tag = "5")] + pub sec_prot: CtrlWifiSecProt, + #[noproto(tag = "6")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqConnectAp { + #[noproto(tag = "1")] + pub ssid: String<32>, + #[noproto(tag = "2")] + pub pwd: String<32>, + #[noproto(tag = "3")] + pub bssid: String<32>, + #[noproto(tag = "4")] + pub is_wpa3_supported: bool, + #[noproto(tag = "5")] + pub listen_interval: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgRespConnectAp { + #[noproto(tag = "1")] + pub resp: u32, + #[noproto(tag = "2")] + pub mac: String<32>, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqGetSoftApConfig {} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgRespGetSoftApConfig { + #[noproto(tag = "1")] + pub ssid: String<32>, + #[noproto(tag = "2")] + pub pwd: String<32>, + #[noproto(tag = "3")] + pub chnl: u32, + #[noproto(tag = "4")] + pub sec_prot: CtrlWifiSecProt, + #[noproto(tag = "5")] + pub max_conn: u32, + #[noproto(tag = "6")] + pub ssid_hidden: bool, + #[noproto(tag = "7")] + pub bw: u32, + #[noproto(tag = "8")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqStartSoftAp { + #[noproto(tag = "1")] + pub ssid: String<32>, + #[noproto(tag = "2")] + pub pwd: String<32>, + #[noproto(tag = "3")] + pub chnl: u32, + #[noproto(tag = "4")] + pub sec_prot: CtrlWifiSecProt, + #[noproto(tag = "5")] + pub max_conn: u32, + #[noproto(tag = "6")] + pub ssid_hidden: bool, + #[noproto(tag = "7")] + pub bw: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgRespStartSoftAp { + #[noproto(tag = "1")] + pub resp: u32, + #[noproto(tag = "2")] + pub mac: String<32>, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqScanResult {} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgRespScanResult { + #[noproto(tag = "1")] + pub count: u32, + #[noproto(repeated, tag = "2")] + pub entries: Vec, + #[noproto(tag = "3")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqSoftApConnectedSta {} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgRespSoftApConnectedSta { + #[noproto(tag = "1")] + pub num: u32, + #[noproto(repeated, tag = "2")] + pub stations: Vec, + #[noproto(tag = "3")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqOtaBegin {} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgRespOtaBegin { + #[noproto(tag = "1")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqOtaWrite { + #[noproto(tag = "1")] + pub ota_data: Vec, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgRespOtaWrite { + #[noproto(tag = "1")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqOtaEnd {} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgRespOtaEnd { + #[noproto(tag = "1")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqVendorIeData { + #[noproto(tag = "1")] + pub element_id: u32, + #[noproto(tag = "2")] + pub length: u32, + #[noproto(tag = "3")] + pub vendor_oui: Vec, + #[noproto(tag = "4")] + pub vendor_oui_type: u32, + #[noproto(tag = "5")] + pub payload: Vec, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqSetSoftApVendorSpecificIe { + #[noproto(tag = "1")] + pub enable: bool, + #[noproto(tag = "2")] + pub r#type: CtrlVendorIeType, + #[noproto(tag = "3")] + pub idx: CtrlVendorIeid, + #[noproto(optional, tag = "4")] + pub vendor_ie_data: Option, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgRespSetSoftApVendorSpecificIe { + #[noproto(tag = "1")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqSetWifiMaxTxPower { + #[noproto(tag = "1")] + pub wifi_max_tx_power: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgRespSetWifiMaxTxPower { + #[noproto(tag = "1")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqGetWifiCurrTxPower {} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgRespGetWifiCurrTxPower { + #[noproto(tag = "1")] + pub wifi_curr_tx_power: u32, + #[noproto(tag = "2")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgReqConfigHeartbeat { + #[noproto(tag = "1")] + pub enable: bool, + #[noproto(tag = "2")] + pub duration: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgRespConfigHeartbeat { + #[noproto(tag = "1")] + pub resp: u32, +} +/// * Event structure * + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgEventEspInit { + #[noproto(tag = "1")] + pub init_data: Vec, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgEventHeartbeat { + #[noproto(tag = "1")] + pub hb_num: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgEventStationDisconnectFromAp { + #[noproto(tag = "1")] + pub resp: u32, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsgEventStationDisconnectFromEspSoftAp { + #[noproto(tag = "1")] + pub resp: u32, + #[noproto(tag = "2")] + pub mac: String<32>, +} + +#[derive(Debug, Default, Clone, Eq, PartialEq, noproto::Message)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CtrlMsg { + /// msg_type could be req, resp or Event + #[noproto(tag = "1")] + pub msg_type: CtrlMsgType, + /// msg id + #[noproto(tag = "2")] + pub msg_id: CtrlMsgId, + /// union of all msg ids + #[noproto( + oneof, + tags = "101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 301, 302, 303, 304" + )] + pub payload: Option, +} + +/// union of all msg ids +#[derive(Debug, Clone, Eq, PartialEq, noproto::Oneof)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum CtrlMsgPayload { + /// * Requests * + #[noproto(tag = "101")] + ReqGetMacAddress(CtrlMsgReqGetMacAddress), + #[noproto(tag = "102")] + ReqSetMacAddress(CtrlMsgReqSetMacAddress), + #[noproto(tag = "103")] + ReqGetWifiMode(CtrlMsgReqGetMode), + #[noproto(tag = "104")] + ReqSetWifiMode(CtrlMsgReqSetMode), + #[noproto(tag = "105")] + ReqScanApList(CtrlMsgReqScanResult), + #[noproto(tag = "106")] + ReqGetApConfig(CtrlMsgReqGetApConfig), + #[noproto(tag = "107")] + ReqConnectAp(CtrlMsgReqConnectAp), + #[noproto(tag = "108")] + ReqDisconnectAp(CtrlMsgReqGetStatus), + #[noproto(tag = "109")] + ReqGetSoftapConfig(CtrlMsgReqGetSoftApConfig), + #[noproto(tag = "110")] + ReqSetSoftapVendorSpecificIe(CtrlMsgReqSetSoftApVendorSpecificIe), + #[noproto(tag = "111")] + ReqStartSoftap(CtrlMsgReqStartSoftAp), + #[noproto(tag = "112")] + ReqSoftapConnectedStasList(CtrlMsgReqSoftApConnectedSta), + #[noproto(tag = "113")] + ReqStopSoftap(CtrlMsgReqGetStatus), + #[noproto(tag = "114")] + ReqSetPowerSaveMode(CtrlMsgReqSetMode), + #[noproto(tag = "115")] + ReqGetPowerSaveMode(CtrlMsgReqGetMode), + #[noproto(tag = "116")] + ReqOtaBegin(CtrlMsgReqOtaBegin), + #[noproto(tag = "117")] + ReqOtaWrite(CtrlMsgReqOtaWrite), + #[noproto(tag = "118")] + ReqOtaEnd(CtrlMsgReqOtaEnd), + #[noproto(tag = "119")] + ReqSetWifiMaxTxPower(CtrlMsgReqSetWifiMaxTxPower), + #[noproto(tag = "120")] + ReqGetWifiCurrTxPower(CtrlMsgReqGetWifiCurrTxPower), + #[noproto(tag = "121")] + ReqConfigHeartbeat(CtrlMsgReqConfigHeartbeat), + /// * Responses * + #[noproto(tag = "201")] + RespGetMacAddress(CtrlMsgRespGetMacAddress), + #[noproto(tag = "202")] + RespSetMacAddress(CtrlMsgRespSetMacAddress), + #[noproto(tag = "203")] + RespGetWifiMode(CtrlMsgRespGetMode), + #[noproto(tag = "204")] + RespSetWifiMode(CtrlMsgRespSetMode), + #[noproto(tag = "205")] + RespScanApList(CtrlMsgRespScanResult), + #[noproto(tag = "206")] + RespGetApConfig(CtrlMsgRespGetApConfig), + #[noproto(tag = "207")] + RespConnectAp(CtrlMsgRespConnectAp), + #[noproto(tag = "208")] + RespDisconnectAp(CtrlMsgRespGetStatus), + #[noproto(tag = "209")] + RespGetSoftapConfig(CtrlMsgRespGetSoftApConfig), + #[noproto(tag = "210")] + RespSetSoftapVendorSpecificIe(CtrlMsgRespSetSoftApVendorSpecificIe), + #[noproto(tag = "211")] + RespStartSoftap(CtrlMsgRespStartSoftAp), + #[noproto(tag = "212")] + RespSoftapConnectedStasList(CtrlMsgRespSoftApConnectedSta), + #[noproto(tag = "213")] + RespStopSoftap(CtrlMsgRespGetStatus), + #[noproto(tag = "214")] + RespSetPowerSaveMode(CtrlMsgRespSetMode), + #[noproto(tag = "215")] + RespGetPowerSaveMode(CtrlMsgRespGetMode), + #[noproto(tag = "216")] + RespOtaBegin(CtrlMsgRespOtaBegin), + #[noproto(tag = "217")] + RespOtaWrite(CtrlMsgRespOtaWrite), + #[noproto(tag = "218")] + RespOtaEnd(CtrlMsgRespOtaEnd), + #[noproto(tag = "219")] + RespSetWifiMaxTxPower(CtrlMsgRespSetWifiMaxTxPower), + #[noproto(tag = "220")] + RespGetWifiCurrTxPower(CtrlMsgRespGetWifiCurrTxPower), + #[noproto(tag = "221")] + RespConfigHeartbeat(CtrlMsgRespConfigHeartbeat), + /// * Notifications * + #[noproto(tag = "301")] + EventEspInit(CtrlMsgEventEspInit), + #[noproto(tag = "302")] + EventHeartbeat(CtrlMsgEventHeartbeat), + #[noproto(tag = "303")] + EventStationDisconnectFromAp(CtrlMsgEventStationDisconnectFromAp), + #[noproto(tag = "304")] + EventStationDisconnectFromEspSoftAp(CtrlMsgEventStationDisconnectFromEspSoftAp), +} + +/// Enums similar to ESP IDF +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)] +#[repr(u32)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum CtrlVendorIeType { + #[default] + Beacon = 0, + ProbeReq = 1, + ProbeResp = 2, + AssocReq = 3, + AssocResp = 4, +} + +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)] +#[repr(u32)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum CtrlVendorIeid { + #[default] + Id0 = 0, + Id1 = 1, +} + +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)] +#[repr(u32)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum CtrlWifiMode { + #[default] + None = 0, + Sta = 1, + Ap = 2, + Apsta = 3, +} + +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)] +#[repr(u32)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum CtrlWifiBw { + #[default] + BwInvalid = 0, + Ht20 = 1, + Ht40 = 2, +} + +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)] +#[repr(u32)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum CtrlWifiPowerSave { + #[default] + PsInvalid = 0, + MinModem = 1, + MaxModem = 2, +} + +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)] +#[repr(u32)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum CtrlWifiSecProt { + #[default] + Open = 0, + Wep = 1, + WpaPsk = 2, + Wpa2Psk = 3, + WpaWpa2Psk = 4, + Wpa2Enterprise = 5, + Wpa3Psk = 6, + Wpa2Wpa3Psk = 7, +} + +/// enums for Control path +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)] +#[repr(u32)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum CtrlStatus { + #[default] + Connected = 0, + NotConnected = 1, + NoApFound = 2, + ConnectionFail = 3, + InvalidArgument = 4, + OutOfRange = 5, +} + +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)] +#[repr(u32)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum CtrlMsgType { + #[default] + MsgTypeInvalid = 0, + Req = 1, + Resp = 2, + Event = 3, + MsgTypeMax = 4, +} + +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, noproto::Enumeration)] +#[repr(u32)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum CtrlMsgId { + #[default] + MsgIdInvalid = 0, + /// * Request Msgs * + ReqBase = 100, + ReqGetMacAddress = 101, + ReqSetMacAddress = 102, + ReqGetWifiMode = 103, + ReqSetWifiMode = 104, + ReqGetApScanList = 105, + ReqGetApConfig = 106, + ReqConnectAp = 107, + ReqDisconnectAp = 108, + ReqGetSoftApConfig = 109, + ReqSetSoftApVendorSpecificIe = 110, + ReqStartSoftAp = 111, + ReqGetSoftApConnectedStaList = 112, + ReqStopSoftAp = 113, + ReqSetPowerSaveMode = 114, + ReqGetPowerSaveMode = 115, + ReqOtaBegin = 116, + ReqOtaWrite = 117, + ReqOtaEnd = 118, + ReqSetWifiMaxTxPower = 119, + ReqGetWifiCurrTxPower = 120, + ReqConfigHeartbeat = 121, + /// Add new control path command response before Req_Max + /// and update Req_Max + ReqMax = 122, + /// * Response Msgs * + RespBase = 200, + RespGetMacAddress = 201, + RespSetMacAddress = 202, + RespGetWifiMode = 203, + RespSetWifiMode = 204, + RespGetApScanList = 205, + RespGetApConfig = 206, + RespConnectAp = 207, + RespDisconnectAp = 208, + RespGetSoftApConfig = 209, + RespSetSoftApVendorSpecificIe = 210, + RespStartSoftAp = 211, + RespGetSoftApConnectedStaList = 212, + RespStopSoftAp = 213, + RespSetPowerSaveMode = 214, + RespGetPowerSaveMode = 215, + RespOtaBegin = 216, + RespOtaWrite = 217, + RespOtaEnd = 218, + RespSetWifiMaxTxPower = 219, + RespGetWifiCurrTxPower = 220, + RespConfigHeartbeat = 221, + /// Add new control path command response before Resp_Max + /// and update Resp_Max + RespMax = 222, + /// * Event Msgs * + EventBase = 300, + EventEspInit = 301, + EventHeartbeat = 302, + EventStationDisconnectFromAp = 303, + EventStationDisconnectFromEspSoftAp = 304, + /// Add new control path command notification before Event_Max + /// and update Event_Max + EventMax = 305, +} diff --git a/embassy-net-w5500/Cargo.toml b/embassy-net-w5500/Cargo.toml new file mode 100644 index 000000000..8972b814a --- /dev/null +++ b/embassy-net-w5500/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "embassy-net-w5500" +version = "0.1.0" +description = "embassy-net driver for the W5500 ethernet chip" +keywords = ["embedded", "w5500", "embassy-net", "embedded-hal-async", "ethernet", "async"] +categories = ["embedded", "hardware-support", "no-std", "network-programming", "async"] +license = "MIT OR Apache-2.0" +edition = "2021" + +[dependencies] +embedded-hal = { version = "1.0.0-alpha.11" } +embedded-hal-async = { version = "=0.2.0-alpha.2" } +embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel" } +embassy-time = { version = "0.1.2", path = "../embassy-time" } +embassy-futures = { version = "0.1.0", path = "../embassy-futures" } +defmt = { version = "0.3", optional = true } + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-w5500-v$VERSION/embassy-net-w5500/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net-w5500/src/" +target = "thumbv7em-none-eabi" \ No newline at end of file diff --git a/embassy-net-w5500/README.md b/embassy-net-w5500/README.md new file mode 100644 index 000000000..9eaf4b700 --- /dev/null +++ b/embassy-net-w5500/README.md @@ -0,0 +1,7 @@ +# WIZnet W5500 `embassy-net` integration + +[`embassy-net`](https://crates.io/crates/embassy-net) integration for the WIZnet W5500 SPI ethernet chip, operating in MACRAW mode. + +Supports any SPI driver implementing [`embedded-hal-async`](https://crates.io/crates/embedded-hal-async) + +See [`examples`](https://github.com/kalkyl/embassy-net-w5500/tree/main/examples) directory for usage examples with the rp2040 [`WIZnet W5500-EVB-Pico`](https://www.wiznet.io/product-item/w5500-evb-pico/) module. \ No newline at end of file diff --git a/embassy-net-w5500/src/device.rs b/embassy-net-w5500/src/device.rs new file mode 100644 index 000000000..9874df0d6 --- /dev/null +++ b/embassy-net-w5500/src/device.rs @@ -0,0 +1,131 @@ +use embedded_hal_async::spi::SpiDevice; + +use crate::socket; +use crate::spi::SpiInterface; + +pub const MODE: u16 = 0x00; +pub const MAC: u16 = 0x09; +pub const SOCKET_INTR: u16 = 0x18; +pub const PHY_CFG: u16 = 0x2E; + +#[repr(u8)] +pub enum RegisterBlock { + Common = 0x00, + Socket0 = 0x01, + TxBuf = 0x02, + RxBuf = 0x03, +} + +/// W5500 in MACRAW mode +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct W5500 { + bus: SpiInterface, +} + +impl W5500 { + /// Create and initialize the W5500 driver + pub async fn new(spi: SPI, mac_addr: [u8; 6]) -> Result, SPI::Error> { + let mut bus = SpiInterface(spi); + // Reset device + bus.write_frame(RegisterBlock::Common, MODE, &[0x80]).await?; + + // Enable interrupt pin + bus.write_frame(RegisterBlock::Common, SOCKET_INTR, &[0x01]).await?; + // Enable receive interrupt + bus.write_frame( + RegisterBlock::Socket0, + socket::SOCKET_INTR_MASK, + &[socket::Interrupt::Receive as u8], + ) + .await?; + + // Set MAC address + bus.write_frame(RegisterBlock::Common, MAC, &mac_addr).await?; + + // Set the raw socket RX/TX buffer sizes to 16KB + bus.write_frame(RegisterBlock::Socket0, socket::TXBUF_SIZE, &[16]) + .await?; + bus.write_frame(RegisterBlock::Socket0, socket::RXBUF_SIZE, &[16]) + .await?; + + // MACRAW mode with MAC filtering. + let mode: u8 = (1 << 2) | (1 << 7); + bus.write_frame(RegisterBlock::Socket0, socket::MODE, &[mode]).await?; + socket::command(&mut bus, socket::Command::Open).await?; + + Ok(Self { bus }) + } + + /// Read bytes from the RX buffer. Returns the number of bytes read. + async fn read_bytes(&mut self, buffer: &mut [u8], offset: u16) -> Result { + let rx_size = socket::get_rx_size(&mut self.bus).await? as usize; + + let read_buffer = if rx_size > buffer.len() + offset as usize { + buffer + } else { + &mut buffer[..rx_size - offset as usize] + }; + + let read_ptr = socket::get_rx_read_ptr(&mut self.bus).await?.wrapping_add(offset); + self.bus.read_frame(RegisterBlock::RxBuf, read_ptr, read_buffer).await?; + socket::set_rx_read_ptr(&mut self.bus, read_ptr.wrapping_add(read_buffer.len() as u16)).await?; + + Ok(read_buffer.len()) + } + + /// Read an ethernet frame from the device. Returns the number of bytes read. + pub async fn read_frame(&mut self, frame: &mut [u8]) -> Result { + let rx_size = socket::get_rx_size(&mut self.bus).await? as usize; + if rx_size == 0 { + return Ok(0); + } + + socket::reset_interrupt(&mut self.bus, socket::Interrupt::Receive).await?; + + // First two bytes gives the size of the received ethernet frame + let expected_frame_size: usize = { + let mut frame_bytes = [0u8; 2]; + assert!(self.read_bytes(&mut frame_bytes[..], 0).await? == 2); + u16::from_be_bytes(frame_bytes) as usize - 2 + }; + + // Read the ethernet frame + let read_buffer = if frame.len() > expected_frame_size { + &mut frame[..expected_frame_size] + } else { + frame + }; + + let recvd_frame_size = self.read_bytes(read_buffer, 2).await?; + + // Register RX as completed + socket::command(&mut self.bus, socket::Command::Receive).await?; + + // If the whole frame wasn't read, drop it + if recvd_frame_size < expected_frame_size { + Ok(0) + } else { + Ok(recvd_frame_size) + } + } + + /// Write an ethernet frame to the device. Returns number of bytes written + pub async fn write_frame(&mut self, frame: &[u8]) -> Result { + while socket::get_tx_free_size(&mut self.bus).await? < frame.len() as u16 {} + let write_ptr = socket::get_tx_write_ptr(&mut self.bus).await?; + self.bus.write_frame(RegisterBlock::TxBuf, write_ptr, frame).await?; + socket::set_tx_write_ptr(&mut self.bus, write_ptr.wrapping_add(frame.len() as u16)).await?; + socket::command(&mut self.bus, socket::Command::Send).await?; + Ok(frame.len()) + } + + pub async fn is_link_up(&mut self) -> bool { + let mut link = [0]; + self.bus + .read_frame(RegisterBlock::Common, PHY_CFG, &mut link) + .await + .ok(); + link[0] & 1 == 1 + } +} diff --git a/embassy-net-w5500/src/lib.rs b/embassy-net-w5500/src/lib.rs new file mode 100644 index 000000000..efd9bed66 --- /dev/null +++ b/embassy-net-w5500/src/lib.rs @@ -0,0 +1,109 @@ +//! [`embassy-net`](https://crates.io/crates/embassy-net) driver for the WIZnet W5500 ethernet chip. +#![no_std] + +mod device; +mod socket; +mod spi; + +use embassy_futures::select::{select, Either}; +use embassy_net_driver_channel as ch; +use embassy_net_driver_channel::driver::LinkState; +use embassy_time::{Duration, Timer}; +use embedded_hal::digital::OutputPin; +use embedded_hal_async::digital::Wait; +use embedded_hal_async::spi::SpiDevice; + +use crate::device::W5500; +const MTU: usize = 1514; + +/// Type alias for the embassy-net driver for W5500 +pub type Device<'d> = embassy_net_driver_channel::Device<'d, MTU>; + +/// Internal state for the embassy-net integration. +pub struct State { + ch_state: ch::State, +} + +impl State { + /// Create a new `State`. + pub const fn new() -> Self { + Self { + ch_state: ch::State::new(), + } + } +} + +/// Background runner for the W5500. +/// +/// You must call `.run()` in a background task for the W5500 to operate. +pub struct Runner<'d, SPI: SpiDevice, INT: Wait, RST: OutputPin> { + mac: W5500, + ch: ch::Runner<'d, MTU>, + int: INT, + _reset: RST, +} + +/// You must call this in a background task for the W5500 to operate. +impl<'d, SPI: SpiDevice, INT: Wait, RST: OutputPin> Runner<'d, SPI, INT, RST> { + pub async fn run(mut self) -> ! { + let (state_chan, mut rx_chan, mut tx_chan) = self.ch.split(); + loop { + if self.mac.is_link_up().await { + state_chan.set_link_state(LinkState::Up); + loop { + match select( + async { + self.int.wait_for_low().await.ok(); + rx_chan.rx_buf().await + }, + tx_chan.tx_buf(), + ) + .await + { + Either::First(p) => { + if let Ok(n) = self.mac.read_frame(p).await { + rx_chan.rx_done(n); + } + } + Either::Second(p) => { + self.mac.write_frame(p).await.ok(); + tx_chan.tx_done(); + } + } + } + } else { + state_chan.set_link_state(LinkState::Down); + } + } + } +} + +/// Obtain a driver for using the W5500 with [`embassy-net`](https://crates.io/crates/embassy-net). +pub async fn new<'a, const N_RX: usize, const N_TX: usize, SPI: SpiDevice, INT: Wait, RST: OutputPin>( + mac_addr: [u8; 6], + state: &'a mut State, + spi_dev: SPI, + int: INT, + mut reset: RST, +) -> (Device<'a>, Runner<'a, SPI, INT, RST>) { + // Reset the W5500. + reset.set_low().ok(); + // Ensure the reset is registered. + Timer::after(Duration::from_millis(1)).await; + reset.set_high().ok(); + // Wait for the W5500 to achieve PLL lock. + Timer::after(Duration::from_millis(2)).await; + + let mac = W5500::new(spi_dev, mac_addr).await.unwrap(); + + let (runner, device) = ch::new(&mut state.ch_state, mac_addr); + ( + device, + Runner { + ch: runner, + mac, + int, + _reset: reset, + }, + ) +} diff --git a/embassy-net-w5500/src/socket.rs b/embassy-net-w5500/src/socket.rs new file mode 100644 index 000000000..3d65583c1 --- /dev/null +++ b/embassy-net-w5500/src/socket.rs @@ -0,0 +1,80 @@ +use embedded_hal_async::spi::SpiDevice; + +use crate::device::RegisterBlock; +use crate::spi::SpiInterface; + +pub const MODE: u16 = 0x00; +pub const COMMAND: u16 = 0x01; +pub const RXBUF_SIZE: u16 = 0x1E; +pub const TXBUF_SIZE: u16 = 0x1F; +pub const TX_FREE_SIZE: u16 = 0x20; +pub const TX_DATA_WRITE_PTR: u16 = 0x24; +pub const RECVD_SIZE: u16 = 0x26; +pub const RX_DATA_READ_PTR: u16 = 0x28; +pub const SOCKET_INTR_MASK: u16 = 0x2C; + +#[repr(u8)] +pub enum Command { + Open = 0x01, + Send = 0x20, + Receive = 0x40, +} + +pub const INTR: u16 = 0x02; +#[repr(u8)] +pub enum Interrupt { + Receive = 0b00100_u8, +} + +pub async fn reset_interrupt(bus: &mut SpiInterface, code: Interrupt) -> Result<(), SPI::Error> { + let data = [code as u8]; + bus.write_frame(RegisterBlock::Socket0, INTR, &data).await +} + +pub async fn get_tx_write_ptr(bus: &mut SpiInterface) -> Result { + let mut data = [0u8; 2]; + bus.read_frame(RegisterBlock::Socket0, TX_DATA_WRITE_PTR, &mut data) + .await?; + Ok(u16::from_be_bytes(data)) +} + +pub async fn set_tx_write_ptr(bus: &mut SpiInterface, ptr: u16) -> Result<(), SPI::Error> { + let data = ptr.to_be_bytes(); + bus.write_frame(RegisterBlock::Socket0, TX_DATA_WRITE_PTR, &data).await +} + +pub async fn get_rx_read_ptr(bus: &mut SpiInterface) -> Result { + let mut data = [0u8; 2]; + bus.read_frame(RegisterBlock::Socket0, RX_DATA_READ_PTR, &mut data) + .await?; + Ok(u16::from_be_bytes(data)) +} + +pub async fn set_rx_read_ptr(bus: &mut SpiInterface, ptr: u16) -> Result<(), SPI::Error> { + let data = ptr.to_be_bytes(); + bus.write_frame(RegisterBlock::Socket0, RX_DATA_READ_PTR, &data).await +} + +pub async fn command(bus: &mut SpiInterface, command: Command) -> Result<(), SPI::Error> { + let data = [command as u8]; + bus.write_frame(RegisterBlock::Socket0, COMMAND, &data).await +} + +pub async fn get_rx_size(bus: &mut SpiInterface) -> Result { + loop { + // Wait until two sequential reads are equal + let mut res0 = [0u8; 2]; + bus.read_frame(RegisterBlock::Socket0, RECVD_SIZE, &mut res0).await?; + let mut res1 = [0u8; 2]; + bus.read_frame(RegisterBlock::Socket0, RECVD_SIZE, &mut res1).await?; + if res0 == res1 { + break Ok(u16::from_be_bytes(res0)); + } + } +} + +pub async fn get_tx_free_size(bus: &mut SpiInterface) -> Result { + let mut data = [0; 2]; + bus.read_frame(RegisterBlock::Socket0, TX_FREE_SIZE, &mut data).await?; + Ok(u16::from_be_bytes(data)) +} diff --git a/embassy-net-w5500/src/spi.rs b/embassy-net-w5500/src/spi.rs new file mode 100644 index 000000000..07749d6be --- /dev/null +++ b/embassy-net-w5500/src/spi.rs @@ -0,0 +1,32 @@ +use embedded_hal_async::spi::{Operation, SpiDevice}; + +use crate::device::RegisterBlock; + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SpiInterface(pub SPI); + +impl SpiInterface { + pub async fn read_frame(&mut self, block: RegisterBlock, address: u16, data: &mut [u8]) -> Result<(), SPI::Error> { + let address_phase = address.to_be_bytes(); + let control_phase = [(block as u8) << 3]; + let operations = &mut [ + Operation::Write(&address_phase), + Operation::Write(&control_phase), + Operation::TransferInPlace(data), + ]; + self.0.transaction(operations).await + } + + pub async fn write_frame(&mut self, block: RegisterBlock, address: u16, data: &[u8]) -> Result<(), SPI::Error> { + let address_phase = address.to_be_bytes(); + let control_phase = [(block as u8) << 3 | 0b0000_0100]; + let data_phase = data; + let operations = &mut [ + Operation::Write(&address_phase[..]), + Operation::Write(&control_phase), + Operation::Write(&data_phase), + ]; + self.0.transaction(operations).await + } +} diff --git a/embassy-net/Cargo.toml b/embassy-net/Cargo.toml index 2143f36d3..6dc429ddc 100644 --- a/embassy-net/Cargo.toml +++ b/embassy-net/Cargo.toml @@ -2,44 +2,57 @@ name = "embassy-net" version = "0.1.0" edition = "2021" - +license = "MIT OR Apache-2.0" +description = "Async TCP/IP network stack for embedded systems" +repository = "https://github.com/embassy-rs/embassy" +categories = [ + "embedded", + "no-std", + "asynchronous", +] [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-net-v$VERSION/embassy-net/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-net/src/" -features = [ "pool-4", "defmt", "tcp", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "embassy-time/tick-1mhz"] +features = ["nightly", "unstable-traits", "defmt", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "igmp"] target = "thumbv7em-none-eabi" +[package.metadata.docs.rs] +features = ["nightly", "unstable-traits", "defmt", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "igmp"] + [features] default = [] std = [] -defmt = ["dep:defmt", "smoltcp/defmt"] +defmt = ["dep:defmt", "smoltcp/defmt", "embassy-net-driver/defmt"] + +nightly = ["dep:embedded-io", "embedded-io?/async", "dep:embedded-nal-async"] +unstable-traits = [] udp = ["smoltcp/socket-udp"] tcp = ["smoltcp/socket-tcp"] -dns = ["smoltcp/socket-dns"] -dhcpv4 = ["medium-ethernet", "smoltcp/socket-dhcpv4"] +dns = ["smoltcp/socket-dns", "smoltcp/proto-dns"] +dhcpv4 = ["proto-ipv4", "medium-ethernet", "smoltcp/socket-dhcpv4"] +proto-ipv4 = ["smoltcp/proto-ipv4"] proto-ipv6 = ["smoltcp/proto-ipv6"] medium-ethernet = ["smoltcp/medium-ethernet"] medium-ip = ["smoltcp/medium-ip"] - -pool-4 = [] -pool-8 = [] -pool-16 = [] -pool-32 = [] -pool-64 = [] -pool-128 = [] -unstable-traits = [] +igmp = ["smoltcp/proto-igmp"] [dependencies] defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } -embassy-time = { version = "0.1.0", path = "../embassy-time" } -embassy-sync = { version = "0.1.0", path = "../embassy-sync" } -embedded-io = { version = "0.3.0", features = [ "async" ] } +smoltcp = { version = "0.10.0", default-features = false, features = [ + "socket", + "async", +] } + +embassy-net-driver = { version = "0.1.0", path = "../embassy-net-driver" } +embassy-time = { version = "0.1.2", path = "../embassy-time" } +embassy-sync = { version = "0.2.0", path = "../embassy-sync" } +embedded-io = { version = "0.4.0", optional = true } managed = { version = "0.8.0", default-features = false, features = [ "map" ] } heapless = { version = "0.7.5", default-features = false } @@ -48,16 +61,5 @@ generic-array = { version = "0.14.4", default-features = false } stable_deref_trait = { version = "1.2.0", default-features = false } futures = { version = "0.3.17", default-features = false, features = [ "async-await" ] } atomic-pool = "1.0" -atomic-polyfill = "1.0.1" -embedded-nal-async = "0.2.0" - -[dependencies.smoltcp] -version = "0.8.0" -git = "https://github.com/smoltcp-rs/smoltcp" -rev = "ed0cf16750a42f30e31fcaf5347915592924b1e3" -default-features = false -features = [ - "proto-ipv4", - "socket", - "async", -] +embedded-nal-async = { version = "0.4.0", optional = true } +atomic-polyfill = { version = "1.0" } diff --git a/embassy-net/README.md b/embassy-net/README.md index 9657e3589..48f9fd832 100644 --- a/embassy-net/README.md +++ b/embassy-net/README.md @@ -1,28 +1,56 @@ # embassy-net -embassy-net contains an async network API based on smoltcp and embassy, designed -for embedded systems. +`embassy-net` is a no-std no-alloc async network stack, designed for embedded systems. -## Running the example +It builds on [`smoltcp`](https://github.com/smoltcp-rs/smoltcp). It provides a higher-level and more opinionated +API. It glues together the components provided by `smoltcp`, handling the low-level details with defaults and +memory management designed to work well for embedded systems, aiiming for a more "Just Works" experience. -First, create the tap0 interface. You only need to do this once. +## Features -```sh -sudo ip tuntap add name tap0 mode tap user $USER -sudo ip link set tap0 up -sudo ip addr add 192.168.69.100/24 dev tap0 -sudo ip -6 addr add fe80::100/64 dev tap0 -sudo ip -6 addr add fdaa::100/64 dev tap0 -sudo ip -6 route add fe80::/64 dev tap0 -sudo ip -6 route add fdaa::/64 dev tap0 -``` +- IPv4, IPv6 +- Ethernet and bare-IP mediums. +- TCP, UDP, DNS, DHCPv4, IGMPv4 +- TCP sockets implement the `embedded-io` async traits. -Then run the example located in the `examples` folder: +See the [`smoltcp`](https://github.com/smoltcp-rs/smoltcp) README for a detailed list of implemented and +unimplemented features of the network protocols. -```sh -cd $EMBASSY_ROOT/examples/std/ -cargo run --bin net -``` +## Hardware support + +- [`esp-wifi`](https://github.com/esp-rs/esp-wifi) for WiFi support on bare-metal ESP32 chips. Maintained by Espressif. +- [`cyw43`](https://github.com/embassy-rs/embassy/tree/main/cyw43) for WiFi on CYW43xx chips, used in the Raspberry Pi Pico W +- [`embassy-usb`](https://github.com/embassy-rs/embassy/tree/main/embassy-usb) for Ethernet-over-USB (CDC NCM) support. +- [`embassy-stm32`](https://github.com/embassy-rs/embassy/tree/main/embassy-stm32) for the builtin Ethernet MAC in all STM32 chips (STM32F1, STM32F2, STM32F4, STM32F7, STM32H7, STM32H5). +- [`embassy-net-w5500`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-w5500) for Wiznet W5500 SPI Ethernet MAC+PHY chip. +- [`embassy-net-esp-hosted`](https://github.com/embassy-rs/embassy/tree/main/embassy-net-esp-hosted) for using ESP32 chips with the [`esp-hosted`](https://github.com/espressif/esp-hosted) firmware as WiFi adapters for another non-ESP32 MCU. + +## Examples + +- For usage with Embassy HALs and network chip drivers, search [here](https://github.com/embassy-rs/embassy/tree/main/examples) for `eth` or `wifi`. +- The [`esp-wifi` repo](https://github.com/esp-rs/esp-wifi) has examples for use on bare-metal ESP32 chips. +- For usage on `std` platforms, see [the `std` examples](https://github.com/embassy-rs/embassy/tree/main/examples/std/src/bin) + +## Adding support for new hardware + +To add `embassy-net` support for new hardware (i.e. a new Ethernet or WiFi chip, or +an Ethernet/WiFi MCU peripheral), you have to implement the [`embassy-net-driver`](https://crates.io/crates/embassy-net-driver) +traits. + +Alternatively, [`embassy-net-driver-channel`](https://crates.io/crates/embassy-net-driver-channel) provides a higer-level API +to construct a driver that processes packets in its own background task and communicates with the `embassy-net` task via +packet queues for RX and TX. + +Drivers should depend only on `embassy-net-driver` or `embassy-net-driver-channel`. Never on the main `embassy-net` crate. +This allows existing drivers to continue working for newer `embassy-net` major versions, without needing an update, if the driver +trait has not had breaking changes. + +## Interoperability + +This crate can run on any executor. + +[`embassy-time`](https://crates.io/crates/embassy-net-driver) is used for timekeeping and timeouts. You must +link an `embassy-time` driver in your project to use this crate. ## License diff --git a/embassy-net/src/device.rs b/embassy-net/src/device.rs index c183bd58a..4513c86d3 100644 --- a/embassy-net/src/device.rs +++ b/embassy-net/src/device.rs @@ -1,129 +1,106 @@ -use core::task::Waker; +use core::task::Context; -use smoltcp::phy::{Device as SmolDevice, DeviceCapabilities}; -use smoltcp::time::Instant as SmolInstant; +use embassy_net_driver::{Capabilities, Checksum, Driver, Medium, RxToken, TxToken}; +use smoltcp::phy; +use smoltcp::time::Instant; -use crate::packet_pool::PacketBoxExt; -use crate::{Packet, PacketBox, PacketBuf}; - -#[derive(PartialEq, Eq, Clone, Copy)] -pub enum LinkState { - Down, - Up, +pub(crate) struct DriverAdapter<'d, 'c, T> +where + T: Driver, +{ + // must be Some when actually using this to rx/tx + pub cx: Option<&'d mut Context<'c>>, + pub inner: &'d mut T, } -// 'static required due to the "fake GAT" in smoltcp::phy::Device. -// https://github.com/smoltcp-rs/smoltcp/pull/572 -pub trait Device { - fn is_transmit_ready(&mut self) -> bool; - fn transmit(&mut self, pkt: PacketBuf); - fn receive(&mut self) -> Option; +impl<'d, 'c, T> phy::Device for DriverAdapter<'d, 'c, T> +where + T: Driver, +{ + type RxToken<'a> = RxTokenAdapter> where Self: 'a; + type TxToken<'a> = TxTokenAdapter> where Self: 'a; - fn register_waker(&mut self, waker: &Waker); - fn capabilities(&self) -> DeviceCapabilities; - fn link_state(&mut self) -> LinkState; - fn ethernet_address(&self) -> [u8; 6]; -} - -impl Device for &'static mut T { - fn is_transmit_ready(&mut self) -> bool { - T::is_transmit_ready(self) - } - fn transmit(&mut self, pkt: PacketBuf) { - T::transmit(self, pkt) - } - fn receive(&mut self) -> Option { - T::receive(self) - } - fn register_waker(&mut self, waker: &Waker) { - T::register_waker(self, waker) - } - fn capabilities(&self) -> DeviceCapabilities { - T::capabilities(self) - } - fn link_state(&mut self) -> LinkState { - T::link_state(self) - } - fn ethernet_address(&self) -> [u8; 6] { - T::ethernet_address(self) - } -} - -pub struct DeviceAdapter { - pub device: D, - caps: DeviceCapabilities, -} - -impl DeviceAdapter { - pub(crate) fn new(device: D) -> Self { - Self { - caps: device.capabilities(), - device, - } - } -} - -impl<'a, D: Device + 'static> SmolDevice<'a> for DeviceAdapter { - type RxToken = RxToken; - type TxToken = TxToken<'a, D>; - - fn receive(&'a mut self) -> Option<(Self::RxToken, Self::TxToken)> { - let tx_pkt = PacketBox::new(Packet::new())?; - let rx_pkt = self.device.receive()?; - let rx_token = RxToken { pkt: rx_pkt }; - let tx_token = TxToken { - device: &mut self.device, - pkt: tx_pkt, - }; - - Some((rx_token, tx_token)) + fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + self.inner + .receive(self.cx.as_deref_mut().unwrap()) + .map(|(rx, tx)| (RxTokenAdapter(rx), TxTokenAdapter(tx))) } /// Construct a transmit token. - fn transmit(&'a mut self) -> Option { - if !self.device.is_transmit_ready() { - return None; - } - - let tx_pkt = PacketBox::new(Packet::new())?; - Some(TxToken { - device: &mut self.device, - pkt: tx_pkt, - }) + fn transmit(&mut self, _timestamp: Instant) -> Option> { + self.inner.transmit(self.cx.as_deref_mut().unwrap()).map(TxTokenAdapter) } /// Get a description of device capabilities. - fn capabilities(&self) -> DeviceCapabilities { - self.caps.clone() + fn capabilities(&self) -> phy::DeviceCapabilities { + fn convert(c: Checksum) -> phy::Checksum { + match c { + Checksum::Both => phy::Checksum::Both, + Checksum::Tx => phy::Checksum::Tx, + Checksum::Rx => phy::Checksum::Rx, + Checksum::None => phy::Checksum::None, + } + } + let caps: Capabilities = self.inner.capabilities(); + let mut smolcaps = phy::DeviceCapabilities::default(); + + smolcaps.max_transmission_unit = caps.max_transmission_unit; + smolcaps.max_burst_size = caps.max_burst_size; + smolcaps.medium = match caps.medium { + #[cfg(feature = "medium-ethernet")] + Medium::Ethernet => phy::Medium::Ethernet, + #[cfg(feature = "medium-ip")] + Medium::Ip => phy::Medium::Ip, + #[allow(unreachable_patterns)] + _ => panic!( + "Unsupported medium {:?}. Make sure to enable it in embassy-net's Cargo features.", + caps.medium + ), + }; + smolcaps.checksum.ipv4 = convert(caps.checksum.ipv4); + smolcaps.checksum.tcp = convert(caps.checksum.tcp); + smolcaps.checksum.udp = convert(caps.checksum.udp); + #[cfg(feature = "proto-ipv4")] + { + smolcaps.checksum.icmpv4 = convert(caps.checksum.icmpv4); + } + #[cfg(feature = "proto-ipv6")] + { + smolcaps.checksum.icmpv6 = convert(caps.checksum.icmpv6); + } + + smolcaps } } -pub struct RxToken { - pkt: PacketBuf, -} +pub(crate) struct RxTokenAdapter(T) +where + T: RxToken; -impl smoltcp::phy::RxToken for RxToken { - fn consume(mut self, _timestamp: SmolInstant, f: F) -> smoltcp::Result +impl phy::RxToken for RxTokenAdapter +where + T: RxToken, +{ + fn consume(self, f: F) -> R where - F: FnOnce(&mut [u8]) -> smoltcp::Result, + F: FnOnce(&mut [u8]) -> R, { - f(&mut self.pkt) + self.0.consume(|buf| f(buf)) } } -pub struct TxToken<'a, D: Device> { - device: &'a mut D, - pkt: PacketBox, -} +pub(crate) struct TxTokenAdapter(T) +where + T: TxToken; -impl<'a, D: Device> smoltcp::phy::TxToken for TxToken<'a, D> { - fn consume(self, _timestamp: SmolInstant, len: usize, f: F) -> smoltcp::Result +impl phy::TxToken for TxTokenAdapter +where + T: TxToken, +{ + fn consume(self, len: usize, f: F) -> R where - F: FnOnce(&mut [u8]) -> smoltcp::Result, + F: FnOnce(&mut [u8]) -> R, { - let mut buf = self.pkt.slice(0..len); - let r = f(&mut buf)?; - self.device.transmit(buf); - Ok(r) + self.0.consume(len, |buf| f(buf)) } } diff --git a/embassy-net/src/dns.rs b/embassy-net/src/dns.rs new file mode 100644 index 000000000..94f75f108 --- /dev/null +++ b/embassy-net/src/dns.rs @@ -0,0 +1,107 @@ +//! DNS client compatible with the `embedded-nal-async` traits. +//! +//! This exists only for compatibility with crates that use `embedded-nal-async`. +//! Prefer using [`Stack::dns_query`](crate::Stack::dns_query) directly if you're +//! not using `embedded-nal-async`. + +use heapless::Vec; +pub use smoltcp::socket::dns::{DnsQuery, Socket}; +pub(crate) use smoltcp::socket::dns::{GetQueryResultError, StartQueryError}; +pub use smoltcp::wire::{DnsQueryType, IpAddress}; + +use crate::{Driver, Stack}; + +/// Errors returned by DnsSocket. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Invalid name + InvalidName, + /// Name too long + NameTooLong, + /// Name lookup failed + Failed, +} + +impl From for Error { + fn from(_: GetQueryResultError) -> Self { + Self::Failed + } +} + +impl From for Error { + fn from(e: StartQueryError) -> Self { + match e { + StartQueryError::NoFreeSlot => Self::Failed, + StartQueryError::InvalidName => Self::InvalidName, + StartQueryError::NameTooLong => Self::NameTooLong, + } + } +} + +/// DNS client compatible with the `embedded-nal-async` traits. +/// +/// This exists only for compatibility with crates that use `embedded-nal-async`. +/// Prefer using [`Stack::dns_query`](crate::Stack::dns_query) directly if you're +/// not using `embedded-nal-async`. +pub struct DnsSocket<'a, D> +where + D: Driver + 'static, +{ + stack: &'a Stack, +} + +impl<'a, D> DnsSocket<'a, D> +where + D: Driver + 'static, +{ + /// Create a new DNS socket using the provided stack. + /// + /// NOTE: If using DHCP, make sure it has reconfigured the stack to ensure the DNS servers are updated. + pub fn new(stack: &'a Stack) -> Self { + Self { stack } + } + + /// Make a query for a given name and return the corresponding IP addresses. + pub async fn query(&self, name: &str, qtype: DnsQueryType) -> Result, Error> { + self.stack.dns_query(name, qtype).await + } +} + +#[cfg(all(feature = "unstable-traits", feature = "nightly"))] +impl<'a, D> embedded_nal_async::Dns for DnsSocket<'a, D> +where + D: Driver + 'static, +{ + type Error = Error; + + async fn get_host_by_name( + &self, + host: &str, + addr_type: embedded_nal_async::AddrType, + ) -> Result { + use embedded_nal_async::{AddrType, IpAddr}; + let qtype = match addr_type { + AddrType::IPv6 => DnsQueryType::Aaaa, + _ => DnsQueryType::A, + }; + let addrs = self.query(host, qtype).await?; + if let Some(first) = addrs.get(0) { + Ok(match first { + #[cfg(feature = "proto-ipv4")] + IpAddress::Ipv4(addr) => IpAddr::V4(addr.0.into()), + #[cfg(feature = "proto-ipv6")] + IpAddress::Ipv6(addr) => IpAddr::V6(addr.0.into()), + }) + } else { + Err(Error::Failed) + } + } + + async fn get_host_by_address( + &self, + _addr: embedded_nal_async::IpAddr, + ) -> Result, Self::Error> { + todo!() + } +} diff --git a/embassy-net/src/lib.rs b/embassy-net/src/lib.rs index 83d364715..0d0a986f6 100644 --- a/embassy-net/src/lib.rs +++ b/embassy-net/src/lib.rs @@ -1,31 +1,726 @@ #![cfg_attr(not(feature = "std"), no_std)] -#![allow(clippy::new_without_default)] -#![feature(generic_associated_types, type_alias_impl_trait)] +#![cfg_attr(feature = "nightly", feature(async_fn_in_trait, impl_trait_projections))] +#![warn(missing_docs)] +#![doc = include_str!("../README.md")] // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; mod device; -mod packet_pool; -mod stack; - -pub use device::{Device, LinkState}; -pub use packet_pool::{Packet, PacketBox, PacketBoxExt, PacketBuf, MTU}; -pub use stack::{Config, ConfigStrategy, Stack, StackResources}; - +#[cfg(feature = "dns")] +pub mod dns; #[cfg(feature = "tcp")] pub mod tcp; - +mod time; #[cfg(feature = "udp")] pub mod udp; -// smoltcp reexports -pub use smoltcp::phy::{DeviceCapabilities, Medium}; -pub use smoltcp::time::{Duration as SmolDuration, Instant as SmolInstant}; +use core::cell::RefCell; +use core::future::{poll_fn, Future}; +use core::task::{Context, Poll}; + +pub use embassy_net_driver as driver; +use embassy_net_driver::{Driver, LinkState, Medium}; +use embassy_sync::waitqueue::WakerRegistration; +use embassy_time::{Instant, Timer}; +use futures::pin_mut; +use heapless::Vec; +#[cfg(feature = "igmp")] +pub use smoltcp::iface::MulticastError; +use smoltcp::iface::{Interface, SocketHandle, SocketSet, SocketStorage}; +#[cfg(feature = "dhcpv4")] +use smoltcp::socket::dhcpv4::{self, RetryConfig}; +#[cfg(feature = "udp")] +pub use smoltcp::wire::IpListenEndpoint; #[cfg(feature = "medium-ethernet")] pub use smoltcp::wire::{EthernetAddress, HardwareAddress}; -pub use smoltcp::wire::{IpAddress, IpCidr, Ipv4Address, Ipv4Cidr}; +pub use smoltcp::wire::{IpAddress, IpCidr, IpEndpoint}; +#[cfg(feature = "proto-ipv4")] +pub use smoltcp::wire::{Ipv4Address, Ipv4Cidr}; #[cfg(feature = "proto-ipv6")] pub use smoltcp::wire::{Ipv6Address, Ipv6Cidr}; -#[cfg(feature = "udp")] -pub use smoltcp::{socket::udp::PacketMetadata, wire::IpListenEndpoint}; + +use crate::device::DriverAdapter; +use crate::time::{instant_from_smoltcp, instant_to_smoltcp}; + +const LOCAL_PORT_MIN: u16 = 1025; +const LOCAL_PORT_MAX: u16 = 65535; +#[cfg(feature = "dns")] +const MAX_QUERIES: usize = 4; + +/// Memory resources needed for a network stack. +pub struct StackResources { + sockets: [SocketStorage<'static>; SOCK], + #[cfg(feature = "dns")] + queries: [Option; MAX_QUERIES], +} + +impl StackResources { + /// Create a new set of stack resources. + pub const fn new() -> Self { + #[cfg(feature = "dns")] + const INIT: Option = None; + Self { + sockets: [SocketStorage::EMPTY; SOCK], + #[cfg(feature = "dns")] + queries: [INIT; MAX_QUERIES], + } + } +} + +/// Static IP address configuration. +#[cfg(feature = "proto-ipv4")] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StaticConfigV4 { + /// IP address and subnet mask. + pub address: Ipv4Cidr, + /// Default gateway. + pub gateway: Option, + /// DNS servers. + pub dns_servers: Vec, +} + +/// Static IPv6 address configuration +#[cfg(feature = "proto-ipv6")] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StaticConfigV6 { + /// IP address and subnet mask. + pub address: Ipv6Cidr, + /// Default gateway. + pub gateway: Option, + /// DNS servers. + pub dns_servers: Vec, +} + +/// DHCP configuration. +#[cfg(feature = "dhcpv4")] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DhcpConfig { + /// Maximum lease duration. + /// + /// If not set, the lease duration specified by the server will be used. + /// If set, the lease duration will be capped at this value. + pub max_lease_duration: Option, + /// Retry configuration. + pub retry_config: RetryConfig, + /// Ignore NAKs from DHCP servers. + /// + /// This is not compliant with the DHCP RFCs, since theoretically we must stop using the assigned IP when receiving a NAK. This can increase reliability on broken networks with buggy routers or rogue DHCP servers, however. + pub ignore_naks: bool, + /// Server port. This is almost always 67. Do not change unless you know what you're doing. + pub server_port: u16, + /// Client port. This is almost always 68. Do not change unless you know what you're doing. + pub client_port: u16, +} + +#[cfg(feature = "dhcpv4")] +impl Default for DhcpConfig { + fn default() -> Self { + Self { + max_lease_duration: Default::default(), + retry_config: Default::default(), + ignore_naks: Default::default(), + server_port: smoltcp::wire::DHCP_SERVER_PORT, + client_port: smoltcp::wire::DHCP_CLIENT_PORT, + } + } +} + +/// Network stack configuration. +pub struct Config { + /// IPv4 configuration + #[cfg(feature = "proto-ipv4")] + pub ipv4: ConfigV4, + /// IPv6 configuration + #[cfg(feature = "proto-ipv6")] + pub ipv6: ConfigV6, +} + +impl Config { + /// IPv4 configuration with static addressing. + #[cfg(feature = "proto-ipv4")] + pub fn ipv4_static(config: StaticConfigV4) -> Self { + Self { + ipv4: ConfigV4::Static(config), + #[cfg(feature = "proto-ipv6")] + ipv6: ConfigV6::None, + } + } + + /// IPv6 configuration with static addressing. + #[cfg(feature = "proto-ipv6")] + pub fn ipv6_static(config: StaticConfigV6) -> Self { + Self { + #[cfg(feature = "proto-ipv4")] + ipv4: ConfigV4::None, + ipv6: ConfigV6::Static(config), + } + } + + /// IPv6 configuration with dynamic addressing. + /// + /// # Example + /// ```rust + /// let _cfg = Config::dhcpv4(Default::default()); + /// ``` + #[cfg(feature = "dhcpv4")] + pub fn dhcpv4(config: DhcpConfig) -> Self { + Self { + ipv4: ConfigV4::Dhcp(config), + #[cfg(feature = "proto-ipv6")] + ipv6: ConfigV6::None, + } + } +} + +/// Network stack IPv4 configuration. +#[cfg(feature = "proto-ipv4")] +pub enum ConfigV4 { + /// Use a static IPv4 address configuration. + Static(StaticConfigV4), + /// Use DHCP to obtain an IP address configuration. + #[cfg(feature = "dhcpv4")] + Dhcp(DhcpConfig), + /// Do not configure IPv6. + None, +} + +/// Network stack IPv6 configuration. +#[cfg(feature = "proto-ipv6")] +pub enum ConfigV6 { + /// Use a static IPv6 address configuration. + Static(StaticConfigV6), + /// Do not configure IPv6. + None, +} + +/// A network stack. +/// +/// This is the main entry point for the network stack. +pub struct Stack { + pub(crate) socket: RefCell, + inner: RefCell>, +} + +struct Inner { + device: D, + link_up: bool, + #[cfg(feature = "proto-ipv4")] + static_v4: Option, + #[cfg(feature = "proto-ipv6")] + static_v6: Option, + #[cfg(feature = "dhcpv4")] + dhcp_socket: Option, + #[cfg(feature = "dns")] + dns_socket: SocketHandle, + #[cfg(feature = "dns")] + dns_waker: WakerRegistration, +} + +pub(crate) struct SocketStack { + pub(crate) sockets: SocketSet<'static>, + pub(crate) iface: Interface, + pub(crate) waker: WakerRegistration, + next_local_port: u16, +} + +impl Stack { + /// Create a new network stack. + pub fn new( + mut device: D, + config: Config, + resources: &'static mut StackResources, + random_seed: u64, + ) -> Self { + #[cfg(feature = "medium-ethernet")] + let medium = device.capabilities().medium; + + let hardware_addr = match medium { + #[cfg(feature = "medium-ethernet")] + Medium::Ethernet => HardwareAddress::Ethernet(EthernetAddress(device.ethernet_address())), + #[cfg(feature = "medium-ip")] + Medium::Ip => HardwareAddress::Ip, + #[allow(unreachable_patterns)] + _ => panic!( + "Unsupported medium {:?}. Make sure to enable it in embassy-net's Cargo features.", + medium + ), + }; + let mut iface_cfg = smoltcp::iface::Config::new(hardware_addr); + iface_cfg.random_seed = random_seed; + + let iface = Interface::new( + iface_cfg, + &mut DriverAdapter { + inner: &mut device, + cx: None, + }, + instant_to_smoltcp(Instant::now()), + ); + + let sockets = SocketSet::new(&mut resources.sockets[..]); + + let next_local_port = (random_seed % (LOCAL_PORT_MAX - LOCAL_PORT_MIN) as u64) as u16 + LOCAL_PORT_MIN; + + let mut socket = SocketStack { + sockets, + iface, + waker: WakerRegistration::new(), + next_local_port, + }; + + let mut inner = Inner { + device, + link_up: false, + #[cfg(feature = "proto-ipv4")] + static_v4: None, + #[cfg(feature = "proto-ipv6")] + static_v6: None, + #[cfg(feature = "dhcpv4")] + dhcp_socket: None, + #[cfg(feature = "dns")] + dns_socket: socket.sockets.add(dns::Socket::new( + &[], + managed::ManagedSlice::Borrowed(&mut resources.queries), + )), + #[cfg(feature = "dns")] + dns_waker: WakerRegistration::new(), + }; + + #[cfg(feature = "proto-ipv4")] + match config.ipv4 { + ConfigV4::Static(config) => { + inner.apply_config_v4(&mut socket, config); + } + #[cfg(feature = "dhcpv4")] + ConfigV4::Dhcp(config) => { + let mut dhcp_socket = smoltcp::socket::dhcpv4::Socket::new(); + inner.apply_dhcp_config(&mut dhcp_socket, config); + let handle = socket.sockets.add(dhcp_socket); + inner.dhcp_socket = Some(handle); + } + ConfigV4::None => {} + } + #[cfg(feature = "proto-ipv6")] + match config.ipv6 { + ConfigV6::Static(config) => { + inner.apply_config_v6(&mut socket, config); + } + ConfigV6::None => {} + } + + Self { + socket: RefCell::new(socket), + inner: RefCell::new(inner), + } + } + + fn with(&self, f: impl FnOnce(&SocketStack, &Inner) -> R) -> R { + f(&*self.socket.borrow(), &*self.inner.borrow()) + } + + fn with_mut(&self, f: impl FnOnce(&mut SocketStack, &mut Inner) -> R) -> R { + f(&mut *self.socket.borrow_mut(), &mut *self.inner.borrow_mut()) + } + + /// Get the MAC address of the network interface. + pub fn ethernet_address(&self) -> [u8; 6] { + self.with(|_s, i| i.device.ethernet_address()) + } + + /// Get whether the link is up. + pub fn is_link_up(&self) -> bool { + self.with(|_s, i| i.link_up) + } + + /// Get whether the network stack has a valid IP configuration. + /// This is true if the network stack has a static IP configuration or if DHCP has completed + pub fn is_config_up(&self) -> bool { + let v4_up; + let v6_up; + + #[cfg(feature = "proto-ipv4")] + { + v4_up = self.config_v4().is_some(); + } + #[cfg(not(feature = "proto-ipv4"))] + { + v4_up = false; + } + + #[cfg(feature = "proto-ipv6")] + { + v6_up = self.config_v6().is_some(); + } + #[cfg(not(feature = "proto-ipv6"))] + { + v6_up = false; + } + + v4_up || v6_up + } + + /// Get the current IPv4 configuration. + #[cfg(feature = "proto-ipv4")] + pub fn config_v4(&self) -> Option { + self.with(|_s, i| i.static_v4.clone()) + } + + /// Get the current IPv6 configuration. + #[cfg(feature = "proto-ipv6")] + pub fn config_v6(&self) -> Option { + self.with(|_s, i| i.static_v6.clone()) + } + + /// Run the network stack. + /// + /// You must call this in a background task, to process network events. + pub async fn run(&self) -> ! { + poll_fn(|cx| { + self.with_mut(|s, i| i.poll(cx, s)); + Poll::<()>::Pending + }) + .await; + unreachable!() + } + + /// Make a query for a given name and return the corresponding IP addresses. + #[cfg(feature = "dns")] + pub async fn dns_query(&self, name: &str, qtype: dns::DnsQueryType) -> Result, dns::Error> { + // For A and AAAA queries we try detect whether `name` is just an IP address + match qtype { + #[cfg(feature = "proto-ipv4")] + dns::DnsQueryType::A => { + if let Ok(ip) = name.parse().map(IpAddress::Ipv4) { + return Ok([ip].into_iter().collect()); + } + } + #[cfg(feature = "proto-ipv6")] + dns::DnsQueryType::Aaaa => { + if let Ok(ip) = name.parse().map(IpAddress::Ipv6) { + return Ok([ip].into_iter().collect()); + } + } + _ => {} + } + + let query = poll_fn(|cx| { + self.with_mut(|s, i| { + let socket = s.sockets.get_mut::(i.dns_socket); + match socket.start_query(s.iface.context(), name, qtype) { + Ok(handle) => Poll::Ready(Ok(handle)), + Err(dns::StartQueryError::NoFreeSlot) => { + i.dns_waker.register(cx.waker()); + Poll::Pending + } + Err(e) => Poll::Ready(Err(e)), + } + }) + }) + .await?; + + #[must_use = "to delay the drop handler invocation to the end of the scope"] + struct OnDrop { + f: core::mem::MaybeUninit, + } + + impl OnDrop { + fn new(f: F) -> Self { + Self { + f: core::mem::MaybeUninit::new(f), + } + } + + fn defuse(self) { + core::mem::forget(self) + } + } + + impl Drop for OnDrop { + fn drop(&mut self) { + unsafe { self.f.as_ptr().read()() } + } + } + + let drop = OnDrop::new(|| { + self.with_mut(|s, i| { + let socket = s.sockets.get_mut::(i.dns_socket); + socket.cancel_query(query); + s.waker.wake(); + i.dns_waker.wake(); + }) + }); + + let res = poll_fn(|cx| { + self.with_mut(|s, i| { + let socket = s.sockets.get_mut::(i.dns_socket); + match socket.get_query_result(query) { + Ok(addrs) => { + i.dns_waker.wake(); + Poll::Ready(Ok(addrs)) + } + Err(dns::GetQueryResultError::Pending) => { + socket.register_query_waker(query, cx.waker()); + Poll::Pending + } + Err(e) => { + i.dns_waker.wake(); + Poll::Ready(Err(e.into())) + } + } + }) + }) + .await; + + drop.defuse(); + + res + } +} + +#[cfg(feature = "igmp")] +impl Stack { + /// Join a multicast group. + pub fn join_multicast_group(&self, addr: T) -> Result + where + T: Into, + { + let addr = addr.into(); + + self.with_mut(|s, i| { + s.iface + .join_multicast_group(&mut i.device, addr, instant_to_smoltcp(Instant::now())) + }) + } + + /// Leave a multicast group. + pub fn leave_multicast_group(&self, addr: T) -> Result + where + T: Into, + { + let addr = addr.into(); + + self.with_mut(|s, i| { + s.iface + .leave_multicast_group(&mut i.device, addr, instant_to_smoltcp(Instant::now())) + }) + } + + /// Get whether the network stack has joined the given multicast group. + pub fn has_multicast_group>(&self, addr: T) -> bool { + self.socket.borrow().iface.has_multicast_group(addr) + } +} + +impl SocketStack { + #[allow(clippy::absurd_extreme_comparisons, dead_code)] + pub fn get_local_port(&mut self) -> u16 { + let res = self.next_local_port; + self.next_local_port = if res >= LOCAL_PORT_MAX { LOCAL_PORT_MIN } else { res + 1 }; + res + } +} + +impl Inner { + #[cfg(feature = "proto-ipv4")] + fn apply_config_v4(&mut self, s: &mut SocketStack, config: StaticConfigV4) { + #[cfg(feature = "medium-ethernet")] + let medium = self.device.capabilities().medium; + + debug!("Acquired IP configuration:"); + + debug!(" IP address: {}", config.address); + s.iface.update_ip_addrs(|addrs| { + if addrs.is_empty() { + addrs.push(IpCidr::Ipv4(config.address)).unwrap(); + } else { + addrs[0] = IpCidr::Ipv4(config.address); + } + }); + + #[cfg(feature = "medium-ethernet")] + if medium == Medium::Ethernet { + if let Some(gateway) = config.gateway { + debug!(" Default gateway: {}", gateway); + s.iface.routes_mut().add_default_ipv4_route(gateway).unwrap(); + } else { + debug!(" Default gateway: None"); + s.iface.routes_mut().remove_default_ipv4_route(); + } + } + for (i, s) in config.dns_servers.iter().enumerate() { + debug!(" DNS server {}: {}", i, s); + } + + self.static_v4 = Some(config); + + #[cfg(feature = "dns")] + { + self.update_dns_servers(s) + } + } + + /// Replaces the current IPv6 static configuration with a newly supplied config. + #[cfg(feature = "proto-ipv6")] + fn apply_config_v6(&mut self, s: &mut SocketStack, config: StaticConfigV6) { + #[cfg(feature = "medium-ethernet")] + let medium = self.device.capabilities().medium; + + debug!("Acquired IPv6 configuration:"); + + debug!(" IP address: {}", config.address); + s.iface.update_ip_addrs(|addrs| { + if addrs.is_empty() { + addrs.push(IpCidr::Ipv6(config.address)).unwrap(); + } else { + addrs[0] = IpCidr::Ipv6(config.address); + } + }); + + #[cfg(feature = "medium-ethernet")] + if Medium::Ethernet == medium { + if let Some(gateway) = config.gateway { + debug!(" Default gateway: {}", gateway); + s.iface.routes_mut().add_default_ipv6_route(gateway).unwrap(); + } else { + debug!(" Default gateway: None"); + s.iface.routes_mut().remove_default_ipv6_route(); + } + } + for (i, s) in config.dns_servers.iter().enumerate() { + debug!(" DNS server {}: {}", i, s); + } + + self.static_v6 = Some(config); + + #[cfg(feature = "dns")] + { + self.update_dns_servers(s) + } + } + + #[cfg(feature = "dns")] + fn update_dns_servers(&mut self, s: &mut SocketStack) { + let socket = s.sockets.get_mut::(self.dns_socket); + + let servers_v4; + #[cfg(feature = "proto-ipv4")] + { + servers_v4 = self + .static_v4 + .iter() + .flat_map(|cfg| cfg.dns_servers.iter().map(|c| IpAddress::Ipv4(*c))); + }; + #[cfg(not(feature = "proto-ipv4"))] + { + servers_v4 = core::iter::empty(); + } + + let servers_v6; + #[cfg(feature = "proto-ipv6")] + { + servers_v6 = self + .static_v6 + .iter() + .flat_map(|cfg| cfg.dns_servers.iter().map(|c| IpAddress::Ipv6(*c))); + } + #[cfg(not(feature = "proto-ipv6"))] + { + servers_v6 = core::iter::empty(); + } + + // Prefer the v6 DNS servers over the v4 servers + let servers: Vec = servers_v6.chain(servers_v4).collect(); + socket.update_servers(&servers[..]); + } + + #[cfg(feature = "dhcpv4")] + fn apply_dhcp_config(&self, socket: &mut smoltcp::socket::dhcpv4::Socket, config: DhcpConfig) { + socket.set_ignore_naks(config.ignore_naks); + socket.set_max_lease_duration(config.max_lease_duration.map(crate::time::duration_to_smoltcp)); + socket.set_ports(config.server_port, config.client_port); + socket.set_retry_config(config.retry_config); + } + + #[allow(unused)] // used only with dhcp + fn unapply_config(&mut self, s: &mut SocketStack) { + #[cfg(feature = "medium-ethernet")] + let medium = self.device.capabilities().medium; + + debug!("Lost IP configuration"); + s.iface.update_ip_addrs(|ip_addrs| ip_addrs.clear()); + #[cfg(feature = "medium-ethernet")] + if medium == Medium::Ethernet { + #[cfg(feature = "proto-ipv4")] + { + s.iface.routes_mut().remove_default_ipv4_route(); + } + } + #[cfg(feature = "proto-ipv4")] + { + self.static_v4 = None + } + } + + fn poll(&mut self, cx: &mut Context<'_>, s: &mut SocketStack) { + s.waker.register(cx.waker()); + + #[cfg(feature = "medium-ethernet")] + if self.device.capabilities().medium == Medium::Ethernet { + s.iface.set_hardware_addr(HardwareAddress::Ethernet(EthernetAddress( + self.device.ethernet_address(), + ))); + } + + let timestamp = instant_to_smoltcp(Instant::now()); + let mut smoldev = DriverAdapter { + cx: Some(cx), + inner: &mut self.device, + }; + s.iface.poll(timestamp, &mut smoldev, &mut s.sockets); + + // Update link up + let old_link_up = self.link_up; + self.link_up = self.device.link_state(cx) == LinkState::Up; + + // Print when changed + if old_link_up != self.link_up { + info!("link_up = {:?}", self.link_up); + } + + #[cfg(feature = "dhcpv4")] + if let Some(dhcp_handle) = self.dhcp_socket { + let socket = s.sockets.get_mut::(dhcp_handle); + + if self.link_up { + match socket.poll() { + None => {} + Some(dhcpv4::Event::Deconfigured) => self.unapply_config(s), + Some(dhcpv4::Event::Configured(config)) => { + let config = StaticConfigV4 { + address: config.address, + gateway: config.router, + dns_servers: config.dns_servers, + }; + self.apply_config_v4(s, config) + } + } + } else if old_link_up { + socket.reset(); + self.unapply_config(s); + } + } + //if old_link_up || self.link_up { + // self.poll_configurator(timestamp) + //} + // + + if let Some(poll_at) = s.iface.poll_at(timestamp, &mut s.sockets) { + let t = Timer::at(instant_from_smoltcp(poll_at)); + pin_mut!(t); + if t.poll(cx).is_ready() { + cx.waker().wake_by_ref(); + } + } + } +} diff --git a/embassy-net/src/packet_pool.rs b/embassy-net/src/packet_pool.rs deleted file mode 100644 index cb8a1316c..000000000 --- a/embassy-net/src/packet_pool.rs +++ /dev/null @@ -1,107 +0,0 @@ -use core::ops::{Deref, DerefMut, Range}; - -use as_slice::{AsMutSlice, AsSlice}; -use atomic_pool::{pool, Box}; - -pub const MTU: usize = 1516; - -#[cfg(feature = "pool-4")] -pub const PACKET_POOL_SIZE: usize = 4; - -#[cfg(feature = "pool-8")] -pub const PACKET_POOL_SIZE: usize = 8; - -#[cfg(feature = "pool-16")] -pub const PACKET_POOL_SIZE: usize = 16; - -#[cfg(feature = "pool-32")] -pub const PACKET_POOL_SIZE: usize = 32; - -#[cfg(feature = "pool-64")] -pub const PACKET_POOL_SIZE: usize = 64; - -#[cfg(feature = "pool-128")] -pub const PACKET_POOL_SIZE: usize = 128; - -pool!(pub PacketPool: [Packet; PACKET_POOL_SIZE]); -pub type PacketBox = Box; - -#[repr(align(4))] -pub struct Packet(pub [u8; MTU]); - -impl Packet { - pub const fn new() -> Self { - Self([0; MTU]) - } -} - -pub trait PacketBoxExt { - fn slice(self, range: Range) -> PacketBuf; -} - -impl PacketBoxExt for PacketBox { - fn slice(self, range: Range) -> PacketBuf { - PacketBuf { packet: self, range } - } -} - -impl AsSlice for Packet { - type Element = u8; - - fn as_slice(&self) -> &[Self::Element] { - &self.deref()[..] - } -} - -impl AsMutSlice for Packet { - fn as_mut_slice(&mut self) -> &mut [Self::Element] { - &mut self.deref_mut()[..] - } -} - -impl Deref for Packet { - type Target = [u8; MTU]; - - fn deref(&self) -> &[u8; MTU] { - &self.0 - } -} - -impl DerefMut for Packet { - fn deref_mut(&mut self) -> &mut [u8; MTU] { - &mut self.0 - } -} - -pub struct PacketBuf { - packet: PacketBox, - range: Range, -} - -impl AsSlice for PacketBuf { - type Element = u8; - - fn as_slice(&self) -> &[Self::Element] { - &self.packet[self.range.clone()] - } -} - -impl AsMutSlice for PacketBuf { - fn as_mut_slice(&mut self) -> &mut [Self::Element] { - &mut self.packet[self.range.clone()] - } -} - -impl Deref for PacketBuf { - type Target = [u8]; - - fn deref(&self) -> &[u8] { - &self.packet[self.range.clone()] - } -} - -impl DerefMut for PacketBuf { - fn deref_mut(&mut self) -> &mut [u8] { - &mut self.packet[self.range.clone()] - } -} diff --git a/embassy-net/src/stack.rs b/embassy-net/src/stack.rs deleted file mode 100644 index 8d2dd4bca..000000000 --- a/embassy-net/src/stack.rs +++ /dev/null @@ -1,316 +0,0 @@ -use core::cell::UnsafeCell; -use core::future::Future; -use core::task::{Context, Poll}; - -use embassy_sync::waitqueue::WakerRegistration; -use embassy_time::{Instant, Timer}; -use futures::future::poll_fn; -use futures::pin_mut; -use heapless::Vec; -#[cfg(feature = "dhcpv4")] -use smoltcp::iface::SocketHandle; -use smoltcp::iface::{Interface, InterfaceBuilder, SocketSet, SocketStorage}; -#[cfg(feature = "medium-ethernet")] -use smoltcp::iface::{Neighbor, NeighborCache, Route, Routes}; -#[cfg(feature = "medium-ethernet")] -use smoltcp::phy::{Device as _, Medium}; -#[cfg(feature = "dhcpv4")] -use smoltcp::socket::dhcpv4; -use smoltcp::time::Instant as SmolInstant; -#[cfg(feature = "medium-ethernet")] -use smoltcp::wire::{EthernetAddress, HardwareAddress, IpAddress}; -use smoltcp::wire::{IpCidr, Ipv4Address, Ipv4Cidr}; - -use crate::device::{Device, DeviceAdapter, LinkState}; - -const LOCAL_PORT_MIN: u16 = 1025; -const LOCAL_PORT_MAX: u16 = 65535; - -pub struct StackResources { - addresses: [IpCidr; ADDR], - sockets: [SocketStorage<'static>; SOCK], - - #[cfg(feature = "medium-ethernet")] - routes: [Option<(IpCidr, Route)>; 1], - #[cfg(feature = "medium-ethernet")] - neighbor_cache: [Option<(IpAddress, Neighbor)>; NEIGHBOR], -} - -impl StackResources { - pub fn new() -> Self { - Self { - addresses: [IpCidr::new(Ipv4Address::UNSPECIFIED.into(), 32); ADDR], - sockets: [SocketStorage::EMPTY; SOCK], - #[cfg(feature = "medium-ethernet")] - routes: [None; 1], - #[cfg(feature = "medium-ethernet")] - neighbor_cache: [None; NEIGHBOR], - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Config { - pub address: Ipv4Cidr, - pub gateway: Option, - pub dns_servers: Vec, -} - -pub enum ConfigStrategy { - Static(Config), - #[cfg(feature = "dhcpv4")] - Dhcp, -} - -pub struct Stack { - pub(crate) socket: UnsafeCell, - inner: UnsafeCell>, -} - -struct Inner { - device: DeviceAdapter, - link_up: bool, - config: Option, - #[cfg(feature = "dhcpv4")] - dhcp_socket: Option, -} - -pub(crate) struct SocketStack { - pub(crate) sockets: SocketSet<'static>, - pub(crate) iface: Interface<'static>, - pub(crate) waker: WakerRegistration, - next_local_port: u16, -} - -unsafe impl Send for Stack {} - -impl Stack { - pub fn new( - device: D, - config: ConfigStrategy, - resources: &'static mut StackResources, - random_seed: u64, - ) -> Self { - #[cfg(feature = "medium-ethernet")] - let medium = device.capabilities().medium; - - #[cfg(feature = "medium-ethernet")] - let ethernet_addr = if medium == Medium::Ethernet { - device.ethernet_address() - } else { - [0, 0, 0, 0, 0, 0] - }; - - let mut device = DeviceAdapter::new(device); - - let mut b = InterfaceBuilder::new(); - b = b.ip_addrs(&mut resources.addresses[..]); - b = b.random_seed(random_seed); - - #[cfg(feature = "medium-ethernet")] - if medium == Medium::Ethernet { - b = b.hardware_addr(HardwareAddress::Ethernet(EthernetAddress(ethernet_addr))); - b = b.neighbor_cache(NeighborCache::new(&mut resources.neighbor_cache[..])); - b = b.routes(Routes::new(&mut resources.routes[..])); - } - - let iface = b.finalize(&mut device); - - let sockets = SocketSet::new(&mut resources.sockets[..]); - - let next_local_port = (random_seed % (LOCAL_PORT_MAX - LOCAL_PORT_MIN) as u64) as u16 + LOCAL_PORT_MIN; - - let mut inner = Inner { - device, - link_up: false, - config: None, - #[cfg(feature = "dhcpv4")] - dhcp_socket: None, - }; - let mut socket = SocketStack { - sockets, - iface, - waker: WakerRegistration::new(), - next_local_port, - }; - - match config { - ConfigStrategy::Static(config) => inner.apply_config(&mut socket, config), - #[cfg(feature = "dhcpv4")] - ConfigStrategy::Dhcp => { - let handle = socket.sockets.add(smoltcp::socket::dhcpv4::Socket::new()); - inner.dhcp_socket = Some(handle); - } - } - - Self { - socket: UnsafeCell::new(socket), - inner: UnsafeCell::new(inner), - } - } - - /// SAFETY: must not call reentrantly. - unsafe fn with(&self, f: impl FnOnce(&SocketStack, &Inner) -> R) -> R { - f(&*self.socket.get(), &*self.inner.get()) - } - - /// SAFETY: must not call reentrantly. - unsafe fn with_mut(&self, f: impl FnOnce(&mut SocketStack, &mut Inner) -> R) -> R { - f(&mut *self.socket.get(), &mut *self.inner.get()) - } - - pub fn ethernet_address(&self) -> [u8; 6] { - unsafe { self.with(|_s, i| i.device.device.ethernet_address()) } - } - - pub fn is_link_up(&self) -> bool { - unsafe { self.with(|_s, i| i.link_up) } - } - - pub fn is_config_up(&self) -> bool { - unsafe { self.with(|_s, i| i.config.is_some()) } - } - - pub fn config(&self) -> Option { - unsafe { self.with(|_s, i| i.config.clone()) } - } - - pub async fn run(&self) -> ! { - poll_fn(|cx| { - unsafe { self.with_mut(|s, i| i.poll(cx, s)) } - Poll::<()>::Pending - }) - .await; - unreachable!() - } -} - -impl SocketStack { - #[allow(clippy::absurd_extreme_comparisons)] - pub fn get_local_port(&mut self) -> u16 { - let res = self.next_local_port; - self.next_local_port = if res >= LOCAL_PORT_MAX { LOCAL_PORT_MIN } else { res + 1 }; - res - } -} - -impl Inner { - fn apply_config(&mut self, s: &mut SocketStack, config: Config) { - #[cfg(feature = "medium-ethernet")] - let medium = self.device.capabilities().medium; - - debug!("Acquired IP configuration:"); - - debug!(" IP address: {}", config.address); - self.set_ipv4_addr(s, config.address); - - #[cfg(feature = "medium-ethernet")] - if medium == Medium::Ethernet { - if let Some(gateway) = config.gateway { - debug!(" Default gateway: {}", gateway); - s.iface.routes_mut().add_default_ipv4_route(gateway).unwrap(); - } else { - debug!(" Default gateway: None"); - s.iface.routes_mut().remove_default_ipv4_route(); - } - } - for (i, s) in config.dns_servers.iter().enumerate() { - debug!(" DNS server {}: {}", i, s); - } - - self.config = Some(config) - } - - #[allow(unused)] // used only with dhcp - fn unapply_config(&mut self, s: &mut SocketStack) { - #[cfg(feature = "medium-ethernet")] - let medium = self.device.capabilities().medium; - - debug!("Lost IP configuration"); - self.set_ipv4_addr(s, Ipv4Cidr::new(Ipv4Address::UNSPECIFIED, 0)); - #[cfg(feature = "medium-ethernet")] - if medium == Medium::Ethernet { - s.iface.routes_mut().remove_default_ipv4_route(); - } - self.config = None - } - - fn set_ipv4_addr(&mut self, s: &mut SocketStack, cidr: Ipv4Cidr) { - s.iface.update_ip_addrs(|addrs| { - let dest = addrs.iter_mut().next().unwrap(); - *dest = IpCidr::Ipv4(cidr); - }); - } - - fn poll(&mut self, cx: &mut Context<'_>, s: &mut SocketStack) { - self.device.device.register_waker(cx.waker()); - s.waker.register(cx.waker()); - - let timestamp = instant_to_smoltcp(Instant::now()); - if s.iface.poll(timestamp, &mut self.device, &mut s.sockets).is_err() { - // If poll() returns error, it may not be done yet, so poll again later. - cx.waker().wake_by_ref(); - return; - } - - // Update link up - let old_link_up = self.link_up; - self.link_up = self.device.device.link_state() == LinkState::Up; - - // Print when changed - if old_link_up != self.link_up { - info!("link_up = {:?}", self.link_up); - } - - #[cfg(feature = "dhcpv4")] - if let Some(dhcp_handle) = self.dhcp_socket { - let socket = s.sockets.get_mut::(dhcp_handle); - - if self.link_up { - match socket.poll() { - None => {} - Some(dhcpv4::Event::Deconfigured) => self.unapply_config(s), - Some(dhcpv4::Event::Configured(config)) => { - let mut dns_servers = Vec::new(); - for s in &config.dns_servers { - if let Some(addr) = s { - dns_servers.push(addr.clone()).unwrap(); - } - } - - self.apply_config( - s, - Config { - address: config.address, - gateway: config.router, - dns_servers, - }, - ) - } - } - } else if old_link_up { - socket.reset(); - self.unapply_config(s); - } - } - //if old_link_up || self.link_up { - // self.poll_configurator(timestamp) - //} - - if let Some(poll_at) = s.iface.poll_at(timestamp, &mut s.sockets) { - let t = Timer::at(instant_from_smoltcp(poll_at)); - pin_mut!(t); - if t.poll(cx).is_ready() { - cx.waker().wake_by_ref(); - } - } - } -} - -fn instant_to_smoltcp(instant: Instant) -> SmolInstant { - SmolInstant::from_millis(instant.as_millis() as i64) -} - -fn instant_from_smoltcp(instant: SmolInstant) -> Instant { - Instant::from_millis(instant.total_millis() as u64) -} diff --git a/embassy-net/src/tcp.rs b/embassy-net/src/tcp.rs index 910772c7d..367675b13 100644 --- a/embassy-net/src/tcp.rs +++ b/embassy-net/src/tcp.rs @@ -1,24 +1,39 @@ -use core::cell::UnsafeCell; -use core::future::Future; +//! TCP sockets. +//! +//! # Listening +//! +//! `embassy-net` does not have a `TcpListener`. Instead, individual `TcpSocket`s can be put into +//! listening mode by calling [`TcpSocket::accept`]. +//! +//! Incoming connections when no socket is listening are rejected. To accept many incoming +//! connections, create many sockets and put them all into listening mode. + +use core::cell::RefCell; +use core::future::poll_fn; use core::mem; use core::task::Poll; -use futures::future::poll_fn; +use embassy_net_driver::Driver; +use embassy_time::Duration; use smoltcp::iface::{Interface, SocketHandle}; use smoltcp::socket::tcp; -use smoltcp::time::Duration; +pub use smoltcp::socket::tcp::State; use smoltcp::wire::{IpEndpoint, IpListenEndpoint}; -use super::stack::Stack; -use crate::stack::SocketStack; -use crate::Device; +use crate::time::duration_to_smoltcp; +use crate::{SocketStack, Stack}; +/// Error returned by TcpSocket read/write functions. #[derive(PartialEq, Eq, Clone, Copy, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Error { + /// The connection was reset. + /// + /// This can happen on receiving a RST packet, or on timeout. ConnectionReset, } +/// Error returned by [`TcpSocket::connect`]. #[derive(PartialEq, Eq, Clone, Copy, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ConnectError { @@ -32,6 +47,7 @@ pub enum ConnectError { NoRoute, } +/// Error returned by [`TcpSocket::accept`]. #[derive(PartialEq, Eq, Clone, Copy, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum AcceptError { @@ -43,22 +59,53 @@ pub enum AcceptError { ConnectionReset, } +/// A TCP socket. pub struct TcpSocket<'a> { io: TcpIo<'a>, } +/// The reader half of a TCP socket. pub struct TcpReader<'a> { io: TcpIo<'a>, } +/// The writer half of a TCP socket. pub struct TcpWriter<'a> { io: TcpIo<'a>, } +impl<'a> TcpReader<'a> { + /// Read data from the socket. + /// + /// Returns how many bytes were read, or an error. If no data is available, it waits + /// until there is at least one byte available. + pub async fn read(&mut self, buf: &mut [u8]) -> Result { + self.io.read(buf).await + } +} + +impl<'a> TcpWriter<'a> { + /// Write data to the socket. + /// + /// Returns how many bytes were written, or an error. If the socket is not ready to + /// accept data, it waits until it is. + pub async fn write(&mut self, buf: &[u8]) -> Result { + self.io.write(buf).await + } + + /// Flushes the written data to the socket. + /// + /// This waits until all data has been sent, and ACKed by the remote host. For a connection + /// closed with [`abort()`](TcpSocket::abort) it will wait for the TCP RST packet to be sent. + pub async fn flush(&mut self) -> Result<(), Error> { + self.io.flush().await + } +} + impl<'a> TcpSocket<'a> { - pub fn new(stack: &'a Stack, rx_buffer: &'a mut [u8], tx_buffer: &'a mut [u8]) -> Self { - // safety: not accessed reentrantly. - let s = unsafe { &mut *stack.socket.get() }; + /// Create a new TCP socket on the given stack, with the given buffers. + pub fn new(stack: &'a Stack, rx_buffer: &'a mut [u8], tx_buffer: &'a mut [u8]) -> Self { + let s = &mut *stack.socket.borrow_mut(); let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) }; let tx_buffer: &'static mut [u8] = unsafe { mem::transmute(tx_buffer) }; let handle = s.sockets.add(tcp::Socket::new( @@ -74,25 +121,28 @@ impl<'a> TcpSocket<'a> { } } + /// Split the socket into reader and a writer halves. pub fn split(&mut self) -> (TcpReader<'_>, TcpWriter<'_>) { (TcpReader { io: self.io }, TcpWriter { io: self.io }) } + /// Connect to a remote host. pub async fn connect(&mut self, remote_endpoint: T) -> Result<(), ConnectError> where T: Into, { - // safety: not accessed reentrantly. - let local_port = unsafe { &mut *self.io.stack.get() }.get_local_port(); + let local_port = self.io.stack.borrow_mut().get_local_port(); - // safety: not accessed reentrantly. - match unsafe { self.io.with_mut(|s, i| s.connect(i, remote_endpoint, local_port)) } { + match { + self.io + .with_mut(|s, i| s.connect(i.context(), remote_endpoint, local_port)) + } { Ok(()) => {} Err(tcp::ConnectError::InvalidState) => return Err(ConnectError::InvalidState), Err(tcp::ConnectError::Unaddressable) => return Err(ConnectError::NoRoute), } - futures::future::poll_fn(|cx| unsafe { + poll_fn(|cx| { self.io.with_mut(|s, _| match s.state() { tcp::State::Closed | tcp::State::TimeWait => Poll::Ready(Err(ConnectError::ConnectionReset)), tcp::State::Listen => unreachable!(), @@ -106,18 +156,20 @@ impl<'a> TcpSocket<'a> { .await } + /// Accept a connection from a remote host. + /// + /// This function puts the socket in listening mode, and waits until a connection is received. pub async fn accept(&mut self, local_endpoint: T) -> Result<(), AcceptError> where T: Into, { - // safety: not accessed reentrantly. - match unsafe { self.io.with_mut(|s, _| s.listen(local_endpoint)) } { + match self.io.with_mut(|s, _| s.listen(local_endpoint)) { Ok(()) => {} Err(tcp::ListenError::InvalidState) => return Err(AcceptError::InvalidState), Err(tcp::ListenError::Unaddressable) => return Err(AcceptError::InvalidPort), } - futures::future::poll_fn(|cx| unsafe { + poll_fn(|cx| { self.io.with_mut(|s, _| match s.state() { tcp::State::Listen | tcp::State::SynSent | tcp::State::SynReceived => { s.register_send_waker(cx.waker()); @@ -129,52 +181,120 @@ impl<'a> TcpSocket<'a> { .await } + /// Read data from the socket. + /// + /// Returns how many bytes were read, or an error. If no data is available, it waits + /// until there is at least one byte available. + pub async fn read(&mut self, buf: &mut [u8]) -> Result { + self.io.read(buf).await + } + + /// Write data to the socket. + /// + /// Returns how many bytes were written, or an error. If the socket is not ready to + /// accept data, it waits until it is. + pub async fn write(&mut self, buf: &[u8]) -> Result { + self.io.write(buf).await + } + + /// Flushes the written data to the socket. + /// + /// This waits until all data has been sent, and ACKed by the remote host. For a connection + /// closed with [`abort()`](TcpSocket::abort) it will wait for the TCP RST packet to be sent. + pub async fn flush(&mut self) -> Result<(), Error> { + self.io.flush().await + } + + /// Set the timeout for the socket. + /// + /// If the timeout is set, the socket will be closed if no data is received for the + /// specified duration. pub fn set_timeout(&mut self, duration: Option) { - unsafe { self.io.with_mut(|s, _| s.set_timeout(duration)) } + self.io + .with_mut(|s, _| s.set_timeout(duration.map(duration_to_smoltcp))) } + /// Set the keep-alive interval for the socket. + /// + /// If the keep-alive interval is set, the socket will send keep-alive packets after + /// the specified duration of inactivity. + /// + /// If not set, the socket will not send keep-alive packets. pub fn set_keep_alive(&mut self, interval: Option) { - unsafe { self.io.with_mut(|s, _| s.set_keep_alive(interval)) } + self.io + .with_mut(|s, _| s.set_keep_alive(interval.map(duration_to_smoltcp))) } + /// Set the hop limit field in the IP header of sent packets. pub fn set_hop_limit(&mut self, hop_limit: Option) { - unsafe { self.io.with_mut(|s, _| s.set_hop_limit(hop_limit)) } + self.io.with_mut(|s, _| s.set_hop_limit(hop_limit)) } + /// Get the local endpoint of the socket. + /// + /// Returns `None` if the socket is not bound (listening) or not connected. pub fn local_endpoint(&self) -> Option { - unsafe { self.io.with(|s, _| s.local_endpoint()) } + self.io.with(|s, _| s.local_endpoint()) } + /// Get the remote endpoint of the socket. + /// + /// Returns `None` if the socket is not connected. pub fn remote_endpoint(&self) -> Option { - unsafe { self.io.with(|s, _| s.remote_endpoint()) } + self.io.with(|s, _| s.remote_endpoint()) } - pub fn state(&self) -> tcp::State { - unsafe { self.io.with(|s, _| s.state()) } + /// Get the state of the socket. + pub fn state(&self) -> State { + self.io.with(|s, _| s.state()) } + /// Close the write half of the socket. + /// + /// This closes only the write half of the socket. The read half side remains open, the + /// socket can still receive data. + /// + /// Data that has been written to the socket and not yet sent (or not yet ACKed) will still + /// still sent. The last segment of the pending to send data is sent with the FIN flag set. pub fn close(&mut self) { - unsafe { self.io.with_mut(|s, _| s.close()) } + self.io.with_mut(|s, _| s.close()) } + /// Forcibly close the socket. + /// + /// This instantly closes both the read and write halves of the socket. Any pending data + /// that has not been sent will be lost. + /// + /// Note that the TCP RST packet is not sent immediately - if the `TcpSocket` is dropped too soon + /// the remote host may not know the connection has been closed. + /// `abort()` callers should wait for a [`flush()`](TcpSocket::flush) call to complete before + /// dropping or reusing the socket. pub fn abort(&mut self) { - unsafe { self.io.with_mut(|s, _| s.abort()) } + self.io.with_mut(|s, _| s.abort()) } + /// Get whether the socket is ready to send data, i.e. whether there is space in the send buffer. pub fn may_send(&self) -> bool { - unsafe { self.io.with(|s, _| s.may_send()) } + self.io.with(|s, _| s.may_send()) } + /// return whether the recieve half of the full-duplex connection is open. + /// This function returns true if it’s possible to receive data from the remote endpoint. + /// It will return true while there is data in the receive buffer, and if there isn’t, + /// as long as the remote endpoint has not closed the connection. pub fn may_recv(&self) -> bool { - unsafe { self.io.with(|s, _| s.may_recv()) } + self.io.with(|s, _| s.may_recv()) + } + + /// Get whether the socket is ready to receive data, i.e. whether there is some pending data in the receive buffer. + pub fn can_recv(&self) -> bool { + self.io.with(|s, _| s.can_recv()) } } impl<'a> Drop for TcpSocket<'a> { fn drop(&mut self) { - // safety: not accessed reentrantly. - let s = unsafe { &mut *self.io.stack.get() }; - s.sockets.remove(self.io.handle); + self.io.stack.borrow_mut().sockets.remove(self.io.handle); } } @@ -182,21 +302,19 @@ impl<'a> Drop for TcpSocket<'a> { #[derive(Copy, Clone)] struct TcpIo<'a> { - stack: &'a UnsafeCell, + stack: &'a RefCell, handle: SocketHandle, } impl<'d> TcpIo<'d> { - /// SAFETY: must not call reentrantly. - unsafe fn with(&self, f: impl FnOnce(&tcp::Socket, &Interface) -> R) -> R { - let s = &*self.stack.get(); + fn with(&self, f: impl FnOnce(&tcp::Socket, &Interface) -> R) -> R { + let s = &*self.stack.borrow(); let socket = s.sockets.get::(self.handle); f(socket, &s.iface) } - /// SAFETY: must not call reentrantly. - unsafe fn with_mut(&mut self, f: impl FnOnce(&mut tcp::Socket, &mut Interface) -> R) -> R { - let s = &mut *self.stack.get(); + fn with_mut(&mut self, f: impl FnOnce(&mut tcp::Socket, &mut Interface) -> R) -> R { + let s = &mut *self.stack.borrow_mut(); let socket = s.sockets.get_mut::(self.handle); let res = f(socket, &mut s.iface); s.waker.wake(); @@ -204,7 +322,7 @@ impl<'d> TcpIo<'d> { } async fn read(&mut self, buf: &mut [u8]) -> Result { - poll_fn(move |cx| unsafe { + poll_fn(move |cx| { // CAUTION: smoltcp semantics around EOF are different to what you'd expect // from posix-like IO, so we have to tweak things here. self.with_mut(|s, _| match s.recv_slice(buf) { @@ -225,7 +343,7 @@ impl<'d> TcpIo<'d> { } async fn write(&mut self, buf: &[u8]) -> Result { - poll_fn(move |cx| unsafe { + poll_fn(move |cx| { self.with_mut(|s, _| match s.send_slice(buf) { // Not ready to send (no space in the tx buffer) Ok(0) => { @@ -242,95 +360,89 @@ impl<'d> TcpIo<'d> { } async fn flush(&mut self) -> Result<(), Error> { - poll_fn(move |_| { - Poll::Ready(Ok(())) // TODO: Is there a better implementation for this? + poll_fn(move |cx| { + self.with_mut(|s, _| { + let waiting_close = s.state() == tcp::State::Closed && s.remote_endpoint().is_some(); + // If there are outstanding send operations, register for wake up and wait + // smoltcp issues wake-ups when octets are dequeued from the send buffer + if s.send_queue() > 0 || waiting_close { + s.register_send_waker(cx.waker()); + Poll::Pending + // No outstanding sends, socket is flushed + } else { + Poll::Ready(Ok(())) + } + }) }) .await } } -impl embedded_io::Error for ConnectError { - fn kind(&self) -> embedded_io::ErrorKind { - embedded_io::ErrorKind::Other +#[cfg(feature = "nightly")] +mod embedded_io_impls { + use super::*; + + impl embedded_io::Error for ConnectError { + fn kind(&self) -> embedded_io::ErrorKind { + embedded_io::ErrorKind::Other + } + } + + impl embedded_io::Error for Error { + fn kind(&self) -> embedded_io::ErrorKind { + embedded_io::ErrorKind::Other + } + } + + impl<'d> embedded_io::Io for TcpSocket<'d> { + type Error = Error; + } + + impl<'d> embedded_io::asynch::Read for TcpSocket<'d> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.io.read(buf).await + } + } + + impl<'d> embedded_io::asynch::Write for TcpSocket<'d> { + async fn write(&mut self, buf: &[u8]) -> Result { + self.io.write(buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.io.flush().await + } + } + + impl<'d> embedded_io::Io for TcpReader<'d> { + type Error = Error; + } + + impl<'d> embedded_io::asynch::Read for TcpReader<'d> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.io.read(buf).await + } + } + + impl<'d> embedded_io::Io for TcpWriter<'d> { + type Error = Error; + } + + impl<'d> embedded_io::asynch::Write for TcpWriter<'d> { + async fn write(&mut self, buf: &[u8]) -> Result { + self.io.write(buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.io.flush().await + } } } -impl embedded_io::Error for Error { - fn kind(&self) -> embedded_io::ErrorKind { - embedded_io::ErrorKind::Other - } -} - -impl<'d> embedded_io::Io for TcpSocket<'d> { - type Error = Error; -} - -impl<'d> embedded_io::asynch::Read for TcpSocket<'d> { - type ReadFuture<'a> = impl Future> - where - Self: 'a; - - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - self.io.read(buf) - } -} - -impl<'d> embedded_io::asynch::Write for TcpSocket<'d> { - type WriteFuture<'a> = impl Future> - where - Self: 'a; - - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { - self.io.write(buf) - } - - type FlushFuture<'a> = impl Future> - where - Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - self.io.flush() - } -} - -impl<'d> embedded_io::Io for TcpReader<'d> { - type Error = Error; -} - -impl<'d> embedded_io::asynch::Read for TcpReader<'d> { - type ReadFuture<'a> = impl Future> - where - Self: 'a; - - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - self.io.read(buf) - } -} - -impl<'d> embedded_io::Io for TcpWriter<'d> { - type Error = Error; -} - -impl<'d> embedded_io::asynch::Write for TcpWriter<'d> { - type WriteFuture<'a> = impl Future> - where - Self: 'a; - - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { - self.io.write(buf) - } - - type FlushFuture<'a> = impl Future> - where - Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - self.io.flush() - } -} - -#[cfg(feature = "unstable-traits")] +/// TCP client compatible with `embedded-nal-async` traits. +#[cfg(all(feature = "unstable-traits", feature = "nightly"))] pub mod client { + use core::cell::UnsafeCell; use core::mem::MaybeUninit; use core::ptr::NonNull; @@ -339,49 +451,56 @@ pub mod client { use super::*; - /// TCP client capable of creating up to N multiple connections with tx and rx buffers according to TX_SZ and RX_SZ. - pub struct TcpClient<'d, D: Device, const N: usize, const TX_SZ: usize = 1024, const RX_SZ: usize = 1024> { + /// TCP client connection pool compatible with `embedded-nal-async` traits. + /// + /// The pool is capable of managing up to N concurrent connections with tx and rx buffers according to TX_SZ and RX_SZ. + pub struct TcpClient<'d, D: Driver, const N: usize, const TX_SZ: usize = 1024, const RX_SZ: usize = 1024> { stack: &'d Stack, state: &'d TcpClientState, } - impl<'d, D: Device, const N: usize, const TX_SZ: usize, const RX_SZ: usize> TcpClient<'d, D, N, TX_SZ, RX_SZ> { - /// Create a new TcpClient + impl<'d, D: Driver, const N: usize, const TX_SZ: usize, const RX_SZ: usize> TcpClient<'d, D, N, TX_SZ, RX_SZ> { + /// Create a new `TcpClient`. pub fn new(stack: &'d Stack, state: &'d TcpClientState) -> Self { Self { stack, state } } } - impl<'d, D: Device, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_nal_async::TcpConnect + impl<'d, D: Driver, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_nal_async::TcpConnect for TcpClient<'d, D, N, TX_SZ, RX_SZ> { type Error = Error; type Connection<'m> = TcpConnection<'m, N, TX_SZ, RX_SZ> where Self: 'm; - type ConnectFuture<'m> = impl Future, Self::Error>> + 'm - where - Self: 'm; - fn connect<'m>(&'m self, remote: embedded_nal_async::SocketAddr) -> Self::ConnectFuture<'m> { - async move { - let addr: crate::IpAddress = match remote.ip() { - IpAddr::V4(addr) => crate::IpAddress::Ipv4(crate::Ipv4Address::from_bytes(&addr.octets())), - #[cfg(feature = "proto-ipv6")] - IpAddr::V6(addr) => crate::IpAddress::Ipv6(crate::Ipv6Address::from_bytes(&addr.octets())), - #[cfg(not(feature = "proto-ipv6"))] - IpAddr::V6(_) => panic!("ipv6 support not enabled"), - }; - let remote_endpoint = (addr, remote.port()); - let mut socket = TcpConnection::new(&self.stack, self.state)?; - socket - .socket - .connect(remote_endpoint) - .await - .map_err(|_| Error::ConnectionReset)?; - Ok(socket) - } + async fn connect<'a>( + &'a self, + remote: embedded_nal_async::SocketAddr, + ) -> Result, Self::Error> + where + Self: 'a, + { + let addr: crate::IpAddress = match remote.ip() { + #[cfg(feature = "proto-ipv4")] + IpAddr::V4(addr) => crate::IpAddress::Ipv4(crate::Ipv4Address::from_bytes(&addr.octets())), + #[cfg(not(feature = "proto-ipv4"))] + IpAddr::V4(_) => panic!("ipv4 support not enabled"), + #[cfg(feature = "proto-ipv6")] + IpAddr::V6(addr) => crate::IpAddress::Ipv6(crate::Ipv6Address::from_bytes(&addr.octets())), + #[cfg(not(feature = "proto-ipv6"))] + IpAddr::V6(_) => panic!("ipv6 support not enabled"), + }; + let remote_endpoint = (addr, remote.port()); + let mut socket = TcpConnection::new(&self.stack, self.state)?; + socket + .socket + .connect(remote_endpoint) + .await + .map_err(|_| Error::ConnectionReset)?; + Ok(socket) } } + /// Opened TCP connection in a [`TcpClient`]. pub struct TcpConnection<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> { socket: TcpSocket<'d>, state: &'d TcpClientState, @@ -389,10 +508,10 @@ pub mod client { } impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> TcpConnection<'d, N, TX_SZ, RX_SZ> { - fn new(stack: &'d Stack, state: &'d TcpClientState) -> Result { + fn new(stack: &'d Stack, state: &'d TcpClientState) -> Result { let mut bufs = state.pool.alloc().ok_or(Error::ConnectionReset)?; Ok(Self { - socket: unsafe { TcpSocket::new(stack, &mut bufs.as_mut().0, &mut bufs.as_mut().1) }, + socket: unsafe { TcpSocket::new(stack, &mut bufs.as_mut().1, &mut bufs.as_mut().0) }, state, bufs, }) @@ -417,32 +536,20 @@ pub mod client { impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_io::asynch::Read for TcpConnection<'d, N, TX_SZ, RX_SZ> { - type ReadFuture<'a> = impl Future> - where - Self: 'a; - - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - self.socket.read(buf) + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.socket.read(buf).await } } impl<'d, const N: usize, const TX_SZ: usize, const RX_SZ: usize> embedded_io::asynch::Write for TcpConnection<'d, N, TX_SZ, RX_SZ> { - type WriteFuture<'a> = impl Future> - where - Self: 'a; - - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { - self.socket.write(buf) + async fn write(&mut self, buf: &[u8]) -> Result { + self.socket.write(buf).await } - type FlushFuture<'a> = impl Future> - where - Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - self.socket.flush() + async fn flush(&mut self) -> Result<(), Self::Error> { + self.socket.flush().await } } @@ -452,6 +559,7 @@ pub mod client { } impl TcpClientState { + /// Create a new `TcpClientState`. pub const fn new() -> Self { Self { pool: Pool::new() } } diff --git a/embassy-net/src/time.rs b/embassy-net/src/time.rs new file mode 100644 index 000000000..b98d40fdc --- /dev/null +++ b/embassy-net/src/time.rs @@ -0,0 +1,20 @@ +#![allow(unused)] + +use embassy_time::{Duration, Instant}; +use smoltcp::time::{Duration as SmolDuration, Instant as SmolInstant}; + +pub(crate) fn instant_to_smoltcp(instant: Instant) -> SmolInstant { + SmolInstant::from_micros(instant.as_micros() as i64) +} + +pub(crate) fn instant_from_smoltcp(instant: SmolInstant) -> Instant { + Instant::from_micros(instant.total_micros() as u64) +} + +pub(crate) fn duration_to_smoltcp(duration: Duration) -> SmolDuration { + SmolDuration::from_micros(duration.as_micros()) +} + +pub(crate) fn duration_from_smoltcp(duration: SmolDuration) -> Duration { + Duration::from_micros(duration.total_micros()) +} diff --git a/embassy-net/src/udp.rs b/embassy-net/src/udp.rs index 78b09a492..0d97b6db1 100644 --- a/embassy-net/src/udp.rs +++ b/embassy-net/src/udp.rs @@ -1,15 +1,19 @@ -use core::cell::UnsafeCell; -use core::mem; -use core::task::Poll; +//! UDP sockets. -use futures::future::poll_fn; +use core::cell::RefCell; +use core::future::poll_fn; +use core::mem; +use core::task::{Context, Poll}; + +use embassy_net_driver::Driver; use smoltcp::iface::{Interface, SocketHandle}; -use smoltcp::socket::udp::{self, PacketMetadata}; +use smoltcp::socket::udp; +pub use smoltcp::socket::udp::PacketMetadata; use smoltcp::wire::{IpEndpoint, IpListenEndpoint}; -use super::stack::SocketStack; -use crate::{Device, Stack}; +use crate::{SocketStack, Stack}; +/// Error returned by [`UdpSocket::bind`]. #[derive(PartialEq, Eq, Clone, Copy, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum BindError { @@ -19,6 +23,7 @@ pub enum BindError { NoRoute, } +/// Error returned by [`UdpSocket::recv_from`] and [`UdpSocket::send_to`]. #[derive(PartialEq, Eq, Clone, Copy, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Error { @@ -26,21 +31,22 @@ pub enum Error { NoRoute, } +/// An UDP socket. pub struct UdpSocket<'a> { - stack: &'a UnsafeCell, + stack: &'a RefCell, handle: SocketHandle, } impl<'a> UdpSocket<'a> { - pub fn new( + /// Create a new UDP socket using the provided stack and buffers. + pub fn new( stack: &'a Stack, rx_meta: &'a mut [PacketMetadata], rx_buffer: &'a mut [u8], tx_meta: &'a mut [PacketMetadata], tx_buffer: &'a mut [u8], ) -> Self { - // safety: not accessed reentrantly. - let s = unsafe { &mut *stack.socket.get() }; + let s = &mut *stack.socket.borrow_mut(); let rx_meta: &'static mut [PacketMetadata] = unsafe { mem::transmute(rx_meta) }; let rx_buffer: &'static mut [u8] = unsafe { mem::transmute(rx_buffer) }; @@ -57,101 +63,131 @@ impl<'a> UdpSocket<'a> { } } + /// Bind the socket to a local endpoint. pub fn bind(&mut self, endpoint: T) -> Result<(), BindError> where T: Into, { let mut endpoint = endpoint.into(); - // safety: not accessed reentrantly. if endpoint.port == 0 { // If user didn't specify port allocate a dynamic port. - endpoint.port = unsafe { &mut *self.stack.get() }.get_local_port(); + endpoint.port = self.stack.borrow_mut().get_local_port(); } - // safety: not accessed reentrantly. - match unsafe { self.with_mut(|s, _| s.bind(endpoint)) } { + match self.with_mut(|s, _| s.bind(endpoint)) { Ok(()) => Ok(()), Err(udp::BindError::InvalidState) => Err(BindError::InvalidState), Err(udp::BindError::Unaddressable) => Err(BindError::NoRoute), } } - /// SAFETY: must not call reentrantly. - unsafe fn with(&self, f: impl FnOnce(&udp::Socket, &Interface) -> R) -> R { - let s = &*self.stack.get(); + fn with(&self, f: impl FnOnce(&udp::Socket, &Interface) -> R) -> R { + let s = &*self.stack.borrow(); let socket = s.sockets.get::(self.handle); f(socket, &s.iface) } - /// SAFETY: must not call reentrantly. - unsafe fn with_mut(&self, f: impl FnOnce(&mut udp::Socket, &mut Interface) -> R) -> R { - let s = &mut *self.stack.get(); + fn with_mut(&self, f: impl FnOnce(&mut udp::Socket, &mut Interface) -> R) -> R { + let s = &mut *self.stack.borrow_mut(); let socket = s.sockets.get_mut::(self.handle); let res = f(socket, &mut s.iface); s.waker.wake(); res } + /// Receive a datagram. + /// + /// This method will wait until a datagram is received. + /// + /// Returns the number of bytes received and the remote endpoint. pub async fn recv_from(&self, buf: &mut [u8]) -> Result<(usize, IpEndpoint), Error> { - poll_fn(move |cx| unsafe { - self.with_mut(|s, _| match s.recv_slice(buf) { - Ok(x) => Poll::Ready(Ok(x)), - // No data ready - Err(udp::RecvError::Exhausted) => { - //s.register_recv_waker(cx.waker()); - cx.waker().wake_by_ref(); - Poll::Pending - } - }) - }) - .await + poll_fn(move |cx| self.poll_recv_from(buf, cx)).await } + /// Receive a datagram. + /// + /// When no datagram is available, this method will return `Poll::Pending` and + /// register the current task to be notified when a datagram is received. + /// + /// When a datagram is received, this method will return `Poll::Ready` with the + /// number of bytes received and the remote endpoint. + pub fn poll_recv_from(&self, buf: &mut [u8], cx: &mut Context<'_>) -> Poll> { + self.with_mut(|s, _| match s.recv_slice(buf) { + Ok((n, meta)) => Poll::Ready(Ok((n, meta.endpoint))), + // No data ready + Err(udp::RecvError::Exhausted) => { + s.register_recv_waker(cx.waker()); + Poll::Pending + } + }) + } + + /// Send a datagram to the specified remote endpoint. + /// + /// This method will wait until the datagram has been sent. + /// + /// When the remote endpoint is not reachable, this method will return `Err(Error::NoRoute)` pub async fn send_to(&self, buf: &[u8], remote_endpoint: T) -> Result<(), Error> where T: Into, { - let remote_endpoint = remote_endpoint.into(); - poll_fn(move |cx| unsafe { - self.with_mut(|s, _| match s.send_slice(buf, remote_endpoint) { - // Entire datagram has been sent - Ok(()) => Poll::Ready(Ok(())), - Err(udp::SendError::BufferFull) => { - s.register_send_waker(cx.waker()); - Poll::Pending - } - Err(udp::SendError::Unaddressable) => Poll::Ready(Err(Error::NoRoute)), - }) - }) - .await + let remote_endpoint: IpEndpoint = remote_endpoint.into(); + poll_fn(move |cx| self.poll_send_to(buf, remote_endpoint, cx)).await } - pub fn endpoint(&self) -> IpListenEndpoint { - unsafe { self.with(|s, _| s.endpoint()) } + /// Send a datagram to the specified remote endpoint. + /// + /// When the datagram has been sent, this method will return `Poll::Ready(Ok())`. + /// + /// When the socket's send buffer is full, this method will return `Poll::Pending` + /// and register the current task to be notified when the buffer has space available. + /// + /// When the remote endpoint is not reachable, this method will return `Poll::Ready(Err(Error::NoRoute))`. + pub fn poll_send_to(&self, buf: &[u8], remote_endpoint: T, cx: &mut Context<'_>) -> Poll> + where + T: Into, + { + self.with_mut(|s, _| match s.send_slice(buf, remote_endpoint) { + // Entire datagram has been sent + Ok(()) => Poll::Ready(Ok(())), + Err(udp::SendError::BufferFull) => { + s.register_send_waker(cx.waker()); + Poll::Pending + } + Err(udp::SendError::Unaddressable) => Poll::Ready(Err(Error::NoRoute)), + }) } + /// Returns the local endpoint of the socket. + pub fn endpoint(&self) -> IpListenEndpoint { + self.with(|s, _| s.endpoint()) + } + + /// Returns whether the socket is open. + pub fn is_open(&self) -> bool { - unsafe { self.with(|s, _| s.is_open()) } + self.with(|s, _| s.is_open()) } + /// Close the socket. pub fn close(&mut self) { - unsafe { self.with_mut(|s, _| s.close()) } + self.with_mut(|s, _| s.close()) } + /// Returns whether the socket is ready to send data, i.e. it has enough buffer space to hold a packet. pub fn may_send(&self) -> bool { - unsafe { self.with(|s, _| s.can_send()) } + self.with(|s, _| s.can_send()) } + /// Returns whether the socket is ready to receive data, i.e. it has received a packet that's now in the buffer. pub fn may_recv(&self) -> bool { - unsafe { self.with(|s, _| s.can_recv()) } + self.with(|s, _| s.can_recv()) } } impl Drop for UdpSocket<'_> { fn drop(&mut self) { - // safety: not accessed reentrantly. - let s = unsafe { &mut *self.stack.get() }; - s.sockets.remove(self.handle); + self.stack.borrow_mut().sockets.remove(self.handle); } } diff --git a/embassy-nrf/Cargo.toml b/embassy-nrf/Cargo.toml index 186c73a58..57dd22f1c 100644 --- a/embassy-nrf/Cargo.toml +++ b/embassy-nrf/Cargo.toml @@ -2,12 +2,13 @@ name = "embassy-nrf" version = "0.1.0" edition = "2021" +license = "MIT OR Apache-2.0" [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-nrf-v$VERSION/embassy-nrf/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-nrf/src/" -features = ["nightly", "defmt", "unstable-pac", "unstable-traits", "gpiote", "time-driver-rtc1"] +features = ["nightly", "time", "defmt", "unstable-pac", "unstable-traits", "gpiote", "time-driver-rtc1"] flavors = [ { regex_feature = "nrf52.*", target = "thumbv7em-none-eabihf" }, { regex_feature = "nrf53.*", target = "thumbv8m.main-none-eabihf" }, @@ -15,13 +16,26 @@ flavors = [ ] [features] +default = ["rt"] +rt = [ + "nrf52805-pac?/rt", + "nrf52810-pac?/rt", + "nrf52811-pac?/rt", + "nrf52820-pac?/rt", + "nrf52832-pac?/rt", + "nrf52833-pac?/rt", + "nrf52840-pac?/rt", + "nrf5340-app-pac?/rt", + "nrf5340-net-pac?/rt", + "nrf9160-pac?/rt", +] time = ["dep:embassy-time"] -defmt = ["dep:defmt", "embassy-executor/defmt", "embassy-sync/defmt", "embassy-usb?/defmt", "embedded-io?/defmt", "embassy-embedded-hal/defmt"] +defmt = ["dep:defmt", "embassy-sync/defmt", "embassy-usb-driver?/defmt", "embedded-io?/defmt", "embassy-embedded-hal/defmt"] # Enable nightly-only features -nightly = ["embedded-hal-1", "embedded-hal-async", "embassy-usb", "embedded-storage-async", "dep:embedded-io", "embassy-embedded-hal/nightly"] +nightly = ["embedded-hal-1", "embedded-hal-async", "dep:embassy-usb-driver", "embedded-storage-async", "dep:embedded-io", "embassy-embedded-hal/nightly"] # Reexport the PAC for the currently enabled chip at `embassy_nrf::pac`. # This is unstable because semver-minor (non-breaking) releases of embassy-nrf may major-bump (breaking) the PAC version. @@ -33,22 +47,30 @@ unstable-pac = [] # Implement embedded-hal-async traits if `nightly` is set as well. unstable-traits = ["embedded-hal-1"] -nrf52805 = ["nrf52805-pac", "_ppi"] -nrf52810 = ["nrf52810-pac", "_ppi"] -nrf52811 = ["nrf52811-pac", "_ppi"] -nrf52820 = ["nrf52820-pac", "_ppi"] -nrf52832 = ["nrf52832-pac", "_ppi"] -nrf52833 = ["nrf52833-pac", "_ppi", "_gpio-p1"] -nrf52840 = ["nrf52840-pac", "_ppi", "_gpio-p1"] -nrf5340-app-s = ["_nrf5340-app"] -nrf5340-app-ns = ["_nrf5340-app"] +nrf52805 = ["nrf52805-pac", "_nrf52"] +nrf52810 = ["nrf52810-pac", "_nrf52"] +nrf52811 = ["nrf52811-pac", "_nrf52"] +nrf52820 = ["nrf52820-pac", "_nrf52"] +nrf52832 = ["nrf52832-pac", "_nrf52"] +nrf52833 = ["nrf52833-pac", "_nrf52", "_gpio-p1"] +nrf52840 = ["nrf52840-pac", "_nrf52", "_gpio-p1"] +nrf5340-app-s = ["_nrf5340-app", "_s"] +nrf5340-app-ns = ["_nrf5340-app", "_ns"] nrf5340-net = ["_nrf5340-net"] -nrf9160-s = ["_nrf9160"] -nrf9160-ns = ["_nrf9160"] +nrf9160-s = ["_nrf9160", "_s"] +nrf9160-ns = ["_nrf9160", "_ns"] gpiote = [] time-driver-rtc1 = ["_time-driver"] +# Allow using the NFC pins as regular GPIO pins (P0_09/P0_10 on nRF52, P0_02/P0_03 on nRF53) +nfc-pins-as-gpio = [] + +# Allow using the RST pin as a regular GPIO pin. +# nrf52805, nrf52810, nrf52811, nrf52832: P0_21 +# nrf52820, nrf52833, nrf52840: P0_18 +reset-pin-as-gpio = [] + # Features starting with `_` are for internal use only. They're not intended # to be enabled by other crates, and are not covered by semver guarantees. @@ -56,46 +78,50 @@ _nrf5340-app = ["_nrf5340", "nrf5340-app-pac"] _nrf5340-net = ["_nrf5340", "nrf5340-net-pac"] _nrf5340 = ["_gpio-p1", "_dppi"] _nrf9160 = ["nrf9160-pac", "_dppi"] +_nrf52 = ["_ppi"] -_time-driver = ["dep:embassy-time", "embassy-time?/tick-32768hz"] +_time-driver = ["dep:embassy-time", "embassy-time?/tick-hz-32_768"] + +# trustzone state. +_s = [] +_ns = [] _ppi = [] _dppi = [] _gpio-p1 = [] [dependencies] -embassy-executor = { version = "0.1.0", path = "../embassy-executor", optional = true } -embassy-time = { version = "0.1.0", path = "../embassy-time", optional = true } -embassy-sync = { version = "0.1.0", path = "../embassy-sync" } -embassy-cortex-m = { version = "0.1.0", path = "../embassy-cortex-m", features = ["prio-bits-3"]} -embassy-hal-common = {version = "0.1.0", path = "../embassy-hal-common" } +embassy-time = { version = "0.1.2", path = "../embassy-time", optional = true } +embassy-sync = { version = "0.2.0", path = "../embassy-sync" } +embassy-hal-common = {version = "0.1.0", path = "../embassy-hal-common", features = ["cortex-m", "prio-bits-3"] } embassy-embedded-hal = {version = "0.1.0", path = "../embassy-embedded-hal" } -embassy-usb = {version = "0.1.0", path = "../embassy-usb", optional=true } +embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver", optional=true } embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } -embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.8", optional = true} -embedded-hal-async = { version = "0.1.0-alpha.1", optional = true} -embedded-io = { version = "0.3.0", features = ["async"], optional = true } +embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11", optional = true} +embedded-hal-async = { version = "=0.2.0-alpha.2", optional = true} +embedded-io = { version = "0.4.0", features = ["async"], optional = true } defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } cortex-m-rt = ">=0.6.15,<0.8" cortex-m = "0.7.6" -futures = { version = "0.3.17", default-features = false } +futures = { version = "0.3.17", default-features = false } critical-section = "1.1" rand_core = "0.6.3" fixed = "1.10.0" embedded-storage = "0.3.0" -embedded-storage-async = { version = "0.3.0", optional = true } +embedded-storage-async = { version = "0.4.0", optional = true } cfg-if = "1.0.0" -nrf52805-pac = { version = "0.11.0", optional = true, features = [ "rt" ] } -nrf52810-pac = { version = "0.11.0", optional = true, features = [ "rt" ] } -nrf52811-pac = { version = "0.11.0", optional = true, features = [ "rt" ] } -nrf52820-pac = { version = "0.11.0", optional = true, features = [ "rt" ] } -nrf52832-pac = { version = "0.11.0", optional = true, features = [ "rt" ] } -nrf52833-pac = { version = "0.11.0", optional = true, features = [ "rt" ] } -nrf52840-pac = { version = "0.11.0", optional = true, features = [ "rt" ] } -nrf5340-app-pac = { version = "0.11.0", optional = true, features = [ "rt" ] } -nrf5340-net-pac = { version = "0.11.0", optional = true, features = [ "rt" ] } -nrf9160-pac = { version = "0.11.0", optional = true, features = [ "rt" ] } +nrf52805-pac = { version = "0.12.0", optional = true } +nrf52810-pac = { version = "0.12.0", optional = true } +nrf52811-pac = { version = "0.12.0", optional = true } +nrf52820-pac = { version = "0.12.0", optional = true } +nrf52832-pac = { version = "0.12.0", optional = true } +nrf52833-pac = { version = "0.12.0", optional = true } +nrf52840-pac = { version = "0.12.0", optional = true } +nrf5340-app-pac = { version = "0.12.0", optional = true } +nrf5340-net-pac = { version = "0.12.0", optional = true } +nrf9160-pac = { version = "0.12.0", optional = true } + diff --git a/embassy-nrf/README.md b/embassy-nrf/README.md new file mode 100644 index 000000000..129ec0c01 --- /dev/null +++ b/embassy-nrf/README.md @@ -0,0 +1,58 @@ +# Embassy nRF HAL + +HALs implement safe, idiomatic Rust APIs to use the hardware capabilities, so raw register manipulation is not needed. + +The Embassy nRF HAL targets the Nordic Semiconductor nRF family of hardware. The HAL implements both blocking and async APIs +for many peripherals. The benefit of using the async APIs is that the HAL takes care of waiting for peripherals to +complete operations in low power mod and handling interrupts, so that applications can focus on more important matters. + +## EasyDMA considerations + +On nRF chips, peripherals can use the so called EasyDMA feature to offload the task of interacting +with peripherals. It takes care of sending/receiving data over a variety of bus protocols (TWI/I2C, UART, SPI). +However, EasyDMA requires the buffers used to transmit and receive data to reside in RAM. Unfortunately, Rust +slices will not always do so. The following example using the SPI peripheral shows a common situation where this might happen: + +```rust,ignore +// As we pass a slice to the function whose contents will not ever change, +// the compiler writes it into the flash and thus the pointer to it will +// reference static memory. Since EasyDMA requires slices to reside in RAM, +// this function call will fail. +let result = spim.write_from_ram(&[1, 2, 3]); +assert_eq!(result, Err(Error::BufferNotInRAM)); + +// The data is still static and located in flash. However, since we are assigning +// it to a variable, the compiler will load it into memory. Passing a reference to the +// variable will yield a pointer that references dynamic memory, thus making EasyDMA happy. +// This function call succeeds. +let data = [1, 2, 3]; +let result = spim.write_from_ram(&data); +assert!(result.is_ok()); +``` + +Each peripheral struct which uses EasyDMA ([`Spim`](spim::Spim), [`Uarte`](uarte::Uarte), [`Twim`](twim::Twim)) has two variants of their mutating functions: +- Functions with the suffix (e.g. [`write_from_ram`](spim::Spim::write_from_ram), [`transfer_from_ram`](spim::Spim::transfer_from_ram)) will return an error if the passed slice does not reside in RAM. +- Functions without the suffix (e.g. [`write`](spim::Spim::write), [`transfer`](spim::Spim::transfer)) will check whether the data is in RAM and copy it into memory prior to transmission. + +Since copying incurs a overhead, you are given the option to choose from `_from_ram` variants which will +fail and notify you, or the more convenient versions without the suffix which are potentially a little bit +more inefficient. Be aware that this overhead is not only in terms of instruction count but also in terms of memory usage +as the methods without the suffix will be allocating a statically sized buffer (up to 512 bytes for the nRF52840). + +Note that the methods that read data like [`read`](spim::Spim::read) and [`transfer_in_place`](spim::Spim::transfer_in_place) do not have the corresponding `_from_ram` variants as +mutable slices always reside in RAM. + +## Minimum supported Rust version (MSRV) + +Embassy is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release. + +## License + +This work is licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. + diff --git a/embassy-nrf/src/buffered_uarte.rs b/embassy-nrf/src/buffered_uarte.rs index 62af544ae..9bc1c1e7a 100644 --- a/embassy-nrf/src/buffered_uarte.rs +++ b/embassy-nrf/src/buffered_uarte.rs @@ -1,9 +1,4 @@ -//! Async buffered UART -//! -//! WARNING!!! The functionality provided here is intended to be used only -//! in situations where hardware flow control are available i.e. CTS and RTS. -//! This is a problem that should be addressed at a later stage and can be -//! fully explained at . +//! Async buffered UART driver. //! //! Note that discarding a future from a read or write operation may lead to losing //! data. For example, when using `futures_util::future::select` and completion occurs @@ -14,82 +9,234 @@ //! Please also see [crate::uarte] to understand when [BufferedUarte] should be used. use core::cmp::min; -use core::future::Future; -use core::sync::atomic::{compiler_fence, Ordering}; +use core::future::poll_fn; +use core::marker::PhantomData; +use core::slice; +use core::sync::atomic::{compiler_fence, AtomicU8, AtomicUsize, Ordering}; use core::task::Poll; -use embassy_cortex_m::peripheral::{PeripheralMutex, PeripheralState, StateStorage}; -use embassy_hal_common::ring_buffer::RingBuffer; +use embassy_hal_common::atomic_ring_buffer::RingBuffer; use embassy_hal_common::{into_ref, PeripheralRef}; -use embassy_sync::waitqueue::WakerRegistration; -use futures::future::poll_fn; +use embassy_sync::waitqueue::AtomicWaker; // Re-export SVD variants to allow user to directly set values pub use pac::uarte0::{baudrate::BAUDRATE_A as Baudrate, config::PARITY_A as Parity}; -use crate::gpio::Pin as GpioPin; -use crate::interrupt::InterruptExt; -use crate::ppi::{AnyConfigurableChannel, ConfigurableChannel, Event, Ppi, Task}; -use crate::timer::{Frequency, Instance as TimerInstance, Timer}; +use crate::gpio::sealed::Pin; +use crate::gpio::{self, AnyPin, Pin as GpioPin, PselBits}; +use crate::interrupt::typelevel::Interrupt; +use crate::ppi::{ + self, AnyConfigurableChannel, AnyGroup, Channel, ConfigurableChannel, Event, Group, Ppi, PpiGroup, Task, +}; +use crate::timer::{Instance as TimerInstance, Timer}; use crate::uarte::{apply_workaround_for_enable_anomaly, Config, Instance as UarteInstance}; -use crate::{pac, Peripheral}; +use crate::{interrupt, pac, Peripheral}; -#[derive(Copy, Clone, Debug, PartialEq)] -enum RxState { - Idle, - Receiving, -} +mod sealed { + use super::*; -#[derive(Copy, Clone, Debug, PartialEq)] -enum TxState { - Idle, - Transmitting(usize), -} + pub struct State { + pub tx_waker: AtomicWaker, + pub tx_buf: RingBuffer, + pub tx_count: AtomicUsize, -/// A type for storing the state of the UARTE peripheral that can be stored in a static. -pub struct State<'d, U: UarteInstance, T: TimerInstance>(StateStorage>); -impl<'d, U: UarteInstance, T: TimerInstance> State<'d, U, T> { - /// Create an instance for storing UARTE peripheral state. - pub fn new() -> Self { - Self(StateStorage::new()) + pub rx_waker: AtomicWaker, + pub rx_buf: RingBuffer, + pub rx_bufs: AtomicU8, + pub rx_ppi_ch: AtomicU8, } } -struct StateInner<'d, U: UarteInstance, T: TimerInstance> { - _peri: PeripheralRef<'d, U>, - timer: Timer<'d, T>, - _ppi_ch1: Ppi<'d, AnyConfigurableChannel, 1, 2>, - _ppi_ch2: Ppi<'d, AnyConfigurableChannel, 1, 1>, - - rx: RingBuffer<'d>, - rx_state: RxState, - rx_waker: WakerRegistration, - - tx: RingBuffer<'d>, - tx_state: TxState, - tx_waker: WakerRegistration, +/// UART error. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + // No errors for now } -/// Interface to a UARTE instance +pub(crate) use sealed::State; + +impl State { + pub(crate) const fn new() -> Self { + Self { + tx_waker: AtomicWaker::new(), + tx_buf: RingBuffer::new(), + tx_count: AtomicUsize::new(0), + + rx_waker: AtomicWaker::new(), + rx_buf: RingBuffer::new(), + rx_bufs: AtomicU8::new(0), + rx_ppi_ch: AtomicU8::new(0), + } + } +} + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + //trace!("irq: start"); + let r = U::regs(); + let s = U::buffered_state(); + + let buf_len = s.rx_buf.len(); + let half_len = buf_len / 2; + let mut tx = unsafe { s.tx_buf.reader() }; + let mut rx = unsafe { s.rx_buf.writer() }; + + if r.events_error.read().bits() != 0 { + r.events_error.reset(); + let errs = r.errorsrc.read(); + r.errorsrc.write(|w| unsafe { w.bits(errs.bits()) }); + + if errs.overrun().bit() { + panic!("BufferedUarte overrun"); + } + } + + // Received some bytes, wake task. + if r.inten.read().rxdrdy().bit_is_set() && r.events_rxdrdy.read().bits() != 0 { + r.intenclr.write(|w| w.rxdrdy().clear()); + r.events_rxdrdy.reset(); + s.rx_waker.wake(); + } + + // If not RXing, start. + if s.rx_bufs.load(Ordering::Relaxed) == 0 { + let (ptr, len) = rx.push_buf(); + if len >= half_len { + //trace!(" irq_rx: starting {:?}", half_len); + s.rx_bufs.store(1, Ordering::Relaxed); + + // Set up the DMA read + r.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) }); + r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(half_len as _) }); + + // Start UARTE Receive transaction + r.tasks_startrx.write(|w| unsafe { w.bits(1) }); + rx.push_done(half_len); + r.intenset.write(|w| w.rxstarted().set()); + } + } + + if r.events_rxstarted.read().bits() != 0 { + //trace!(" irq_rx: rxstarted"); + let (ptr, len) = rx.push_buf(); + if len >= half_len { + //trace!(" irq_rx: starting second {:?}", half_len); + + // Set up the DMA read + r.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) }); + r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(half_len as _) }); + + let chn = s.rx_ppi_ch.load(Ordering::Relaxed); + + ppi::regs().chenset.write(|w| unsafe { w.bits(1 << chn) }); + + rx.push_done(half_len); + + r.events_rxstarted.reset(); + } else { + //trace!(" irq_rx: rxstarted no buf"); + r.intenclr.write(|w| w.rxstarted().clear()); + } + } + + // ============================= + + // TX end + if r.events_endtx.read().bits() != 0 { + r.events_endtx.reset(); + + let n = s.tx_count.load(Ordering::Relaxed); + //trace!(" irq_tx: endtx {:?}", n); + tx.pop_done(n); + s.tx_waker.wake(); + s.tx_count.store(0, Ordering::Relaxed); + } + + // If not TXing, start. + if s.tx_count.load(Ordering::Relaxed) == 0 { + let (ptr, len) = tx.pop_buf(); + if len != 0 { + //trace!(" irq_tx: starting {:?}", len); + s.tx_count.store(len, Ordering::Relaxed); + + // Set up the DMA write + r.txd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) }); + r.txd.maxcnt.write(|w| unsafe { w.maxcnt().bits(len as _) }); + + // Start UARTE Transmit transaction + r.tasks_starttx.write(|w| unsafe { w.bits(1) }); + } + } + + //trace!("irq: end"); + } +} + +/// Buffered UARTE driver. pub struct BufferedUarte<'d, U: UarteInstance, T: TimerInstance> { - inner: PeripheralMutex<'d, StateInner<'d, U, T>>, + _peri: PeripheralRef<'d, U>, + timer: Timer<'d, T>, + _ppi_ch1: Ppi<'d, AnyConfigurableChannel, 1, 1>, + _ppi_ch2: Ppi<'d, AnyConfigurableChannel, 1, 2>, + _ppi_group: PpiGroup<'d, AnyGroup>, } impl<'d, U: UarteInstance, T: TimerInstance> Unpin for BufferedUarte<'d, U, T> {} impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { - /// Create a new instance of a BufferedUarte. + /// Create a new BufferedUarte without hardware flow control. /// - /// See the [module documentation](crate::buffered_uarte) for more details about the intended use. + /// # Panics /// - /// The BufferedUarte uses the provided state to store the buffers and peripheral state. The timer and ppi channels are used to 'emulate' idle line detection so that read operations - /// can return early if there is no data to receive. + /// Panics if `rx_buffer.len()` is odd. pub fn new( - state: &'d mut State<'d, U, T>, - peri: impl Peripheral

+ 'd, + uarte: impl Peripheral

+ 'd, timer: impl Peripheral

+ 'd, - ppi_ch1: impl Peripheral

+ 'd, - ppi_ch2: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, + ppi_ch1: impl Peripheral

+ 'd, + ppi_ch2: impl Peripheral

+ 'd, + ppi_group: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + rxd: impl Peripheral

+ 'd, + txd: impl Peripheral

+ 'd, + config: Config, + rx_buffer: &'d mut [u8], + tx_buffer: &'d mut [u8], + ) -> Self { + into_ref!(rxd, txd, ppi_ch1, ppi_ch2, ppi_group); + Self::new_inner( + uarte, + timer, + ppi_ch1.map_into(), + ppi_ch2.map_into(), + ppi_group.map_into(), + rxd.map_into(), + txd.map_into(), + None, + None, + config, + rx_buffer, + tx_buffer, + ) + } + + /// Create a new BufferedUarte with hardware flow control (RTS/CTS) + /// + /// # Panics + /// + /// Panics if `rx_buffer.len()` is odd. + pub fn new_with_rtscts( + uarte: impl Peripheral

+ 'd, + timer: impl Peripheral

+ 'd, + ppi_ch1: impl Peripheral

+ 'd, + ppi_ch2: impl Peripheral

+ 'd, + ppi_group: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, rxd: impl Peripheral

+ 'd, txd: impl Peripheral

+ 'd, cts: impl Peripheral

+ 'd, @@ -98,12 +245,43 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { rx_buffer: &'d mut [u8], tx_buffer: &'d mut [u8], ) -> Self { - into_ref!(peri, ppi_ch1, ppi_ch2, irq, rxd, txd, cts, rts); + into_ref!(rxd, txd, cts, rts, ppi_ch1, ppi_ch2, ppi_group); + Self::new_inner( + uarte, + timer, + ppi_ch1.map_into(), + ppi_ch2.map_into(), + ppi_group.map_into(), + rxd.map_into(), + txd.map_into(), + Some(cts.map_into()), + Some(rts.map_into()), + config, + rx_buffer, + tx_buffer, + ) + } + + fn new_inner( + peri: impl Peripheral

+ 'd, + timer: impl Peripheral

+ 'd, + ppi_ch1: PeripheralRef<'d, AnyConfigurableChannel>, + ppi_ch2: PeripheralRef<'d, AnyConfigurableChannel>, + ppi_group: PeripheralRef<'d, AnyGroup>, + rxd: PeripheralRef<'d, AnyPin>, + txd: PeripheralRef<'d, AnyPin>, + cts: Option>, + rts: Option>, + config: Config, + rx_buffer: &'d mut [u8], + tx_buffer: &'d mut [u8], + ) -> Self { + into_ref!(peri, timer); + + assert!(rx_buffer.len() % 2 == 0); let r = U::regs(); - let mut timer = Timer::new(timer); - rxd.conf().write(|w| w.input().connect().drive().h0h1()); r.psel.rxd.write(|w| unsafe { w.bits(rxd.psel_bits()) }); @@ -111,361 +289,388 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { txd.conf().write(|w| w.dir().output().drive().h0h1()); r.psel.txd.write(|w| unsafe { w.bits(txd.psel_bits()) }); - cts.conf().write(|w| w.input().connect().drive().h0h1()); + if let Some(pin) = &cts { + pin.conf().write(|w| w.input().connect().drive().h0h1()); + } r.psel.cts.write(|w| unsafe { w.bits(cts.psel_bits()) }); - rts.set_high(); - rts.conf().write(|w| w.dir().output().drive().h0h1()); + if let Some(pin) = &rts { + pin.set_high(); + pin.conf().write(|w| w.dir().output().drive().h0h1()); + } r.psel.rts.write(|w| unsafe { w.bits(rts.psel_bits()) }); - r.baudrate.write(|w| w.baudrate().variant(config.baudrate)); - r.config.write(|w| w.parity().variant(config.parity)); + // Initialize state + let s = U::buffered_state(); + s.tx_count.store(0, Ordering::Relaxed); + s.rx_bufs.store(0, Ordering::Relaxed); + let len = tx_buffer.len(); + unsafe { s.tx_buf.init(tx_buffer.as_mut_ptr(), len) }; + let len = rx_buffer.len(); + unsafe { s.rx_buf.init(rx_buffer.as_mut_ptr(), len) }; // Configure r.config.write(|w| { - w.hwfc().bit(true); + w.hwfc().bit(false); w.parity().variant(config.parity); w }); r.baudrate.write(|w| w.baudrate().variant(config.baudrate)); - // Enable interrupts - r.intenset.write(|w| w.endrx().set().endtx().set()); + // clear errors + let errors = r.errorsrc.read().bits(); + r.errorsrc.write(|w| unsafe { w.bits(errors) }); - // Disable the irq, let the Registration enable it when everything is set up. - irq.disable(); - irq.pend(); + r.events_rxstarted.reset(); + r.events_txstarted.reset(); + r.events_error.reset(); + r.events_endrx.reset(); + r.events_endtx.reset(); + + // Enable interrupts + r.intenclr.write(|w| unsafe { w.bits(!0) }); + r.intenset.write(|w| { + w.endtx().set(); + w.rxstarted().set(); + w.error().set(); + w + }); // Enable UARTE instance apply_workaround_for_enable_anomaly(&r); r.enable.write(|w| w.enable().enabled()); - // BAUDRATE register values are `baudrate * 2^32 / 16000000` - // source: https://devzone.nordicsemi.com/f/nordic-q-a/391/uart-baudrate-register-values - // - // We want to stop RX if line is idle for 2 bytes worth of time - // That is 20 bits (each byte is 1 start bit + 8 data bits + 1 stop bit) - // This gives us the amount of 16M ticks for 20 bits. - let timeout = 0x8000_0000 / (config.baudrate as u32 / 40); + // Configure byte counter. + let timer = Timer::new_counter(timer); + timer.cc(1).write(rx_buffer.len() as u32 * 2); + timer.cc(1).short_compare_clear(); + timer.clear(); + timer.start(); - timer.set_frequency(Frequency::F16MHz); - timer.cc(0).write(timeout); - timer.cc(0).short_compare_clear(); - timer.cc(0).short_compare_stop(); - - let mut ppi_ch1 = Ppi::new_one_to_two( - ppi_ch1.map_into(), - Event::from_reg(&r.events_rxdrdy), - timer.task_clear(), - timer.task_start(), - ); + let mut ppi_ch1 = Ppi::new_one_to_one(ppi_ch1, Event::from_reg(&r.events_rxdrdy), timer.task_count()); ppi_ch1.enable(); - let mut ppi_ch2 = Ppi::new_one_to_one( - ppi_ch2.map_into(), - timer.cc(0).event_compare(), - Task::from_reg(&r.tasks_stoprx), + s.rx_ppi_ch.store(ppi_ch2.number() as u8, Ordering::Relaxed); + let mut ppi_group = PpiGroup::new(ppi_group); + let mut ppi_ch2 = Ppi::new_one_to_two( + ppi_ch2, + Event::from_reg(&r.events_endrx), + Task::from_reg(&r.tasks_startrx), + ppi_group.task_disable_all(), ); - ppi_ch2.enable(); + ppi_ch2.disable(); + ppi_group.add_channel(&ppi_ch2); + + U::Interrupt::pend(); + unsafe { U::Interrupt::enable() }; Self { - inner: PeripheralMutex::new(irq, &mut state.0, move || StateInner { - _peri: peri, - timer, - _ppi_ch1: ppi_ch1, - _ppi_ch2: ppi_ch2, - - rx: RingBuffer::new(rx_buffer), - rx_state: RxState::Idle, - rx_waker: WakerRegistration::new(), - - tx: RingBuffer::new(tx_buffer), - tx_state: TxState::Idle, - tx_waker: WakerRegistration::new(), - }), + _peri: peri, + timer, + _ppi_ch1: ppi_ch1, + _ppi_ch2: ppi_ch2, + _ppi_group: ppi_group, } } + fn pend_irq() { + U::Interrupt::pend() + } + /// Adjust the baud rate to the provided value. pub fn set_baudrate(&mut self, baudrate: Baudrate) { - self.inner.with(|state| { - let r = U::regs(); - - let timeout = 0x8000_0000 / (baudrate as u32 / 40); - state.timer.cc(0).write(timeout); - state.timer.clear(); - - r.baudrate.write(|w| w.baudrate().variant(baudrate)); - }); + let r = U::regs(); + r.baudrate.write(|w| w.baudrate().variant(baudrate)); } -} -impl<'d, U: UarteInstance, T: TimerInstance> embedded_io::Io for BufferedUarte<'d, U, T> { - type Error = core::convert::Infallible; -} + /// Split the UART in reader and writer parts. + /// + /// This allows reading and writing concurrently from independent tasks. + pub fn split<'u>(&'u mut self) -> (BufferedUarteRx<'u, 'd, U, T>, BufferedUarteTx<'u, 'd, U, T>) { + (BufferedUarteRx { inner: self }, BufferedUarteTx { inner: self }) + } -impl<'d, U: UarteInstance, T: TimerInstance> embedded_io::asynch::Read for BufferedUarte<'d, U, T> { - type ReadFuture<'a> = impl Future> - where - Self: 'a; + async fn inner_read(&self, buf: &mut [u8]) -> Result { + let data = self.inner_fill_buf().await?; + let n = data.len().min(buf.len()); + buf[..n].copy_from_slice(&data[..n]); + self.inner_consume(n); + Ok(n) + } - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { + async fn inner_write<'a>(&'a self, buf: &'a [u8]) -> Result { poll_fn(move |cx| { - let mut do_pend = false; - let res = self.inner.with(|state| { - compiler_fence(Ordering::SeqCst); - trace!("poll_read"); + //trace!("poll_write: {:?}", buf.len()); + let s = U::buffered_state(); + let mut tx = unsafe { s.tx_buf.writer() }; - // We have data ready in buffer? Return it. - let data = state.rx.pop_buf(); - if !data.is_empty() { - trace!(" got {:?} {:?}", data.as_ptr() as u32, data.len()); - let len = data.len().min(buf.len()); - buf[..len].copy_from_slice(&data[..len]); - state.rx.pop(len); - do_pend = true; - return Poll::Ready(Ok(len)); - } - - trace!(" empty"); - state.rx_waker.register(cx.waker()); - Poll::Pending - }); - if do_pend { - self.inner.pend(); + let tx_buf = tx.push_slice(); + if tx_buf.is_empty() { + //trace!("poll_write: pending"); + s.tx_waker.register(cx.waker()); + return Poll::Pending; } - res + let n = min(tx_buf.len(), buf.len()); + tx_buf[..n].copy_from_slice(&buf[..n]); + tx.push_done(n); + + //trace!("poll_write: queued {:?}", n); + + compiler_fence(Ordering::SeqCst); + Self::pend_irq(); + + Poll::Ready(Ok(n)) }) + .await + } + + async fn inner_flush<'a>(&'a self) -> Result<(), Error> { + poll_fn(move |cx| { + //trace!("poll_flush"); + let s = U::buffered_state(); + if !s.tx_buf.is_empty() { + //trace!("poll_flush: pending"); + s.tx_waker.register(cx.waker()); + return Poll::Pending; + } + + Poll::Ready(Ok(())) + }) + .await + } + + async fn inner_fill_buf<'a>(&'a self) -> Result<&'a [u8], Error> { + poll_fn(move |cx| { + compiler_fence(Ordering::SeqCst); + //trace!("poll_read"); + + let r = U::regs(); + let s = U::buffered_state(); + + // Read the RXDRDY counter. + T::regs().tasks_capture[0].write(|w| unsafe { w.bits(1) }); + let mut end = T::regs().cc[0].read().bits() as usize; + //trace!(" rxdrdy count = {:?}", end); + + // We've set a compare channel that resets the counter to 0 when it reaches `len*2`. + // However, it's unclear if that's instant, or there's a small window where you can + // still read `len()*2`. + // This could happen if in one clock cycle the counter is updated, and in the next the + // clear takes effect. The docs are very sparse, they just say "Task delays: After TIMER + // is started, the CLEAR, COUNT, and STOP tasks are guaranteed to take effect within one + // clock cycle of the PCLK16M." :shrug: + // So, we wrap the counter ourselves, just in case. + if end > s.rx_buf.len() * 2 { + end = 0 + } + + // This logic mirrors `atomic_ring_buffer::Reader::pop_buf()` + let mut start = s.rx_buf.start.load(Ordering::Relaxed); + let len = s.rx_buf.len(); + if start == end { + //trace!(" empty"); + s.rx_waker.register(cx.waker()); + r.intenset.write(|w| w.rxdrdy().set_bit()); + return Poll::Pending; + } + + if start >= len { + start -= len + } + if end >= len { + end -= len + } + + let n = if end > start { end - start } else { len - start }; + assert!(n != 0); + //trace!(" uarte ringbuf: pop_buf {:?}..{:?}", start, start + n); + + let buf = s.rx_buf.buf.load(Ordering::Relaxed); + Poll::Ready(Ok(unsafe { slice::from_raw_parts(buf.add(start), n) })) + }) + .await + } + + fn inner_consume(&self, amt: usize) { + if amt == 0 { + return; + } + + let s = U::buffered_state(); + let mut rx = unsafe { s.rx_buf.reader() }; + rx.pop_done(amt); + U::regs().intenset.write(|w| w.rxstarted().set()); + } + + /// Pull some bytes from this source into the specified buffer, returning how many bytes were read. + pub async fn read(&mut self, buf: &mut [u8]) -> Result { + self.inner_read(buf).await + } + + /// Return the contents of the internal buffer, filling it with more data from the inner reader if it is empty. + pub async fn fill_buf(&mut self) -> Result<&[u8], Error> { + self.inner_fill_buf().await + } + + /// Tell this buffer that `amt` bytes have been consumed from the buffer, so they should no longer be returned in calls to `fill_buf`. + pub fn consume(&mut self, amt: usize) { + self.inner_consume(amt) + } + + /// Write a buffer into this writer, returning how many bytes were written. + pub async fn write(&mut self, buf: &[u8]) -> Result { + self.inner_write(buf).await + } + + /// Flush this output stream, ensuring that all intermediately buffered contents reach their destination. + pub async fn flush(&mut self) -> Result<(), Error> { + self.inner_flush().await } } -impl<'d, U: UarteInstance, T: TimerInstance> embedded_io::asynch::BufRead for BufferedUarte<'d, U, T> { - type FillBufFuture<'a> = impl Future> - where - Self: 'a; +/// Reader part of the buffered UARTE driver. +pub struct BufferedUarteTx<'u, 'd, U: UarteInstance, T: TimerInstance> { + inner: &'u BufferedUarte<'d, U, T>, +} - fn fill_buf<'a>(&'a mut self) -> Self::FillBufFuture<'a> { - poll_fn(move |cx| { - self.inner.with(|state| { - compiler_fence(Ordering::SeqCst); - trace!("fill_buf"); - - // We have data ready in buffer? Return it. - let buf = state.rx.pop_buf(); - if !buf.is_empty() { - trace!(" got {:?} {:?}", buf.as_ptr() as u32, buf.len()); - let buf: &[u8] = buf; - // Safety: buffer lives as long as uart - let buf: &[u8] = unsafe { core::mem::transmute(buf) }; - return Poll::Ready(Ok(buf)); - } - - trace!(" empty"); - state.rx_waker.register(cx.waker()); - Poll::>::Pending - }) - }) +impl<'u, 'd, U: UarteInstance, T: TimerInstance> BufferedUarteTx<'u, 'd, U, T> { + /// Write a buffer into this writer, returning how many bytes were written. + pub async fn write(&mut self, buf: &[u8]) -> Result { + self.inner.inner_write(buf).await } - fn consume(&mut self, amt: usize) { - let signal = self.inner.with(|state| { - let full = state.rx.is_full(); - state.rx.pop(amt); - full - }); - if signal { - self.inner.pend(); + /// Flush this output stream, ensuring that all intermediately buffered contents reach their destination. + pub async fn flush(&mut self) -> Result<(), Error> { + self.inner.inner_flush().await + } +} + +/// Writer part of the buffered UARTE driver. +pub struct BufferedUarteRx<'u, 'd, U: UarteInstance, T: TimerInstance> { + inner: &'u BufferedUarte<'d, U, T>, +} + +impl<'u, 'd, U: UarteInstance, T: TimerInstance> BufferedUarteRx<'u, 'd, U, T> { + /// Pull some bytes from this source into the specified buffer, returning how many bytes were read. + pub async fn read(&mut self, buf: &mut [u8]) -> Result { + self.inner.inner_read(buf).await + } + + /// Return the contents of the internal buffer, filling it with more data from the inner reader if it is empty. + pub async fn fill_buf(&mut self) -> Result<&[u8], Error> { + self.inner.inner_fill_buf().await + } + + /// Tell this buffer that `amt` bytes have been consumed from the buffer, so they should no longer be returned in calls to `fill_buf`. + pub fn consume(&mut self, amt: usize) { + self.inner.inner_consume(amt) + } +} + +#[cfg(feature = "nightly")] +mod _embedded_io { + use super::*; + + impl embedded_io::Error for Error { + fn kind(&self) -> embedded_io::ErrorKind { + match *self {} + } + } + + impl<'d, U: UarteInstance, T: TimerInstance> embedded_io::Io for BufferedUarte<'d, U, T> { + type Error = Error; + } + + impl<'u, 'd, U: UarteInstance, T: TimerInstance> embedded_io::Io for BufferedUarteRx<'u, 'd, U, T> { + type Error = Error; + } + + impl<'u, 'd, U: UarteInstance, T: TimerInstance> embedded_io::Io for BufferedUarteTx<'u, 'd, U, T> { + type Error = Error; + } + + impl<'d, U: UarteInstance, T: TimerInstance> embedded_io::asynch::Read for BufferedUarte<'d, U, T> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.inner_read(buf).await + } + } + + impl<'u, 'd: 'u, U: UarteInstance, T: TimerInstance> embedded_io::asynch::Read for BufferedUarteRx<'u, 'd, U, T> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.inner.inner_read(buf).await + } + } + + impl<'d, U: UarteInstance, T: TimerInstance> embedded_io::asynch::BufRead for BufferedUarte<'d, U, T> { + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + self.inner_fill_buf().await + } + + fn consume(&mut self, amt: usize) { + self.inner_consume(amt) + } + } + + impl<'u, 'd: 'u, U: UarteInstance, T: TimerInstance> embedded_io::asynch::BufRead for BufferedUarteRx<'u, 'd, U, T> { + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + self.inner.inner_fill_buf().await + } + + fn consume(&mut self, amt: usize) { + self.inner.inner_consume(amt) + } + } + + impl<'d, U: UarteInstance, T: TimerInstance> embedded_io::asynch::Write for BufferedUarte<'d, U, T> { + async fn write(&mut self, buf: &[u8]) -> Result { + self.inner_write(buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.inner_flush().await + } + } + + impl<'u, 'd: 'u, U: UarteInstance, T: TimerInstance> embedded_io::asynch::Write for BufferedUarteTx<'u, 'd, U, T> { + async fn write(&mut self, buf: &[u8]) -> Result { + self.inner.inner_write(buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.inner.inner_flush().await } } } -impl<'d, U: UarteInstance, T: TimerInstance> embedded_io::asynch::Write for BufferedUarte<'d, U, T> { - type WriteFuture<'a> = impl Future> - where - Self: 'a; - - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { - poll_fn(move |cx| { - let res = self.inner.with(|state| { - trace!("poll_write: {:?}", buf.len()); - - let tx_buf = state.tx.push_buf(); - if tx_buf.is_empty() { - trace!("poll_write: pending"); - state.tx_waker.register(cx.waker()); - return Poll::Pending; - } - - let n = min(tx_buf.len(), buf.len()); - tx_buf[..n].copy_from_slice(&buf[..n]); - state.tx.push(n); - - trace!("poll_write: queued {:?}", n); - - compiler_fence(Ordering::SeqCst); - - Poll::Ready(Ok(n)) - }); - - self.inner.pend(); - - res - }) - } - - type FlushFuture<'a> = impl Future> - where - Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - poll_fn(move |cx| { - self.inner.with(|state| { - trace!("poll_flush"); - - if !state.tx.is_empty() { - trace!("poll_flush: pending"); - state.tx_waker.register(cx.waker()); - return Poll::Pending; - } - - Poll::Ready(Ok(())) - }) - }) - } -} - -impl<'a, U: UarteInstance, T: TimerInstance> Drop for StateInner<'a, U, T> { +impl<'a, U: UarteInstance, T: TimerInstance> Drop for BufferedUarte<'a, U, T> { fn drop(&mut self) { - let r = U::regs(); + self._ppi_group.disable_all(); - // TODO this probably deadlocks. do like Uarte instead. + let r = U::regs(); self.timer.stop(); - if let RxState::Receiving = self.rx_state { - r.tasks_stoprx.write(|w| unsafe { w.bits(1) }); - } - if let TxState::Transmitting(_) = self.tx_state { - r.tasks_stoptx.write(|w| unsafe { w.bits(1) }); - } - if let RxState::Receiving = self.rx_state { - low_power_wait_until(|| r.events_endrx.read().bits() == 1); - } - if let TxState::Transmitting(_) = self.tx_state { - low_power_wait_until(|| r.events_endtx.read().bits() == 1); + + r.inten.reset(); + r.events_rxto.reset(); + r.tasks_stoprx.write(|w| unsafe { w.bits(1) }); + r.events_txstopped.reset(); + r.tasks_stoptx.write(|w| unsafe { w.bits(1) }); + + while r.events_txstopped.read().bits() == 0 {} + while r.events_rxto.read().bits() == 0 {} + + r.enable.write(|w| w.enable().disabled()); + + gpio::deconfigure_pin(r.psel.rxd.read().bits()); + gpio::deconfigure_pin(r.psel.txd.read().bits()); + gpio::deconfigure_pin(r.psel.rts.read().bits()); + gpio::deconfigure_pin(r.psel.cts.read().bits()); + + let s = U::buffered_state(); + unsafe { + s.rx_buf.deinit(); + s.tx_buf.deinit(); } } } - -impl<'a, U: UarteInstance, T: TimerInstance> PeripheralState for StateInner<'a, U, T> { - type Interrupt = U::Interrupt; - fn on_interrupt(&mut self) { - trace!("irq: start"); - let r = U::regs(); - - loop { - match self.rx_state { - RxState::Idle => { - trace!(" irq_rx: in state idle"); - - let buf = self.rx.push_buf(); - if !buf.is_empty() { - trace!(" irq_rx: starting {:?}", buf.len()); - self.rx_state = RxState::Receiving; - - // Set up the DMA read - r.rxd.ptr.write(|w| - // The PTR field is a full 32 bits wide and accepts the full range - // of values. - unsafe { w.ptr().bits(buf.as_ptr() as u32) }); - r.rxd.maxcnt.write(|w| - // We're giving it the length of the buffer, so no danger of - // accessing invalid memory. We have verified that the length of the - // buffer fits in an `u8`, so the cast to `u8` is also fine. - // - // The MAXCNT field is at least 8 bits wide and accepts the full - // range of values. - unsafe { w.maxcnt().bits(buf.len() as _) }); - trace!(" irq_rx: buf {:?} {:?}", buf.as_ptr() as u32, buf.len()); - - // Start UARTE Receive transaction - r.tasks_startrx.write(|w| unsafe { w.bits(1) }); - } - break; - } - RxState::Receiving => { - trace!(" irq_rx: in state receiving"); - if r.events_endrx.read().bits() != 0 { - self.timer.stop(); - - let n: usize = r.rxd.amount.read().amount().bits() as usize; - trace!(" irq_rx: endrx {:?}", n); - self.rx.push(n); - - r.events_endrx.reset(); - - self.rx_waker.wake(); - self.rx_state = RxState::Idle; - } else { - break; - } - } - } - } - - loop { - match self.tx_state { - TxState::Idle => { - trace!(" irq_tx: in state Idle"); - let buf = self.tx.pop_buf(); - if !buf.is_empty() { - trace!(" irq_tx: starting {:?}", buf.len()); - self.tx_state = TxState::Transmitting(buf.len()); - - // Set up the DMA write - r.txd.ptr.write(|w| - // The PTR field is a full 32 bits wide and accepts the full range - // of values. - unsafe { w.ptr().bits(buf.as_ptr() as u32) }); - r.txd.maxcnt.write(|w| - // We're giving it the length of the buffer, so no danger of - // accessing invalid memory. We have verified that the length of the - // buffer fits in an `u8`, so the cast to `u8` is also fine. - // - // The MAXCNT field is 8 bits wide and accepts the full range of - // values. - unsafe { w.maxcnt().bits(buf.len() as _) }); - - // Start UARTE Transmit transaction - r.tasks_starttx.write(|w| unsafe { w.bits(1) }); - } - break; - } - TxState::Transmitting(n) => { - trace!(" irq_tx: in state Transmitting"); - if r.events_endtx.read().bits() != 0 { - r.events_endtx.reset(); - - trace!(" irq_tx: endtx {:?}", n); - self.tx.pop(n); - self.tx_waker.wake(); - self.tx_state = TxState::Idle; - } else { - break; - } - } - } - } - trace!("irq: end"); - } -} - -/// Low power blocking wait loop using WFE/SEV. -fn low_power_wait_until(mut condition: impl FnMut() -> bool) { - while !condition() { - // WFE might "eat" an event that would have otherwise woken the executor. - cortex_m::asm::wfe(); - } - // Retrigger an event to be transparent to the executor. - cortex_m::asm::sev(); -} diff --git a/embassy-nrf/src/chips/nrf52805.rs b/embassy-nrf/src/chips/nrf52805.rs index d078fa0ad..8776000c8 100644 --- a/embassy-nrf/src/chips/nrf52805.rs +++ b/embassy-nrf/src/chips/nrf52805.rs @@ -6,6 +6,8 @@ pub const FORCE_COPY_BUFFER_SIZE: usize = 256; pub const FLASH_SIZE: usize = 192 * 1024; +pub const RESET_PIN: u32 = 21; + embassy_hal_common::peripherals! { // RTC RTC0, @@ -108,6 +110,7 @@ embassy_hal_common::peripherals! { P0_18, P0_19, P0_20, + #[cfg(feature="reset-pin-as-gpio")] P0_21, P0_22, P0_23, @@ -131,8 +134,16 @@ impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); impl_spim!(SPI0, SPIM0, SPIM0_SPIS0_SPI0); +impl_spis!(SPI0, SPIS0, SPIM0_SPIS0_SPI0); + impl_twim!(TWI0, TWIM0, TWIM0_TWIS0_TWI0); +impl_twis!(TWI0, TWIS0, TWIM0_TWIS0_TWI0); + +impl_qdec!(QDEC, QDEC, QDEC); + +impl_rng!(RNG, RNG, RNG); + impl_timer!(TIMER0, TIMER0, TIMER0); impl_timer!(TIMER1, TIMER1, TIMER1); impl_timer!(TIMER2, TIMER2, TIMER2); @@ -158,6 +169,7 @@ impl_pin!(P0_17, 0, 17); impl_pin!(P0_18, 0, 18); impl_pin!(P0_19, 0, 19); impl_pin!(P0_20, 0, 20); +#[cfg(feature = "reset-pin-as-gpio")] impl_pin!(P0_21, 0, 21); impl_pin!(P0_22, 0, 22); impl_pin!(P0_23, 0, 23); @@ -193,36 +205,32 @@ impl_ppi_channel!(PPI_CH29, 29 => static); impl_ppi_channel!(PPI_CH30, 30 => static); impl_ppi_channel!(PPI_CH31, 31 => static); -impl_saadc_input!(P0_04, ANALOGINPUT2); -impl_saadc_input!(P0_05, ANALOGINPUT3); +impl_saadc_input!(P0_04, ANALOG_INPUT2); +impl_saadc_input!(P0_05, ANALOG_INPUT3); -pub mod irqs { - use embassy_cortex_m::interrupt::_export::declare; - - use crate::pac::Interrupt as InterruptEnum; - - declare!(POWER_CLOCK); - declare!(RADIO); - declare!(UARTE0_UART0); - declare!(TWIM0_TWIS0_TWI0); - declare!(SPIM0_SPIS0_SPI0); - declare!(GPIOTE); - declare!(SAADC); - declare!(TIMER0); - declare!(TIMER1); - declare!(TIMER2); - declare!(RTC0); - declare!(TEMP); - declare!(RNG); - declare!(ECB); - declare!(CCM_AAR); - declare!(WDT); - declare!(RTC1); - declare!(QDEC); - declare!(SWI0_EGU0); - declare!(SWI1_EGU1); - declare!(SWI2); - declare!(SWI3); - declare!(SWI4); - declare!(SWI5); -} +embassy_hal_common::interrupt_mod!( + POWER_CLOCK, + RADIO, + UARTE0_UART0, + TWIM0_TWIS0_TWI0, + SPIM0_SPIS0_SPI0, + GPIOTE, + SAADC, + TIMER0, + TIMER1, + TIMER2, + RTC0, + TEMP, + RNG, + ECB, + CCM_AAR, + WDT, + RTC1, + QDEC, + SWI0_EGU0, + SWI1_EGU1, + SWI2, + SWI3, + SWI4, + SWI5, +); diff --git a/embassy-nrf/src/chips/nrf52810.rs b/embassy-nrf/src/chips/nrf52810.rs index 3e500098c..5519e8953 100644 --- a/embassy-nrf/src/chips/nrf52810.rs +++ b/embassy-nrf/src/chips/nrf52810.rs @@ -6,6 +6,8 @@ pub const FORCE_COPY_BUFFER_SIZE: usize = 256; pub const FLASH_SIZE: usize = 192 * 1024; +pub const RESET_PIN: u32 = 21; + embassy_hal_common::peripherals! { // RTC RTC0, @@ -111,6 +113,7 @@ embassy_hal_common::peripherals! { P0_18, P0_19, P0_20, + #[cfg(feature="reset-pin-as-gpio")] P0_21, P0_22, P0_23, @@ -137,10 +140,20 @@ impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); impl_spim!(SPI0, SPIM0, SPIM0_SPIS0_SPI0); +impl_spis!(SPI0, SPIS0, SPIM0_SPIS0_SPI0); + impl_twim!(TWI0, TWIM0, TWIM0_TWIS0_TWI0); +impl_twis!(TWI0, TWIS0, TWIM0_TWIS0_TWI0); + impl_pwm!(PWM0, PWM0, PWM0); +impl_pdm!(PDM, PDM, PDM); + +impl_qdec!(QDEC, QDEC, QDEC); + +impl_rng!(RNG, RNG, RNG); + impl_timer!(TIMER0, TIMER0, TIMER0); impl_timer!(TIMER1, TIMER1, TIMER1); impl_timer!(TIMER2, TIMER2, TIMER2); @@ -166,6 +179,7 @@ impl_pin!(P0_17, 0, 17); impl_pin!(P0_18, 0, 18); impl_pin!(P0_19, 0, 19); impl_pin!(P0_20, 0, 20); +#[cfg(feature = "reset-pin-as-gpio")] impl_pin!(P0_21, 0, 21); impl_pin!(P0_22, 0, 22); impl_pin!(P0_23, 0, 23); @@ -211,45 +225,41 @@ impl_ppi_channel!(PPI_CH29, 29 => static); impl_ppi_channel!(PPI_CH30, 30 => static); impl_ppi_channel!(PPI_CH31, 31 => static); -impl_saadc_input!(P0_02, ANALOGINPUT0); -impl_saadc_input!(P0_03, ANALOGINPUT1); -impl_saadc_input!(P0_04, ANALOGINPUT2); -impl_saadc_input!(P0_05, ANALOGINPUT3); -impl_saadc_input!(P0_28, ANALOGINPUT4); -impl_saadc_input!(P0_29, ANALOGINPUT5); -impl_saadc_input!(P0_30, ANALOGINPUT6); -impl_saadc_input!(P0_31, ANALOGINPUT7); +impl_saadc_input!(P0_02, ANALOG_INPUT0); +impl_saadc_input!(P0_03, ANALOG_INPUT1); +impl_saadc_input!(P0_04, ANALOG_INPUT2); +impl_saadc_input!(P0_05, ANALOG_INPUT3); +impl_saadc_input!(P0_28, ANALOG_INPUT4); +impl_saadc_input!(P0_29, ANALOG_INPUT5); +impl_saadc_input!(P0_30, ANALOG_INPUT6); +impl_saadc_input!(P0_31, ANALOG_INPUT7); -pub mod irqs { - use embassy_cortex_m::interrupt::_export::declare; - - use crate::pac::Interrupt as InterruptEnum; - - declare!(POWER_CLOCK); - declare!(RADIO); - declare!(UARTE0_UART0); - declare!(TWIM0_TWIS0_TWI0); - declare!(SPIM0_SPIS0_SPI0); - declare!(GPIOTE); - declare!(SAADC); - declare!(TIMER0); - declare!(TIMER1); - declare!(TIMER2); - declare!(RTC0); - declare!(TEMP); - declare!(RNG); - declare!(ECB); - declare!(CCM_AAR); - declare!(WDT); - declare!(RTC1); - declare!(QDEC); - declare!(COMP); - declare!(SWI0_EGU0); - declare!(SWI1_EGU1); - declare!(SWI2); - declare!(SWI3); - declare!(SWI4); - declare!(SWI5); - declare!(PWM0); - declare!(PDM); -} +embassy_hal_common::interrupt_mod!( + POWER_CLOCK, + RADIO, + UARTE0_UART0, + TWIM0_TWIS0_TWI0, + SPIM0_SPIS0_SPI0, + GPIOTE, + SAADC, + TIMER0, + TIMER1, + TIMER2, + RTC0, + TEMP, + RNG, + ECB, + CCM_AAR, + WDT, + RTC1, + QDEC, + COMP, + SWI0_EGU0, + SWI1_EGU1, + SWI2, + SWI3, + SWI4, + SWI5, + PWM0, + PDM, +); diff --git a/embassy-nrf/src/chips/nrf52811.rs b/embassy-nrf/src/chips/nrf52811.rs index 25c7c0d91..d5367c59a 100644 --- a/embassy-nrf/src/chips/nrf52811.rs +++ b/embassy-nrf/src/chips/nrf52811.rs @@ -6,6 +6,8 @@ pub const FORCE_COPY_BUFFER_SIZE: usize = 256; pub const FLASH_SIZE: usize = 192 * 1024; +pub const RESET_PIN: u32 = 21; + embassy_hal_common::peripherals! { // RTC RTC0, @@ -111,6 +113,7 @@ embassy_hal_common::peripherals! { P0_18, P0_19, P0_20, + #[cfg(feature="reset-pin-as-gpio")] P0_21, P0_22, P0_23, @@ -138,10 +141,21 @@ impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); impl_spim!(TWISPI0, SPIM0, TWIM0_TWIS0_TWI0_SPIM0_SPIS0_SPI0); impl_spim!(SPI1, SPIM1, SPIM1_SPIS1_SPI1); +impl_spis!(TWISPI0, SPIS0, TWIM0_TWIS0_TWI0_SPIM0_SPIS0_SPI0); +impl_spis!(SPI1, SPIS1, SPIM1_SPIS1_SPI1); + impl_twim!(TWISPI0, TWIM0, TWIM0_TWIS0_TWI0_SPIM0_SPIS0_SPI0); +impl_twis!(TWISPI0, TWIS0, TWIM0_TWIS0_TWI0_SPIM0_SPIS0_SPI0); + impl_pwm!(PWM0, PWM0, PWM0); +impl_pdm!(PDM, PDM, PDM); + +impl_qdec!(QDEC, QDEC, QDEC); + +impl_rng!(RNG, RNG, RNG); + impl_timer!(TIMER0, TIMER0, TIMER0); impl_timer!(TIMER1, TIMER1, TIMER1); impl_timer!(TIMER2, TIMER2, TIMER2); @@ -167,6 +181,7 @@ impl_pin!(P0_17, 0, 17); impl_pin!(P0_18, 0, 18); impl_pin!(P0_19, 0, 19); impl_pin!(P0_20, 0, 20); +#[cfg(feature = "reset-pin-as-gpio")] impl_pin!(P0_21, 0, 21); impl_pin!(P0_22, 0, 22); impl_pin!(P0_23, 0, 23); @@ -212,45 +227,41 @@ impl_ppi_channel!(PPI_CH29, 29 => static); impl_ppi_channel!(PPI_CH30, 30 => static); impl_ppi_channel!(PPI_CH31, 31 => static); -impl_saadc_input!(P0_02, ANALOGINPUT0); -impl_saadc_input!(P0_03, ANALOGINPUT1); -impl_saadc_input!(P0_04, ANALOGINPUT2); -impl_saadc_input!(P0_05, ANALOGINPUT3); -impl_saadc_input!(P0_28, ANALOGINPUT4); -impl_saadc_input!(P0_29, ANALOGINPUT5); -impl_saadc_input!(P0_30, ANALOGINPUT6); -impl_saadc_input!(P0_31, ANALOGINPUT7); +impl_saadc_input!(P0_02, ANALOG_INPUT0); +impl_saadc_input!(P0_03, ANALOG_INPUT1); +impl_saadc_input!(P0_04, ANALOG_INPUT2); +impl_saadc_input!(P0_05, ANALOG_INPUT3); +impl_saadc_input!(P0_28, ANALOG_INPUT4); +impl_saadc_input!(P0_29, ANALOG_INPUT5); +impl_saadc_input!(P0_30, ANALOG_INPUT6); +impl_saadc_input!(P0_31, ANALOG_INPUT7); -pub mod irqs { - use embassy_cortex_m::interrupt::_export::declare; - - use crate::pac::Interrupt as InterruptEnum; - - declare!(POWER_CLOCK); - declare!(RADIO); - declare!(UARTE0_UART0); - declare!(TWIM0_TWIS0_TWI0_SPIM0_SPIS0_SPI0); - declare!(SPIM1_SPIS1_SPI1); - declare!(GPIOTE); - declare!(SAADC); - declare!(TIMER0); - declare!(TIMER1); - declare!(TIMER2); - declare!(RTC0); - declare!(TEMP); - declare!(RNG); - declare!(ECB); - declare!(CCM_AAR); - declare!(WDT); - declare!(RTC1); - declare!(QDEC); - declare!(COMP); - declare!(SWI0_EGU0); - declare!(SWI1_EGU1); - declare!(SWI2); - declare!(SWI3); - declare!(SWI4); - declare!(SWI5); - declare!(PWM0); - declare!(PDM); -} +embassy_hal_common::interrupt_mod!( + POWER_CLOCK, + RADIO, + UARTE0_UART0, + TWIM0_TWIS0_TWI0_SPIM0_SPIS0_SPI0, + SPIM1_SPIS1_SPI1, + GPIOTE, + SAADC, + TIMER0, + TIMER1, + TIMER2, + RTC0, + TEMP, + RNG, + ECB, + CCM_AAR, + WDT, + RTC1, + QDEC, + COMP, + SWI0_EGU0, + SWI1_EGU1, + SWI2, + SWI3, + SWI4, + SWI5, + PWM0, + PDM, +); diff --git a/embassy-nrf/src/chips/nrf52820.rs b/embassy-nrf/src/chips/nrf52820.rs index dba033b0f..785170447 100644 --- a/embassy-nrf/src/chips/nrf52820.rs +++ b/embassy-nrf/src/chips/nrf52820.rs @@ -6,6 +6,8 @@ pub const FORCE_COPY_BUFFER_SIZE: usize = 512; pub const FLASH_SIZE: usize = 256 * 1024; +pub const RESET_PIN: u32 = 18; + embassy_hal_common::peripherals! { // USB USBD, @@ -106,6 +108,7 @@ embassy_hal_common::peripherals! { P0_15, P0_16, P0_17, + #[cfg(feature="reset-pin-as-gpio")] P0_18, P0_19, P0_20, @@ -136,14 +139,24 @@ impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); impl_spim!(TWISPI0, SPIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); impl_spim!(TWISPI1, SPIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); +impl_spis!(TWISPI0, SPIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); +impl_spis!(TWISPI1, SPIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); + impl_twim!(TWISPI0, TWIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); impl_twim!(TWISPI1, TWIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); +impl_twis!(TWISPI0, TWIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); +impl_twis!(TWISPI1, TWIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); + impl_timer!(TIMER0, TIMER0, TIMER0); impl_timer!(TIMER1, TIMER1, TIMER1); impl_timer!(TIMER2, TIMER2, TIMER2); impl_timer!(TIMER3, TIMER3, TIMER3, extended); +impl_qdec!(QDEC, QDEC, QDEC); + +impl_rng!(RNG, RNG, RNG); + impl_pin!(P0_00, 0, 0); impl_pin!(P0_01, 0, 1); impl_pin!(P0_02, 0, 2); @@ -162,6 +175,7 @@ impl_pin!(P0_14, 0, 14); impl_pin!(P0_15, 0, 15); impl_pin!(P0_16, 0, 16); impl_pin!(P0_17, 0, 17); +#[cfg(feature = "reset-pin-as-gpio")] impl_pin!(P0_18, 0, 18); impl_pin!(P0_19, 0, 19); impl_pin!(P0_20, 0, 20); @@ -210,35 +224,31 @@ impl_ppi_channel!(PPI_CH29, 29 => static); impl_ppi_channel!(PPI_CH30, 30 => static); impl_ppi_channel!(PPI_CH31, 31 => static); -pub mod irqs { - use embassy_cortex_m::interrupt::_export::declare; - - use crate::pac::Interrupt as InterruptEnum; - - declare!(POWER_CLOCK); - declare!(RADIO); - declare!(UARTE0_UART0); - declare!(SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); - declare!(SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); - declare!(GPIOTE); - declare!(TIMER0); - declare!(TIMER1); - declare!(TIMER2); - declare!(RTC0); - declare!(TEMP); - declare!(RNG); - declare!(ECB); - declare!(CCM_AAR); - declare!(WDT); - declare!(RTC1); - declare!(QDEC); - declare!(COMP); - declare!(SWI0_EGU0); - declare!(SWI1_EGU1); - declare!(SWI2_EGU2); - declare!(SWI3_EGU3); - declare!(SWI4_EGU4); - declare!(SWI5_EGU5); - declare!(TIMER3); - declare!(USBD); -} +embassy_hal_common::interrupt_mod!( + POWER_CLOCK, + RADIO, + UARTE0_UART0, + SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0, + SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1, + GPIOTE, + TIMER0, + TIMER1, + TIMER2, + RTC0, + TEMP, + RNG, + ECB, + CCM_AAR, + WDT, + RTC1, + QDEC, + COMP, + SWI0_EGU0, + SWI1_EGU1, + SWI2_EGU2, + SWI3_EGU3, + SWI4_EGU4, + SWI5_EGU5, + TIMER3, + USBD, +); diff --git a/embassy-nrf/src/chips/nrf52832.rs b/embassy-nrf/src/chips/nrf52832.rs index 2c6276d4a..b77564a5c 100644 --- a/embassy-nrf/src/chips/nrf52832.rs +++ b/embassy-nrf/src/chips/nrf52832.rs @@ -10,6 +10,8 @@ pub const FORCE_COPY_BUFFER_SIZE: usize = 255; // nrf52832xxAB = 256kb pub const FLASH_SIZE: usize = 512 * 1024; +pub const RESET_PIN: u32 = 21; + embassy_hal_common::peripherals! { // RTC RTC0, @@ -109,7 +111,9 @@ embassy_hal_common::peripherals! { P0_06, P0_07, P0_08, + #[cfg(feature = "nfc-pins-as-gpio")] P0_09, + #[cfg(feature = "nfc-pins-as-gpio")] P0_10, P0_11, P0_12, @@ -121,6 +125,7 @@ embassy_hal_common::peripherals! { P0_18, P0_19, P0_20, + #[cfg(feature="reset-pin-as-gpio")] P0_21, P0_22, P0_23, @@ -139,6 +144,9 @@ embassy_hal_common::peripherals! { // QDEC QDEC, + // I2S + I2S, + // PDM PDM, } @@ -149,13 +157,26 @@ impl_spim!(TWISPI0, SPIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); impl_spim!(TWISPI1, SPIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); impl_spim!(SPI2, SPIM2, SPIM2_SPIS2_SPI2); +impl_spis!(TWISPI0, SPIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); +impl_spis!(TWISPI1, SPIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); +impl_spis!(SPI2, SPIS2, SPIM2_SPIS2_SPI2); + impl_twim!(TWISPI0, TWIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); impl_twim!(TWISPI1, TWIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); +impl_twis!(TWISPI0, TWIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); +impl_twis!(TWISPI1, TWIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); + impl_pwm!(PWM0, PWM0, PWM0); impl_pwm!(PWM1, PWM1, PWM1); impl_pwm!(PWM2, PWM2, PWM2); +impl_pdm!(PDM, PDM, PDM); + +impl_qdec!(QDEC, QDEC, QDEC); + +impl_rng!(RNG, RNG, RNG); + impl_timer!(TIMER0, TIMER0, TIMER0); impl_timer!(TIMER1, TIMER1, TIMER1); impl_timer!(TIMER2, TIMER2, TIMER2); @@ -171,7 +192,9 @@ impl_pin!(P0_05, 0, 5); impl_pin!(P0_06, 0, 6); impl_pin!(P0_07, 0, 7); impl_pin!(P0_08, 0, 8); +#[cfg(feature = "nfc-pins-as-gpio")] impl_pin!(P0_09, 0, 9); +#[cfg(feature = "nfc-pins-as-gpio")] impl_pin!(P0_10, 0, 10); impl_pin!(P0_11, 0, 11); impl_pin!(P0_12, 0, 12); @@ -183,6 +206,7 @@ impl_pin!(P0_17, 0, 17); impl_pin!(P0_18, 0, 18); impl_pin!(P0_19, 0, 19); impl_pin!(P0_20, 0, 20); +#[cfg(feature = "reset-pin-as-gpio")] impl_pin!(P0_21, 0, 21); impl_pin!(P0_22, 0, 22); impl_pin!(P0_23, 0, 23); @@ -228,55 +252,53 @@ impl_ppi_channel!(PPI_CH29, 29 => static); impl_ppi_channel!(PPI_CH30, 30 => static); impl_ppi_channel!(PPI_CH31, 31 => static); -impl_saadc_input!(P0_02, ANALOGINPUT0); -impl_saadc_input!(P0_03, ANALOGINPUT1); -impl_saadc_input!(P0_04, ANALOGINPUT2); -impl_saadc_input!(P0_05, ANALOGINPUT3); -impl_saadc_input!(P0_28, ANALOGINPUT4); -impl_saadc_input!(P0_29, ANALOGINPUT5); -impl_saadc_input!(P0_30, ANALOGINPUT6); -impl_saadc_input!(P0_31, ANALOGINPUT7); +impl_saadc_input!(P0_02, ANALOG_INPUT0); +impl_saadc_input!(P0_03, ANALOG_INPUT1); +impl_saadc_input!(P0_04, ANALOG_INPUT2); +impl_saadc_input!(P0_05, ANALOG_INPUT3); +impl_saadc_input!(P0_28, ANALOG_INPUT4); +impl_saadc_input!(P0_29, ANALOG_INPUT5); +impl_saadc_input!(P0_30, ANALOG_INPUT6); +impl_saadc_input!(P0_31, ANALOG_INPUT7); -pub mod irqs { - use embassy_cortex_m::interrupt::_export::declare; +impl_i2s!(I2S, I2S, I2S); - use crate::pac::Interrupt as InterruptEnum; - - declare!(POWER_CLOCK); - declare!(RADIO); - declare!(UARTE0_UART0); - declare!(SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); - declare!(SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); - declare!(NFCT); - declare!(GPIOTE); - declare!(SAADC); - declare!(TIMER0); - declare!(TIMER1); - declare!(TIMER2); - declare!(RTC0); - declare!(TEMP); - declare!(RNG); - declare!(ECB); - declare!(CCM_AAR); - declare!(WDT); - declare!(RTC1); - declare!(QDEC); - declare!(COMP_LPCOMP); - declare!(SWI0_EGU0); - declare!(SWI1_EGU1); - declare!(SWI2_EGU2); - declare!(SWI3_EGU3); - declare!(SWI4_EGU4); - declare!(SWI5_EGU5); - declare!(TIMER3); - declare!(TIMER4); - declare!(PWM0); - declare!(PDM); - declare!(MWU); - declare!(PWM1); - declare!(PWM2); - declare!(SPIM2_SPIS2_SPI2); - declare!(RTC2); - declare!(I2S); - declare!(FPU); -} +embassy_hal_common::interrupt_mod!( + POWER_CLOCK, + RADIO, + UARTE0_UART0, + SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0, + SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1, + NFCT, + GPIOTE, + SAADC, + TIMER0, + TIMER1, + TIMER2, + RTC0, + TEMP, + RNG, + ECB, + CCM_AAR, + WDT, + RTC1, + QDEC, + COMP_LPCOMP, + SWI0_EGU0, + SWI1_EGU1, + SWI2_EGU2, + SWI3_EGU3, + SWI4_EGU4, + SWI5_EGU5, + TIMER3, + TIMER4, + PWM0, + PDM, + MWU, + PWM1, + PWM2, + SPIM2_SPIS2_SPI2, + RTC2, + FPU, + I2S, +); diff --git a/embassy-nrf/src/chips/nrf52833.rs b/embassy-nrf/src/chips/nrf52833.rs index 3b33907d2..bff7f4ebb 100644 --- a/embassy-nrf/src/chips/nrf52833.rs +++ b/embassy-nrf/src/chips/nrf52833.rs @@ -6,6 +6,8 @@ pub const FORCE_COPY_BUFFER_SIZE: usize = 512; pub const FLASH_SIZE: usize = 512 * 1024; +pub const RESET_PIN: u32 = 18; + embassy_hal_common::peripherals! { // USB USBD, @@ -111,7 +113,9 @@ embassy_hal_common::peripherals! { P0_06, P0_07, P0_08, + #[cfg(feature = "nfc-pins-as-gpio")] P0_09, + #[cfg(feature = "nfc-pins-as-gpio")] P0_10, P0_11, P0_12, @@ -120,6 +124,7 @@ embassy_hal_common::peripherals! { P0_15, P0_16, P0_17, + #[cfg(feature="reset-pin-as-gpio")] P0_18, P0_19, P0_20, @@ -161,6 +166,9 @@ embassy_hal_common::peripherals! { // PDM PDM, + + // I2S + I2S, } #[cfg(feature = "nightly")] @@ -174,14 +182,27 @@ impl_spim!(TWISPI1, SPIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); impl_spim!(SPI2, SPIM2, SPIM2_SPIS2_SPI2); impl_spim!(SPI3, SPIM3, SPIM3); +impl_spis!(TWISPI0, SPIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); +impl_spis!(TWISPI1, SPIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); +impl_spis!(SPI2, SPIS2, SPIM2_SPIS2_SPI2); + impl_twim!(TWISPI0, TWIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); impl_twim!(TWISPI1, TWIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); +impl_twis!(TWISPI0, TWIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); +impl_twis!(TWISPI1, TWIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); + impl_pwm!(PWM0, PWM0, PWM0); impl_pwm!(PWM1, PWM1, PWM1); impl_pwm!(PWM2, PWM2, PWM2); impl_pwm!(PWM3, PWM3, PWM3); +impl_pdm!(PDM, PDM, PDM); + +impl_qdec!(QDEC, QDEC, QDEC); + +impl_rng!(RNG, RNG, RNG); + impl_timer!(TIMER0, TIMER0, TIMER0); impl_timer!(TIMER1, TIMER1, TIMER1); impl_timer!(TIMER2, TIMER2, TIMER2); @@ -197,7 +218,9 @@ impl_pin!(P0_05, 0, 5); impl_pin!(P0_06, 0, 6); impl_pin!(P0_07, 0, 7); impl_pin!(P0_08, 0, 8); +#[cfg(feature = "nfc-pins-as-gpio")] impl_pin!(P0_09, 0, 9); +#[cfg(feature = "nfc-pins-as-gpio")] impl_pin!(P0_10, 0, 10); impl_pin!(P0_11, 0, 11); impl_pin!(P0_12, 0, 12); @@ -206,6 +229,7 @@ impl_pin!(P0_14, 0, 14); impl_pin!(P0_15, 0, 15); impl_pin!(P0_16, 0, 16); impl_pin!(P0_17, 0, 17); +#[cfg(feature = "reset-pin-as-gpio")] impl_pin!(P0_18, 0, 18); impl_pin!(P0_19, 0, 19); impl_pin!(P0_20, 0, 20); @@ -271,59 +295,57 @@ impl_ppi_channel!(PPI_CH29, 29 => static); impl_ppi_channel!(PPI_CH30, 30 => static); impl_ppi_channel!(PPI_CH31, 31 => static); -impl_saadc_input!(P0_02, ANALOGINPUT0); -impl_saadc_input!(P0_03, ANALOGINPUT1); -impl_saadc_input!(P0_04, ANALOGINPUT2); -impl_saadc_input!(P0_05, ANALOGINPUT3); -impl_saadc_input!(P0_28, ANALOGINPUT4); -impl_saadc_input!(P0_29, ANALOGINPUT5); -impl_saadc_input!(P0_30, ANALOGINPUT6); -impl_saadc_input!(P0_31, ANALOGINPUT7); +impl_saadc_input!(P0_02, ANALOG_INPUT0); +impl_saadc_input!(P0_03, ANALOG_INPUT1); +impl_saadc_input!(P0_04, ANALOG_INPUT2); +impl_saadc_input!(P0_05, ANALOG_INPUT3); +impl_saadc_input!(P0_28, ANALOG_INPUT4); +impl_saadc_input!(P0_29, ANALOG_INPUT5); +impl_saadc_input!(P0_30, ANALOG_INPUT6); +impl_saadc_input!(P0_31, ANALOG_INPUT7); -pub mod irqs { - use embassy_cortex_m::interrupt::_export::declare; +impl_i2s!(I2S, I2S, I2S); - use crate::pac::Interrupt as InterruptEnum; - - declare!(POWER_CLOCK); - declare!(RADIO); - declare!(UARTE0_UART0); - declare!(SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); - declare!(SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); - declare!(NFCT); - declare!(GPIOTE); - declare!(SAADC); - declare!(TIMER0); - declare!(TIMER1); - declare!(TIMER2); - declare!(RTC0); - declare!(TEMP); - declare!(RNG); - declare!(ECB); - declare!(CCM_AAR); - declare!(WDT); - declare!(RTC1); - declare!(QDEC); - declare!(COMP_LPCOMP); - declare!(SWI0_EGU0); - declare!(SWI1_EGU1); - declare!(SWI2_EGU2); - declare!(SWI3_EGU3); - declare!(SWI4_EGU4); - declare!(SWI5_EGU5); - declare!(TIMER3); - declare!(TIMER4); - declare!(PWM0); - declare!(PDM); - declare!(MWU); - declare!(PWM1); - declare!(PWM2); - declare!(SPIM2_SPIS2_SPI2); - declare!(RTC2); - declare!(I2S); - declare!(FPU); - declare!(USBD); - declare!(UARTE1); - declare!(PWM3); - declare!(SPIM3); -} +embassy_hal_common::interrupt_mod!( + POWER_CLOCK, + RADIO, + UARTE0_UART0, + SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0, + SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1, + NFCT, + GPIOTE, + SAADC, + TIMER0, + TIMER1, + TIMER2, + RTC0, + TEMP, + RNG, + ECB, + CCM_AAR, + WDT, + RTC1, + QDEC, + COMP_LPCOMP, + SWI0_EGU0, + SWI1_EGU1, + SWI2_EGU2, + SWI3_EGU3, + SWI4_EGU4, + SWI5_EGU5, + TIMER3, + TIMER4, + PWM0, + PDM, + MWU, + PWM1, + PWM2, + SPIM2_SPIS2_SPI2, + RTC2, + FPU, + USBD, + UARTE1, + PWM3, + SPIM3, + I2S, +); diff --git a/embassy-nrf/src/chips/nrf52840.rs b/embassy-nrf/src/chips/nrf52840.rs index ae59f8b25..9b0050823 100644 --- a/embassy-nrf/src/chips/nrf52840.rs +++ b/embassy-nrf/src/chips/nrf52840.rs @@ -6,6 +6,8 @@ pub const FORCE_COPY_BUFFER_SIZE: usize = 512; pub const FLASH_SIZE: usize = 1024 * 1024; +pub const RESET_PIN: u32 = 18; + embassy_hal_common::peripherals! { // USB USBD, @@ -117,7 +119,9 @@ embassy_hal_common::peripherals! { P0_06, P0_07, P0_08, + #[cfg(feature = "nfc-pins-as-gpio")] P0_09, + #[cfg(feature = "nfc-pins-as-gpio")] P0_10, P0_11, P0_12, @@ -126,6 +130,7 @@ embassy_hal_common::peripherals! { P0_15, P0_16, P0_17, + #[cfg(feature="reset-pin-as-gpio")] P0_18, P0_19, P0_20, @@ -164,6 +169,9 @@ embassy_hal_common::peripherals! { // PDM PDM, + + // I2S + I2S, } #[cfg(feature = "nightly")] @@ -177,9 +185,16 @@ impl_spim!(TWISPI1, SPIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); impl_spim!(SPI2, SPIM2, SPIM2_SPIS2_SPI2); impl_spim!(SPI3, SPIM3, SPIM3); +impl_spis!(TWISPI0, SPIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); +impl_spis!(TWISPI1, SPIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); +impl_spis!(SPI2, SPIS2, SPIM2_SPIS2_SPI2); + impl_twim!(TWISPI0, TWIM0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); impl_twim!(TWISPI1, TWIM1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); +impl_twis!(TWISPI0, TWIS0, SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); +impl_twis!(TWISPI1, TWIS1, SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); + impl_pwm!(PWM0, PWM0, PWM0); impl_pwm!(PWM1, PWM1, PWM1); impl_pwm!(PWM2, PWM2, PWM2); @@ -193,6 +208,12 @@ impl_timer!(TIMER4, TIMER4, TIMER4, extended); impl_qspi!(QSPI, QSPI, QSPI); +impl_pdm!(PDM, PDM, PDM); + +impl_qdec!(QDEC, QDEC, QDEC); + +impl_rng!(RNG, RNG, RNG); + impl_pin!(P0_00, 0, 0); impl_pin!(P0_01, 0, 1); impl_pin!(P0_02, 0, 2); @@ -202,7 +223,9 @@ impl_pin!(P0_05, 0, 5); impl_pin!(P0_06, 0, 6); impl_pin!(P0_07, 0, 7); impl_pin!(P0_08, 0, 8); +#[cfg(feature = "nfc-pins-as-gpio")] impl_pin!(P0_09, 0, 9); +#[cfg(feature = "nfc-pins-as-gpio")] impl_pin!(P0_10, 0, 10); impl_pin!(P0_11, 0, 11); impl_pin!(P0_12, 0, 12); @@ -211,6 +234,7 @@ impl_pin!(P0_14, 0, 14); impl_pin!(P0_15, 0, 15); impl_pin!(P0_16, 0, 16); impl_pin!(P0_17, 0, 17); +#[cfg(feature = "reset-pin-as-gpio")] impl_pin!(P0_18, 0, 18); impl_pin!(P0_19, 0, 19); impl_pin!(P0_20, 0, 20); @@ -276,61 +300,59 @@ impl_ppi_channel!(PPI_CH29, 29 => static); impl_ppi_channel!(PPI_CH30, 30 => static); impl_ppi_channel!(PPI_CH31, 31 => static); -impl_saadc_input!(P0_02, ANALOGINPUT0); -impl_saadc_input!(P0_03, ANALOGINPUT1); -impl_saadc_input!(P0_04, ANALOGINPUT2); -impl_saadc_input!(P0_05, ANALOGINPUT3); -impl_saadc_input!(P0_28, ANALOGINPUT4); -impl_saadc_input!(P0_29, ANALOGINPUT5); -impl_saadc_input!(P0_30, ANALOGINPUT6); -impl_saadc_input!(P0_31, ANALOGINPUT7); +impl_saadc_input!(P0_02, ANALOG_INPUT0); +impl_saadc_input!(P0_03, ANALOG_INPUT1); +impl_saadc_input!(P0_04, ANALOG_INPUT2); +impl_saadc_input!(P0_05, ANALOG_INPUT3); +impl_saadc_input!(P0_28, ANALOG_INPUT4); +impl_saadc_input!(P0_29, ANALOG_INPUT5); +impl_saadc_input!(P0_30, ANALOG_INPUT6); +impl_saadc_input!(P0_31, ANALOG_INPUT7); -pub mod irqs { - use embassy_cortex_m::interrupt::_export::declare; +impl_i2s!(I2S, I2S, I2S); - use crate::pac::Interrupt as InterruptEnum; - - declare!(POWER_CLOCK); - declare!(RADIO); - declare!(UARTE0_UART0); - declare!(SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); - declare!(SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1); - declare!(NFCT); - declare!(GPIOTE); - declare!(SAADC); - declare!(TIMER0); - declare!(TIMER1); - declare!(TIMER2); - declare!(RTC0); - declare!(TEMP); - declare!(RNG); - declare!(ECB); - declare!(CCM_AAR); - declare!(WDT); - declare!(RTC1); - declare!(QDEC); - declare!(COMP_LPCOMP); - declare!(SWI0_EGU0); - declare!(SWI1_EGU1); - declare!(SWI2_EGU2); - declare!(SWI3_EGU3); - declare!(SWI4_EGU4); - declare!(SWI5_EGU5); - declare!(TIMER3); - declare!(TIMER4); - declare!(PWM0); - declare!(PDM); - declare!(MWU); - declare!(PWM1); - declare!(PWM2); - declare!(SPIM2_SPIS2_SPI2); - declare!(RTC2); - declare!(I2S); - declare!(FPU); - declare!(USBD); - declare!(UARTE1); - declare!(QSPI); - declare!(CRYPTOCELL); - declare!(PWM3); - declare!(SPIM3); -} +embassy_hal_common::interrupt_mod!( + POWER_CLOCK, + RADIO, + UARTE0_UART0, + SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0, + SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1, + NFCT, + GPIOTE, + SAADC, + TIMER0, + TIMER1, + TIMER2, + RTC0, + TEMP, + RNG, + ECB, + CCM_AAR, + WDT, + RTC1, + QDEC, + COMP_LPCOMP, + SWI0_EGU0, + SWI1_EGU1, + SWI2_EGU2, + SWI3_EGU3, + SWI4_EGU4, + SWI5_EGU5, + TIMER3, + TIMER4, + PWM0, + PDM, + MWU, + PWM1, + PWM2, + SPIM2_SPIS2_SPI2, + RTC2, + FPU, + USBD, + UARTE1, + QSPI, + CRYPTOCELL, + PWM3, + SPIM3, + I2S, +); diff --git a/embassy-nrf/src/chips/nrf5340_app.rs b/embassy-nrf/src/chips/nrf5340_app.rs index edf800ef3..410ae921c 100644 --- a/embassy-nrf/src/chips/nrf5340_app.rs +++ b/embassy-nrf/src/chips/nrf5340_app.rs @@ -1,9 +1,12 @@ +/// Peripheral Access Crate #[allow(unused_imports)] #[rustfmt::skip] pub mod pac { // The nRF5340 has a secure and non-secure (NS) mode. // To avoid cfg spam, we remove _ns or _s suffixes here. + pub use nrf5340_app_pac::NVIC_PRIO_BITS; + #[doc(no_inline)] pub use nrf5340_app_pac::{ interrupt, @@ -33,10 +36,10 @@ pub mod pac { nvmc_ns as nvmc, oscillators_ns as oscillators, p0_ns as p0, - pdm0_ns as pdm0, + pdm0_ns as pdm, power_ns as power, pwm0_ns as pwm0, - qdec0_ns as qdec0, + qdec0_ns as qdec, qspi_ns as qspi, regulators_ns as regulators, reset_ns as reset, @@ -213,6 +216,8 @@ pub mod pac { pub const EASY_DMA_SIZE: usize = (1 << 16) - 1; pub const FORCE_COPY_BUFFER_SIZE: usize = 1024; +pub const FLASH_SIZE: usize = 1024 * 1024; + embassy_hal_common::peripherals! { // USB USBD, @@ -224,11 +229,14 @@ embassy_hal_common::peripherals! { // WDT WDT, + // NVMC + NVMC, + // UARTE, TWI & SPI - UARTETWISPI0, - UARTETWISPI1, - UARTETWISPI2, - UARTETWISPI3, + SERIAL0, + SERIAL1, + SERIAL2, + SERIAL3, // SAADC SAADC, @@ -244,6 +252,16 @@ embassy_hal_common::peripherals! { TIMER1, TIMER2, + // QSPI + QSPI, + + // PDM + PDM0, + + // QDEC + QDEC0, + QDEC1, + // GPIOTE GPIOTE_CH0, GPIOTE_CH1, @@ -298,7 +316,9 @@ embassy_hal_common::peripherals! { // GPIO port 0 P0_00, P0_01, + #[cfg(feature = "nfc-pins-as-gpio")] P0_02, + #[cfg(feature = "nfc-pins-as-gpio")] P0_03, P0_04, P0_05, @@ -351,20 +371,30 @@ embassy_hal_common::peripherals! { #[cfg(feature = "nightly")] impl_usb!(USBD, USBD, USBD); -impl_uarte!(UARTETWISPI0, UARTE0, SERIAL0); -impl_uarte!(UARTETWISPI1, UARTE1, SERIAL1); -impl_uarte!(UARTETWISPI2, UARTE2, SERIAL2); -impl_uarte!(UARTETWISPI3, UARTE3, SERIAL3); +impl_uarte!(SERIAL0, UARTE0, SERIAL0); +impl_uarte!(SERIAL1, UARTE1, SERIAL1); +impl_uarte!(SERIAL2, UARTE2, SERIAL2); +impl_uarte!(SERIAL3, UARTE3, SERIAL3); -impl_spim!(UARTETWISPI0, SPIM0, SERIAL0); -impl_spim!(UARTETWISPI1, SPIM1, SERIAL1); -impl_spim!(UARTETWISPI2, SPIM2, SERIAL2); -impl_spim!(UARTETWISPI3, SPIM3, SERIAL3); +impl_spim!(SERIAL0, SPIM0, SERIAL0); +impl_spim!(SERIAL1, SPIM1, SERIAL1); +impl_spim!(SERIAL2, SPIM2, SERIAL2); +impl_spim!(SERIAL3, SPIM3, SERIAL3); -impl_twim!(UARTETWISPI0, TWIM0, SERIAL0); -impl_twim!(UARTETWISPI1, TWIM1, SERIAL1); -impl_twim!(UARTETWISPI2, TWIM2, SERIAL2); -impl_twim!(UARTETWISPI3, TWIM3, SERIAL3); +impl_spis!(SERIAL0, SPIS0, SERIAL0); +impl_spis!(SERIAL1, SPIS1, SERIAL1); +impl_spis!(SERIAL2, SPIS2, SERIAL2); +impl_spis!(SERIAL3, SPIS3, SERIAL3); + +impl_twim!(SERIAL0, TWIM0, SERIAL0); +impl_twim!(SERIAL1, TWIM1, SERIAL1); +impl_twim!(SERIAL2, TWIM2, SERIAL2); +impl_twim!(SERIAL3, TWIM3, SERIAL3); + +impl_twis!(SERIAL0, TWIS0, SERIAL0); +impl_twis!(SERIAL1, TWIS1, SERIAL1); +impl_twis!(SERIAL2, TWIS2, SERIAL2); +impl_twis!(SERIAL3, TWIS3, SERIAL3); impl_pwm!(PWM0, PWM0, PWM0); impl_pwm!(PWM1, PWM1, PWM1); @@ -375,9 +405,18 @@ impl_timer!(TIMER0, TIMER0, TIMER0); impl_timer!(TIMER1, TIMER1, TIMER1); impl_timer!(TIMER2, TIMER2, TIMER2); +impl_qspi!(QSPI, QSPI, QSPI); + +impl_pdm!(PDM0, PDM0, PDM0); + +impl_qdec!(QDEC0, QDEC0, QDEC0); +impl_qdec!(QDEC1, QDEC1, QDEC1); + impl_pin!(P0_00, 0, 0); impl_pin!(P0_01, 0, 1); +#[cfg(feature = "nfc-pins-as-gpio")] impl_pin!(P0_02, 0, 2); +#[cfg(feature = "nfc-pins-as-gpio")] impl_pin!(P0_03, 0, 3); impl_pin!(P0_04, 0, 4); impl_pin!(P0_05, 0, 5); @@ -458,59 +497,55 @@ impl_ppi_channel!(PPI_CH29, 29 => configurable); impl_ppi_channel!(PPI_CH30, 30 => configurable); impl_ppi_channel!(PPI_CH31, 31 => configurable); -impl_saadc_input!(P0_13, ANALOGINPUT0); -impl_saadc_input!(P0_14, ANALOGINPUT1); -impl_saadc_input!(P0_15, ANALOGINPUT2); -impl_saadc_input!(P0_16, ANALOGINPUT3); -impl_saadc_input!(P0_17, ANALOGINPUT4); -impl_saadc_input!(P0_18, ANALOGINPUT5); -impl_saadc_input!(P0_19, ANALOGINPUT6); -impl_saadc_input!(P0_20, ANALOGINPUT7); +impl_saadc_input!(P0_13, ANALOG_INPUT0); +impl_saadc_input!(P0_14, ANALOG_INPUT1); +impl_saadc_input!(P0_15, ANALOG_INPUT2); +impl_saadc_input!(P0_16, ANALOG_INPUT3); +impl_saadc_input!(P0_17, ANALOG_INPUT4); +impl_saadc_input!(P0_18, ANALOG_INPUT5); +impl_saadc_input!(P0_19, ANALOG_INPUT6); +impl_saadc_input!(P0_20, ANALOG_INPUT7); -pub mod irqs { - use embassy_cortex_m::interrupt::_export::declare; - - use crate::pac::Interrupt as InterruptEnum; - - declare!(FPU); - declare!(CACHE); - declare!(SPU); - declare!(CLOCK_POWER); - declare!(SERIAL0); - declare!(SERIAL1); - declare!(SPIM4); - declare!(SERIAL2); - declare!(SERIAL3); - declare!(GPIOTE0); - declare!(SAADC); - declare!(TIMER0); - declare!(TIMER1); - declare!(TIMER2); - declare!(RTC0); - declare!(RTC1); - declare!(WDT0); - declare!(WDT1); - declare!(COMP_LPCOMP); - declare!(EGU0); - declare!(EGU1); - declare!(EGU2); - declare!(EGU3); - declare!(EGU4); - declare!(EGU5); - declare!(PWM0); - declare!(PWM1); - declare!(PWM2); - declare!(PWM3); - declare!(PDM0); - declare!(I2S0); - declare!(IPC); - declare!(QSPI); - declare!(NFCT); - declare!(GPIOTE1); - declare!(QDEC0); - declare!(QDEC1); - declare!(USBD); - declare!(USBREGULATOR); - declare!(KMU); - declare!(CRYPTOCELL); -} +embassy_hal_common::interrupt_mod!( + FPU, + CACHE, + SPU, + CLOCK_POWER, + SERIAL0, + SERIAL1, + SPIM4, + SERIAL2, + SERIAL3, + GPIOTE0, + SAADC, + TIMER0, + TIMER1, + TIMER2, + RTC0, + RTC1, + WDT0, + WDT1, + COMP_LPCOMP, + EGU0, + EGU1, + EGU2, + EGU3, + EGU4, + EGU5, + PWM0, + PWM1, + PWM2, + PWM3, + PDM0, + I2S0, + IPC, + QSPI, + NFCT, + GPIOTE1, + QDEC0, + QDEC1, + USBD, + USBREGULATOR, + KMU, + CRYPTOCELL, +); diff --git a/embassy-nrf/src/chips/nrf5340_net.rs b/embassy-nrf/src/chips/nrf5340_net.rs index ae136e09d..6ac783085 100644 --- a/embassy-nrf/src/chips/nrf5340_net.rs +++ b/embassy-nrf/src/chips/nrf5340_net.rs @@ -1,9 +1,12 @@ +/// Peripheral Access Crate #[allow(unused_imports)] #[rustfmt::skip] pub mod pac { // The nRF5340 has a secure and non-secure (NS) mode. // To avoid cfg spam, we remove _ns or _s suffixes here. + pub use nrf5340_net_pac::NVIC_PRIO_BITS; + #[doc(no_inline)] pub use nrf5340_net_pac::{ interrupt, @@ -104,6 +107,8 @@ pub mod pac { pub const EASY_DMA_SIZE: usize = (1 << 16) - 1; pub const FORCE_COPY_BUFFER_SIZE: usize = 1024; +pub const FLASH_SIZE: usize = 256 * 1024; + embassy_hal_common::peripherals! { // RTC RTC0, @@ -112,15 +117,21 @@ embassy_hal_common::peripherals! { // WDT WDT, + // NVMC + NVMC, + // UARTE, TWI & SPI - UARTETWISPI0, - UARTETWISPI1, - UARTETWISPI2, - UARTETWISPI3, + SERIAL0, + SERIAL1, + SERIAL2, + SERIAL3, // SAADC SAADC, + // RNG + RNG, + // PWM PWM0, PWM1, @@ -236,14 +247,18 @@ embassy_hal_common::peripherals! { P1_15, } -impl_uarte!(UARTETWISPI0, UARTE0, SERIAL0); -impl_spim!(UARTETWISPI0, SPIM0, SERIAL0); -impl_twim!(UARTETWISPI0, TWIM0, SERIAL0); +impl_uarte!(SERIAL0, UARTE0, SERIAL0); +impl_spim!(SERIAL0, SPIM0, SERIAL0); +impl_spis!(SERIAL0, SPIS0, SERIAL0); +impl_twim!(SERIAL0, TWIM0, SERIAL0); +impl_twis!(SERIAL0, TWIS0, SERIAL0); impl_timer!(TIMER0, TIMER0, TIMER0); impl_timer!(TIMER1, TIMER1, TIMER1); impl_timer!(TIMER2, TIMER2, TIMER2); +impl_rng!(RNG, RNG, RNG); + impl_pin!(P0_00, 0, 0); impl_pin!(P0_01, 0, 1); impl_pin!(P0_02, 0, 2); @@ -327,29 +342,25 @@ impl_ppi_channel!(PPI_CH29, 29 => configurable); impl_ppi_channel!(PPI_CH30, 30 => configurable); impl_ppi_channel!(PPI_CH31, 31 => configurable); -pub mod irqs { - use embassy_cortex_m::interrupt::_export::declare; - - use crate::pac::Interrupt as InterruptEnum; - - declare!(CLOCK_POWER); - declare!(RADIO); - declare!(RNG); - declare!(GPIOTE); - declare!(WDT); - declare!(TIMER0); - declare!(ECB); - declare!(AAR_CCM); - declare!(TEMP); - declare!(RTC0); - declare!(IPC); - declare!(SERIAL0); - declare!(EGU0); - declare!(RTC1); - declare!(TIMER1); - declare!(TIMER2); - declare!(SWI0); - declare!(SWI1); - declare!(SWI2); - declare!(SWI3); -} +embassy_hal_common::interrupt_mod!( + CLOCK_POWER, + RADIO, + RNG, + GPIOTE, + WDT, + TIMER0, + ECB, + AAR_CCM, + TEMP, + RTC0, + IPC, + SERIAL0, + EGU0, + RTC1, + TIMER1, + TIMER2, + SWI0, + SWI1, + SWI2, + SWI3, +); diff --git a/embassy-nrf/src/chips/nrf9160.rs b/embassy-nrf/src/chips/nrf9160.rs index a4be8564e..67ea032ff 100644 --- a/embassy-nrf/src/chips/nrf9160.rs +++ b/embassy-nrf/src/chips/nrf9160.rs @@ -1,9 +1,12 @@ +/// Peripheral Access Crate #[allow(unused_imports)] #[rustfmt::skip] pub mod pac { // The nRF9160 has a secure and non-secure (NS) mode. // To avoid cfg spam, we remove _ns or _s suffixes here. + pub use nrf9160_pac::NVIC_PRIO_BITS; + #[doc(no_inline)] pub use nrf9160_pac::{ interrupt, @@ -164,6 +167,8 @@ pub mod pac { pub const EASY_DMA_SIZE: usize = (1 << 13) - 1; pub const FORCE_COPY_BUFFER_SIZE: usize = 1024; +pub const FLASH_SIZE: usize = 1024 * 1024; + embassy_hal_common::peripherals! { // RTC RTC0, @@ -172,11 +177,14 @@ embassy_hal_common::peripherals! { // WDT WDT, + // NVMC + NVMC, + // UARTE, TWI & SPI - UARTETWISPI0, - UARTETWISPI1, - UARTETWISPI2, - UARTETWISPI3, + SERIAL0, + SERIAL1, + SERIAL2, + SERIAL3, // SAADC SAADC, @@ -260,28 +268,43 @@ embassy_hal_common::peripherals! { P0_29, P0_30, P0_31, + + // PDM + PDM, } -impl_uarte!(UARTETWISPI0, UARTE0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0); -impl_uarte!(UARTETWISPI1, UARTE1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1); -impl_uarte!(UARTETWISPI2, UARTE2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2); -impl_uarte!(UARTETWISPI3, UARTE3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3); +impl_uarte!(SERIAL0, UARTE0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0); +impl_uarte!(SERIAL1, UARTE1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1); +impl_uarte!(SERIAL2, UARTE2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2); +impl_uarte!(SERIAL3, UARTE3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3); -impl_spim!(UARTETWISPI0, SPIM0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0); -impl_spim!(UARTETWISPI1, SPIM1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1); -impl_spim!(UARTETWISPI2, SPIM2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2); -impl_spim!(UARTETWISPI3, SPIM3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3); +impl_spim!(SERIAL0, SPIM0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0); +impl_spim!(SERIAL1, SPIM1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1); +impl_spim!(SERIAL2, SPIM2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2); +impl_spim!(SERIAL3, SPIM3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3); -impl_twim!(UARTETWISPI0, TWIM0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0); -impl_twim!(UARTETWISPI1, TWIM1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1); -impl_twim!(UARTETWISPI2, TWIM2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2); -impl_twim!(UARTETWISPI3, TWIM3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3); +impl_spis!(SERIAL0, SPIS0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0); +impl_spis!(SERIAL1, SPIS1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1); +impl_spis!(SERIAL2, SPIS2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2); +impl_spis!(SERIAL3, SPIS3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3); + +impl_twim!(SERIAL0, TWIM0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0); +impl_twim!(SERIAL1, TWIM1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1); +impl_twim!(SERIAL2, TWIM2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2); +impl_twim!(SERIAL3, TWIM3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3); + +impl_twis!(SERIAL0, TWIS0, UARTE0_SPIM0_SPIS0_TWIM0_TWIS0); +impl_twis!(SERIAL1, TWIS1, UARTE1_SPIM1_SPIS1_TWIM1_TWIS1); +impl_twis!(SERIAL2, TWIS2, UARTE2_SPIM2_SPIS2_TWIM2_TWIS2); +impl_twis!(SERIAL3, TWIS3, UARTE3_SPIM3_SPIS3_TWIM3_TWIS3); impl_pwm!(PWM0, PWM0, PWM0); impl_pwm!(PWM1, PWM1, PWM1); impl_pwm!(PWM2, PWM2, PWM2); impl_pwm!(PWM3, PWM3, PWM3); +impl_pdm!(PDM, PDM, PDM); + impl_timer!(TIMER0, TIMER0, TIMER0); impl_timer!(TIMER1, TIMER1, TIMER1); impl_timer!(TIMER2, TIMER2, TIMER2); @@ -336,49 +359,45 @@ impl_ppi_channel!(PPI_CH13, 13 => configurable); impl_ppi_channel!(PPI_CH14, 14 => configurable); impl_ppi_channel!(PPI_CH15, 15 => configurable); -impl_saadc_input!(P0_13, ANALOGINPUT0); -impl_saadc_input!(P0_14, ANALOGINPUT1); -impl_saadc_input!(P0_15, ANALOGINPUT2); -impl_saadc_input!(P0_16, ANALOGINPUT3); -impl_saadc_input!(P0_17, ANALOGINPUT4); -impl_saadc_input!(P0_18, ANALOGINPUT5); -impl_saadc_input!(P0_19, ANALOGINPUT6); -impl_saadc_input!(P0_20, ANALOGINPUT7); +impl_saadc_input!(P0_13, ANALOG_INPUT0); +impl_saadc_input!(P0_14, ANALOG_INPUT1); +impl_saadc_input!(P0_15, ANALOG_INPUT2); +impl_saadc_input!(P0_16, ANALOG_INPUT3); +impl_saadc_input!(P0_17, ANALOG_INPUT4); +impl_saadc_input!(P0_18, ANALOG_INPUT5); +impl_saadc_input!(P0_19, ANALOG_INPUT6); +impl_saadc_input!(P0_20, ANALOG_INPUT7); -pub mod irqs { - use embassy_cortex_m::interrupt::_export::declare; - - use crate::pac::Interrupt as InterruptEnum; - - declare!(SPU); - declare!(CLOCK_POWER); - declare!(UARTE0_SPIM0_SPIS0_TWIM0_TWIS0); - declare!(UARTE1_SPIM1_SPIS1_TWIM1_TWIS1); - declare!(UARTE2_SPIM2_SPIS2_TWIM2_TWIS2); - declare!(UARTE3_SPIM3_SPIS3_TWIM3_TWIS3); - declare!(GPIOTE0); - declare!(SAADC); - declare!(TIMER0); - declare!(TIMER1); - declare!(TIMER2); - declare!(RTC0); - declare!(RTC1); - declare!(WDT); - declare!(EGU0); - declare!(EGU1); - declare!(EGU2); - declare!(EGU3); - declare!(EGU4); - declare!(EGU5); - declare!(PWM0); - declare!(PWM1); - declare!(PWM2); - declare!(PDM); - declare!(PWM3); - declare!(I2S); - declare!(IPC); - declare!(FPU); - declare!(GPIOTE1); - declare!(KMU); - declare!(CRYPTOCELL); -} +embassy_hal_common::interrupt_mod!( + SPU, + CLOCK_POWER, + UARTE0_SPIM0_SPIS0_TWIM0_TWIS0, + UARTE1_SPIM1_SPIS1_TWIM1_TWIS1, + UARTE2_SPIM2_SPIS2_TWIM2_TWIS2, + UARTE3_SPIM3_SPIS3_TWIM3_TWIS3, + GPIOTE0, + SAADC, + TIMER0, + TIMER1, + TIMER2, + RTC0, + RTC1, + WDT, + EGU0, + EGU1, + EGU2, + EGU3, + EGU4, + EGU5, + PWM0, + PWM1, + PWM2, + PDM, + PWM3, + I2S, + IPC, + FPU, + GPIOTE1, + KMU, + CRYPTOCELL, +); diff --git a/embassy-nrf/src/gpio.rs b/embassy-nrf/src/gpio.rs index 924629908..895ab9340 100644 --- a/embassy-nrf/src/gpio.rs +++ b/embassy-nrf/src/gpio.rs @@ -1,4 +1,4 @@ -//! General purpose input/output for nRF. +//! General purpose input/output (GPIO) driver. #![macro_use] use core::convert::Infallible; @@ -70,7 +70,7 @@ impl<'d, T: Pin> Input<'d, T> { } /// Digital input or output level. -#[derive(Debug, Eq, PartialEq)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Level { /// Logical low. @@ -88,9 +88,9 @@ impl From for Level { } } -impl Into for Level { - fn into(self) -> bool { - match self { +impl From for bool { + fn from(level: Level) -> bool { + match level { Level::Low => false, Level::High => true, } @@ -574,7 +574,7 @@ mod eh1 { type Error = Infallible; } - impl<'d, T: Pin> embedded_hal_1::digital::blocking::InputPin for Input<'d, T> { + impl<'d, T: Pin> embedded_hal_1::digital::InputPin for Input<'d, T> { fn is_high(&self) -> Result { Ok(self.is_high()) } @@ -588,7 +588,7 @@ mod eh1 { type Error = Infallible; } - impl<'d, T: Pin> embedded_hal_1::digital::blocking::OutputPin for Output<'d, T> { + impl<'d, T: Pin> embedded_hal_1::digital::OutputPin for Output<'d, T> { fn set_high(&mut self) -> Result<(), Self::Error> { Ok(self.set_high()) } @@ -598,7 +598,7 @@ mod eh1 { } } - impl<'d, T: Pin> embedded_hal_1::digital::blocking::StatefulOutputPin for Output<'d, T> { + impl<'d, T: Pin> embedded_hal_1::digital::StatefulOutputPin for Output<'d, T> { fn is_set_high(&self) -> Result { Ok(self.is_set_high()) } @@ -615,7 +615,7 @@ mod eh1 { /// Implement [`InputPin`] for [`Flex`]; /// /// If the pin is not in input mode the result is unspecified. - impl<'d, T: Pin> embedded_hal_1::digital::blocking::InputPin for Flex<'d, T> { + impl<'d, T: Pin> embedded_hal_1::digital::InputPin for Flex<'d, T> { fn is_high(&self) -> Result { Ok(self.is_high()) } @@ -625,7 +625,7 @@ mod eh1 { } } - impl<'d, T: Pin> embedded_hal_1::digital::blocking::OutputPin for Flex<'d, T> { + impl<'d, T: Pin> embedded_hal_1::digital::OutputPin for Flex<'d, T> { fn set_high(&mut self) -> Result<(), Self::Error> { Ok(self.set_high()) } @@ -635,7 +635,7 @@ mod eh1 { } } - impl<'d, T: Pin> embedded_hal_1::digital::blocking::StatefulOutputPin for Flex<'d, T> { + impl<'d, T: Pin> embedded_hal_1::digital::StatefulOutputPin for Flex<'d, T> { fn is_set_high(&self) -> Result { Ok(self.is_set_high()) } diff --git a/embassy-nrf/src/gpiote.rs b/embassy-nrf/src/gpiote.rs index b52035705..6550f2abd 100644 --- a/embassy-nrf/src/gpiote.rs +++ b/embassy-nrf/src/gpiote.rs @@ -1,40 +1,50 @@ +//! GPIO task/event (GPIOTE) driver. + use core::convert::Infallible; -use core::future::Future; +use core::future::{poll_fn, Future}; use core::task::{Context, Poll}; -use embassy_hal_common::{impl_peripheral, Peripheral, PeripheralRef}; +use embassy_hal_common::{impl_peripheral, into_ref, Peripheral, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; -use futures::future::poll_fn; use crate::gpio::sealed::Pin as _; use crate::gpio::{AnyPin, Flex, Input, Output, Pin as GpioPin}; -use crate::interrupt::{Interrupt, InterruptExt}; +use crate::interrupt::InterruptExt; use crate::ppi::{Event, Task}; use crate::{interrupt, pac, peripherals}; -pub const CHANNEL_COUNT: usize = 8; +/// Amount of GPIOTE channels in the chip. +const CHANNEL_COUNT: usize = 8; #[cfg(any(feature = "nrf52833", feature = "nrf52840"))] -pub const PIN_COUNT: usize = 48; +const PIN_COUNT: usize = 48; #[cfg(not(any(feature = "nrf52833", feature = "nrf52840")))] -pub const PIN_COUNT: usize = 32; +const PIN_COUNT: usize = 32; #[allow(clippy::declare_interior_mutable_const)] const NEW_AW: AtomicWaker = AtomicWaker::new(); static CHANNEL_WAKERS: [AtomicWaker; CHANNEL_COUNT] = [NEW_AW; CHANNEL_COUNT]; static PORT_WAKERS: [AtomicWaker; PIN_COUNT] = [NEW_AW; PIN_COUNT]; +/// Polarity for listening to events for GPIOTE input channels. pub enum InputChannelPolarity { + /// Don't listen for any pin changes. None, + /// Listen for high to low changes. HiToLo, + /// Listen for low to high changes. LoToHi, + /// Listen for any change, either low to high or high to low. Toggle, } -/// Polarity of the `task out` operation. +/// Polarity of the OUT task operation for GPIOTE output channels. pub enum OutputChannelPolarity { + /// Set the pin high. Set, + /// Set the pin low. Clear, + /// Toggle the pin. Toggle, } @@ -64,42 +74,41 @@ pub(crate) fn init(irq_prio: crate::interrupt::Priority) { } // Enable interrupts - cfg_if::cfg_if! { - if #[cfg(any(feature="nrf5340-app-s", feature="nrf9160-s"))] { - let irq = unsafe { interrupt::GPIOTE0::steal() }; - } else if #[cfg(any(feature="nrf5340-app-ns", feature="nrf9160-ns"))] { - let irq = unsafe { interrupt::GPIOTE1::steal() }; - } else { - let irq = unsafe { interrupt::GPIOTE::steal() }; - } - } + #[cfg(any(feature = "nrf5340-app-s", feature = "nrf9160-s"))] + let irq = interrupt::GPIOTE0; + #[cfg(any(feature = "nrf5340-app-ns", feature = "nrf9160-ns"))] + let irq = interrupt::GPIOTE1; + #[cfg(any(feature = "_nrf52", feature = "nrf5340-net"))] + let irq = interrupt::GPIOTE; irq.unpend(); irq.set_priority(irq_prio); - irq.enable(); + unsafe { irq.enable() }; let g = regs(); g.events_port.write(|w| w); g.intenset.write(|w| w.port().set()); } -cfg_if::cfg_if! { - if #[cfg(any(feature="nrf5340-app-s", feature="nrf9160-s"))] { - #[interrupt] - fn GPIOTE0() { - unsafe { handle_gpiote_interrupt() }; - } - } else if #[cfg(any(feature="nrf5340-app-ns", feature="nrf9160-ns"))] { - #[interrupt] - fn GPIOTE1() { - unsafe { handle_gpiote_interrupt() }; - } - } else { - #[interrupt] - fn GPIOTE() { - unsafe { handle_gpiote_interrupt() }; - } - } +#[cfg(any(feature = "nrf5340-app-s", feature = "nrf9160-s"))] +#[cfg(feature = "rt")] +#[interrupt] +fn GPIOTE0() { + unsafe { handle_gpiote_interrupt() }; +} + +#[cfg(any(feature = "nrf5340-app-ns", feature = "nrf9160-ns"))] +#[cfg(feature = "rt")] +#[interrupt] +fn GPIOTE1() { + unsafe { handle_gpiote_interrupt() }; +} + +#[cfg(any(feature = "_nrf52", feature = "nrf5340-net"))] +#[cfg(feature = "rt")] +#[interrupt] +fn GPIOTE() { + unsafe { handle_gpiote_interrupt() }; } unsafe fn handle_gpiote_interrupt() { @@ -149,7 +158,7 @@ impl Iterator for BitIter { /// GPIOTE channel driver in input mode pub struct InputChannel<'d, C: Channel, T: GpioPin> { - ch: C, + ch: PeripheralRef<'d, C>, pin: Input<'d, T>, } @@ -163,7 +172,10 @@ impl<'d, C: Channel, T: GpioPin> Drop for InputChannel<'d, C, T> { } impl<'d, C: Channel, T: GpioPin> InputChannel<'d, C, T> { - pub fn new(ch: C, pin: Input<'d, T>, polarity: InputChannelPolarity) -> Self { + /// Create a new GPIOTE input channel driver. + pub fn new(ch: impl Peripheral

+ 'd, pin: Input<'d, T>, polarity: InputChannelPolarity) -> Self { + into_ref!(ch); + let g = regs(); let num = ch.number(); @@ -187,6 +199,7 @@ impl<'d, C: Channel, T: GpioPin> InputChannel<'d, C, T> { InputChannel { ch, pin } } + /// Asynchronously wait for an event in this channel. pub async fn wait(&self) { let g = regs(); let num = self.ch.number(); @@ -208,7 +221,7 @@ impl<'d, C: Channel, T: GpioPin> InputChannel<'d, C, T> { } /// Returns the IN event, for use with PPI. - pub fn event_in(&self) -> Event { + pub fn event_in(&self) -> Event<'d> { let g = regs(); Event::from_reg(&g.events_in[self.ch.number()]) } @@ -216,7 +229,7 @@ impl<'d, C: Channel, T: GpioPin> InputChannel<'d, C, T> { /// GPIOTE channel driver in output mode pub struct OutputChannel<'d, C: Channel, T: GpioPin> { - ch: C, + ch: PeripheralRef<'d, C>, _pin: Output<'d, T>, } @@ -230,7 +243,9 @@ impl<'d, C: Channel, T: GpioPin> Drop for OutputChannel<'d, C, T> { } impl<'d, C: Channel, T: GpioPin> OutputChannel<'d, C, T> { - pub fn new(ch: C, pin: Output<'d, T>, polarity: OutputChannelPolarity) -> Self { + /// Create a new GPIOTE output channel driver. + pub fn new(ch: impl Peripheral

+ 'd, pin: Output<'d, T>, polarity: OutputChannelPolarity) -> Self { + into_ref!(ch); let g = regs(); let num = ch.number(); @@ -256,20 +271,20 @@ impl<'d, C: Channel, T: GpioPin> OutputChannel<'d, C, T> { OutputChannel { ch, _pin: pin } } - /// Triggers `task out` (as configured with task_out_polarity, defaults to Toggle). + /// Triggers the OUT task (does the action as configured with task_out_polarity, defaults to Toggle). pub fn out(&self) { let g = regs(); g.tasks_out[self.ch.number()].write(|w| unsafe { w.bits(1) }); } - /// Triggers `task set` (set associated pin high). + /// Triggers the SET task (set associated pin high). #[cfg(not(feature = "nrf51"))] pub fn set(&self) { let g = regs(); g.tasks_set[self.ch.number()].write(|w| unsafe { w.bits(1) }); } - /// Triggers `task clear` (set associated pin low). + /// Triggers the CLEAR task (set associated pin low). #[cfg(not(feature = "nrf51"))] pub fn clear(&self) { let g = regs(); @@ -277,21 +292,21 @@ impl<'d, C: Channel, T: GpioPin> OutputChannel<'d, C, T> { } /// Returns the OUT task, for use with PPI. - pub fn task_out(&self) -> Task { + pub fn task_out(&self) -> Task<'d> { let g = regs(); Task::from_reg(&g.tasks_out[self.ch.number()]) } /// Returns the CLR task, for use with PPI. #[cfg(not(feature = "nrf51"))] - pub fn task_clr(&self) -> Task { + pub fn task_clr(&self) -> Task<'d> { let g = regs(); Task::from_reg(&g.tasks_clr[self.ch.number()]) } /// Returns the SET task, for use with PPI. #[cfg(not(feature = "nrf51"))] - pub fn task_set(&self) -> Task { + pub fn task_set(&self) -> Task<'d> { let g = regs(); Task::from_reg(&g.tasks_set[self.ch.number()]) } @@ -299,6 +314,7 @@ impl<'d, C: Channel, T: GpioPin> OutputChannel<'d, C, T> { // ======================= +#[must_use = "futures do nothing unless you `.await` or poll them"] pub(crate) struct PortInputFuture<'a> { pin: PeripheralRef<'a, AnyPin>, } @@ -334,48 +350,58 @@ impl<'a> Future for PortInputFuture<'a> { } impl<'d, T: GpioPin> Input<'d, T> { + /// Wait until the pin is high. If it is already high, return immediately. pub async fn wait_for_high(&mut self) { self.pin.wait_for_high().await } + /// Wait until the pin is low. If it is already low, return immediately. pub async fn wait_for_low(&mut self) { self.pin.wait_for_low().await } + /// Wait for the pin to undergo a transition from low to high. pub async fn wait_for_rising_edge(&mut self) { self.pin.wait_for_rising_edge().await } + /// Wait for the pin to undergo a transition from high to low. pub async fn wait_for_falling_edge(&mut self) { self.pin.wait_for_falling_edge().await } + /// Wait for the pin to undergo any transition, i.e low to high OR high to low. pub async fn wait_for_any_edge(&mut self) { self.pin.wait_for_any_edge().await } } impl<'d, T: GpioPin> Flex<'d, T> { + /// Wait until the pin is high. If it is already high, return immediately. pub async fn wait_for_high(&mut self) { self.pin.conf().modify(|_, w| w.sense().high()); PortInputFuture::new(&mut self.pin).await } + /// Wait until the pin is low. If it is already low, return immediately. pub async fn wait_for_low(&mut self) { self.pin.conf().modify(|_, w| w.sense().low()); PortInputFuture::new(&mut self.pin).await } + /// Wait for the pin to undergo a transition from low to high. pub async fn wait_for_rising_edge(&mut self) { self.wait_for_low().await; self.wait_for_high().await; } + /// Wait for the pin to undergo a transition from high to low. pub async fn wait_for_falling_edge(&mut self) { self.wait_for_high().await; self.wait_for_low().await; } + /// Wait for the pin to undergo any transition, i.e low to high OR high to low. pub async fn wait_for_any_edge(&mut self) { if self.is_high() { self.pin.conf().modify(|_, w| w.sense().low()); @@ -392,8 +418,17 @@ mod sealed { pub trait Channel {} } +/// GPIOTE channel trait. +/// +/// Implemented by all GPIOTE channels. pub trait Channel: sealed::Channel + Sized { + /// Get the channel number. fn number(&self) -> usize; + + /// Convert this channel to a type-erased `AnyChannel`. + /// + /// This allows using several channels in situations that might require + /// them to be the same type, like putting them in an array. fn degrade(self) -> AnyChannel { AnyChannel { number: self.number() as u8, @@ -401,6 +436,12 @@ pub trait Channel: sealed::Channel + Sized { } } +/// Type-erased channel. +/// +/// Obtained by calling `Channel::degrade`. +/// +/// This allows using several channels in situations that might require +/// them to be the same type, like putting them in an array. pub struct AnyChannel { number: u8, } @@ -458,7 +499,7 @@ mod eh1 { type Error = Infallible; } - impl<'d, C: Channel, T: GpioPin> embedded_hal_1::digital::blocking::InputPin for InputChannel<'d, C, T> { + impl<'d, C: Channel, T: GpioPin> embedded_hal_1::digital::InputPin for InputChannel<'d, C, T> { fn is_high(&self) -> Result { Ok(self.pin.is_high()) } @@ -469,72 +510,51 @@ mod eh1 { } } -cfg_if::cfg_if! { - if #[cfg(all(feature = "unstable-traits", feature = "nightly"))] { - use futures::FutureExt; +#[cfg(all(feature = "unstable-traits", feature = "nightly"))] +mod eha { + use super::*; - impl<'d, T: GpioPin> embedded_hal_async::digital::Wait for Input<'d, T> { - type WaitForHighFuture<'a> = impl Future> + 'a where Self: 'a; - - fn wait_for_high<'a>(&'a mut self) -> Self::WaitForHighFuture<'a> { - self.wait_for_high().map(Ok) - } - - type WaitForLowFuture<'a> = impl Future> + 'a where Self: 'a; - - fn wait_for_low<'a>(&'a mut self) -> Self::WaitForLowFuture<'a> { - self.wait_for_low().map(Ok) - } - - type WaitForRisingEdgeFuture<'a> = impl Future> + 'a where Self: 'a; - - fn wait_for_rising_edge<'a>(&'a mut self) -> Self::WaitForRisingEdgeFuture<'a> { - self.wait_for_rising_edge().map(Ok) - } - - type WaitForFallingEdgeFuture<'a> = impl Future> + 'a where Self: 'a; - - fn wait_for_falling_edge<'a>(&'a mut self) -> Self::WaitForFallingEdgeFuture<'a> { - self.wait_for_falling_edge().map(Ok) - } - - type WaitForAnyEdgeFuture<'a> = impl Future> + 'a where Self: 'a; - - fn wait_for_any_edge<'a>(&'a mut self) -> Self::WaitForAnyEdgeFuture<'a> { - self.wait_for_any_edge().map(Ok) - } + impl<'d, T: GpioPin> embedded_hal_async::digital::Wait for Input<'d, T> { + async fn wait_for_high(&mut self) -> Result<(), Self::Error> { + Ok(self.wait_for_high().await) } - impl<'d, T: GpioPin> embedded_hal_async::digital::Wait for Flex<'d, T> { - type WaitForHighFuture<'a> = impl Future> + 'a where Self: 'a; + async fn wait_for_low(&mut self) -> Result<(), Self::Error> { + Ok(self.wait_for_low().await) + } - fn wait_for_high<'a>(&'a mut self) -> Self::WaitForHighFuture<'a> { - self.wait_for_high().map(Ok) - } + async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { + Ok(self.wait_for_rising_edge().await) + } - type WaitForLowFuture<'a> = impl Future> + 'a where Self: 'a; + async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { + Ok(self.wait_for_falling_edge().await) + } - fn wait_for_low<'a>(&'a mut self) -> Self::WaitForLowFuture<'a> { - self.wait_for_low().map(Ok) - } + async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { + Ok(self.wait_for_any_edge().await) + } + } - type WaitForRisingEdgeFuture<'a> = impl Future> + 'a where Self: 'a; + impl<'d, T: GpioPin> embedded_hal_async::digital::Wait for Flex<'d, T> { + async fn wait_for_high(&mut self) -> Result<(), Self::Error> { + Ok(self.wait_for_high().await) + } - fn wait_for_rising_edge<'a>(&'a mut self) -> Self::WaitForRisingEdgeFuture<'a> { - self.wait_for_rising_edge().map(Ok) - } + async fn wait_for_low(&mut self) -> Result<(), Self::Error> { + Ok(self.wait_for_low().await) + } - type WaitForFallingEdgeFuture<'a> = impl Future> + 'a where Self: 'a; + async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { + Ok(self.wait_for_rising_edge().await) + } - fn wait_for_falling_edge<'a>(&'a mut self) -> Self::WaitForFallingEdgeFuture<'a> { - self.wait_for_falling_edge().map(Ok) - } + async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { + Ok(self.wait_for_falling_edge().await) + } - type WaitForAnyEdgeFuture<'a> = impl Future> + 'a where Self: 'a; - - fn wait_for_any_edge<'a>(&'a mut self) -> Self::WaitForAnyEdgeFuture<'a> { - self.wait_for_any_edge().map(Ok) - } + async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { + Ok(self.wait_for_any_edge().await) } } } diff --git a/embassy-nrf/src/i2s.rs b/embassy-nrf/src/i2s.rs new file mode 100644 index 000000000..fea38c4c0 --- /dev/null +++ b/embassy-nrf/src/i2s.rs @@ -0,0 +1,1194 @@ +//! Inter-IC Sound (I2S) driver. + +#![macro_use] + +use core::future::poll_fn; +use core::marker::PhantomData; +use core::mem::size_of; +use core::ops::{Deref, DerefMut}; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; + +use embassy_hal_common::drop::OnDrop; +use embassy_hal_common::{into_ref, PeripheralRef}; + +use crate::gpio::{AnyPin, Pin as GpioPin}; +use crate::interrupt::typelevel::Interrupt; +use crate::pac::i2s::RegisterBlock; +use crate::util::{slice_in_ram_or, slice_ptr_parts}; +use crate::{interrupt, Peripheral, EASY_DMA_SIZE}; + +/// Type alias for `MultiBuffering` with 2 buffers. +pub type DoubleBuffering = MultiBuffering; + +/// I2S transfer error. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + /// The buffer is too long. + BufferTooLong, + /// The buffer is empty. + BufferZeroLength, + /// The buffer is not in data RAM. It's most likely in flash, and nRF's DMA cannot access flash. + BufferNotInRAM, + /// The buffer address is not aligned. + BufferMisaligned, + /// The buffer length is not a multiple of the alignment. + BufferLengthMisaligned, +} + +/// I2S configuration. +#[derive(Clone)] +#[non_exhaustive] +pub struct Config { + /// Sample width + pub sample_width: SampleWidth, + /// Alignment + pub align: Align, + /// Sample format + pub format: Format, + /// Channel configuration. + pub channels: Channels, +} + +impl Default for Config { + fn default() -> Self { + Self { + sample_width: SampleWidth::_16bit, + align: Align::Left, + format: Format::I2S, + channels: Channels::Stereo, + } + } +} + +/// I2S clock configuration. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub struct MasterClock { + freq: MckFreq, + ratio: Ratio, +} + +impl MasterClock { + /// Create a new `MasterClock`. + pub fn new(freq: MckFreq, ratio: Ratio) -> Self { + Self { freq, ratio } + } +} + +impl MasterClock { + /// Get the sample rate for this clock configuration. + pub fn sample_rate(&self) -> u32 { + self.freq.to_frequency() / self.ratio.to_divisor() + } +} + +/// Master clock generator frequency. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum MckFreq { + /// 32 Mhz / 8 = 4000.00 kHz + _32MDiv8, + /// 32 Mhz / 10 = 3200.00 kHz + _32MDiv10, + /// 32 Mhz / 11 = 2909.09 kHz + _32MDiv11, + /// 32 Mhz / 15 = 2133.33 kHz + _32MDiv15, + /// 32 Mhz / 16 = 2000.00 kHz + _32MDiv16, + /// 32 Mhz / 21 = 1523.81 kHz + _32MDiv21, + /// 32 Mhz / 23 = 1391.30 kHz + _32MDiv23, + /// 32 Mhz / 30 = 1066.67 kHz + _32MDiv30, + /// 32 Mhz / 31 = 1032.26 kHz + _32MDiv31, + /// 32 Mhz / 32 = 1000.00 kHz + _32MDiv32, + /// 32 Mhz / 42 = 761.90 kHz + _32MDiv42, + /// 32 Mhz / 63 = 507.94 kHz + _32MDiv63, + /// 32 Mhz / 125 = 256.00 kHz + _32MDiv125, +} + +impl MckFreq { + const REGISTER_VALUES: &'static [u32] = &[ + 0x20000000, 0x18000000, 0x16000000, 0x11000000, 0x10000000, 0x0C000000, 0x0B000000, 0x08800000, 0x08400000, + 0x08000000, 0x06000000, 0x04100000, 0x020C0000, + ]; + + const FREQUENCIES: &'static [u32] = &[ + 4000000, 3200000, 2909090, 2133333, 2000000, 1523809, 1391304, 1066666, 1032258, 1000000, 761904, 507936, + 256000, + ]; + + /// Return the value that needs to be written to the register. + pub fn to_register_value(&self) -> u32 { + Self::REGISTER_VALUES[usize::from(*self)] + } + + /// Return the master clock frequency. + pub fn to_frequency(&self) -> u32 { + Self::FREQUENCIES[usize::from(*self)] + } +} + +impl From for usize { + fn from(variant: MckFreq) -> Self { + variant as _ + } +} + +/// Master clock frequency ratio +/// +/// Sample Rate = LRCK = MCK / Ratio +/// +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum Ratio { + /// Divide by 32 + _32x, + /// Divide by 48 + _48x, + /// Divide by 64 + _64x, + /// Divide by 96 + _96x, + /// Divide by 128 + _128x, + /// Divide by 192 + _192x, + /// Divide by 256 + _256x, + /// Divide by 384 + _384x, + /// Divide by 512 + _512x, +} + +impl Ratio { + const RATIOS: &'static [u32] = &[32, 48, 64, 96, 128, 192, 256, 384, 512]; + + /// Return the value that needs to be written to the register. + pub fn to_register_value(&self) -> u8 { + usize::from(*self) as u8 + } + + /// Return the divisor for this ratio + pub fn to_divisor(&self) -> u32 { + Self::RATIOS[usize::from(*self)] + } +} + +impl From for usize { + fn from(variant: Ratio) -> Self { + variant as _ + } +} + +/// Approximate sample rates. +/// +/// Those are common sample rates that can not be configured without an small error. +/// +/// For custom master clock configuration, please refer to [MasterClock]. +#[derive(Clone, Copy)] +pub enum ApproxSampleRate { + /// 11025 Hz + _11025, + /// 16000 Hz + _16000, + /// 22050 Hz + _22050, + /// 32000 Hz + _32000, + /// 44100 Hz + _44100, + /// 48000 Hz + _48000, +} + +impl From for MasterClock { + fn from(value: ApproxSampleRate) -> Self { + match value { + // error = 86 + ApproxSampleRate::_11025 => MasterClock::new(MckFreq::_32MDiv15, Ratio::_192x), + // error = 127 + ApproxSampleRate::_16000 => MasterClock::new(MckFreq::_32MDiv21, Ratio::_96x), + // error = 172 + ApproxSampleRate::_22050 => MasterClock::new(MckFreq::_32MDiv15, Ratio::_96x), + // error = 254 + ApproxSampleRate::_32000 => MasterClock::new(MckFreq::_32MDiv21, Ratio::_48x), + // error = 344 + ApproxSampleRate::_44100 => MasterClock::new(MckFreq::_32MDiv15, Ratio::_48x), + // error = 381 + ApproxSampleRate::_48000 => MasterClock::new(MckFreq::_32MDiv21, Ratio::_32x), + } + } +} + +impl ApproxSampleRate { + /// Get the sample rate as an integer. + pub fn sample_rate(&self) -> u32 { + MasterClock::from(*self).sample_rate() + } +} + +/// Exact sample rates. +/// +/// Those are non standard sample rates that can be configured without error. +/// +/// For custom master clock configuration, please refer to [Mode]. +#[derive(Clone, Copy)] +pub enum ExactSampleRate { + /// 8000 Hz + _8000, + /// 10582 Hz + _10582, + /// 12500 Hz + _12500, + /// 15625 Hz + _15625, + /// 15873 Hz + _15873, + /// 25000 Hz + _25000, + /// 31250 Hz + _31250, + /// 50000 Hz + _50000, + /// 62500 Hz + _62500, + /// 100000 Hz + _100000, + /// 125000 Hz + _125000, +} + +impl ExactSampleRate { + /// Get the sample rate as an integer. + pub fn sample_rate(&self) -> u32 { + MasterClock::from(*self).sample_rate() + } +} + +impl From for MasterClock { + fn from(value: ExactSampleRate) -> Self { + match value { + ExactSampleRate::_8000 => MasterClock::new(MckFreq::_32MDiv125, Ratio::_32x), + ExactSampleRate::_10582 => MasterClock::new(MckFreq::_32MDiv63, Ratio::_48x), + ExactSampleRate::_12500 => MasterClock::new(MckFreq::_32MDiv10, Ratio::_256x), + ExactSampleRate::_15625 => MasterClock::new(MckFreq::_32MDiv32, Ratio::_64x), + ExactSampleRate::_15873 => MasterClock::new(MckFreq::_32MDiv63, Ratio::_32x), + ExactSampleRate::_25000 => MasterClock::new(MckFreq::_32MDiv10, Ratio::_128x), + ExactSampleRate::_31250 => MasterClock::new(MckFreq::_32MDiv32, Ratio::_32x), + ExactSampleRate::_50000 => MasterClock::new(MckFreq::_32MDiv10, Ratio::_64x), + ExactSampleRate::_62500 => MasterClock::new(MckFreq::_32MDiv16, Ratio::_32x), + ExactSampleRate::_100000 => MasterClock::new(MckFreq::_32MDiv10, Ratio::_32x), + ExactSampleRate::_125000 => MasterClock::new(MckFreq::_32MDiv8, Ratio::_32x), + } + } +} + +/// Sample width. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum SampleWidth { + /// 8 bit samples. + _8bit, + /// 16 bit samples. + _16bit, + /// 24 bit samples. + _24bit, +} + +impl From for u8 { + fn from(variant: SampleWidth) -> Self { + variant as _ + } +} + +/// Channel used for the most significant sample value in a frame. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum Align { + /// Left-align samples. + Left, + /// Right-align samples. + Right, +} + +impl From for bool { + fn from(variant: Align) -> Self { + match variant { + Align::Left => false, + Align::Right => true, + } + } +} + +/// Frame format. +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum Format { + /// I2S frame format + I2S, + /// Aligned frame format + Aligned, +} + +impl From for bool { + fn from(variant: Format) -> Self { + match variant { + Format::I2S => false, + Format::Aligned => true, + } + } +} + +/// Channels +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub enum Channels { + /// Stereo (2 channels). + Stereo, + /// Mono, left channel only. + MonoLeft, + /// Mono, right channel only. + MonoRight, +} + +impl From for u8 { + fn from(variant: Channels) -> Self { + variant as _ + } +} + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let device = Device::::new(); + let s = T::state(); + + if device.is_tx_ptr_updated() { + trace!("TX INT"); + s.tx_waker.wake(); + device.disable_tx_ptr_interrupt(); + } + + if device.is_rx_ptr_updated() { + trace!("RX INT"); + s.rx_waker.wake(); + device.disable_rx_ptr_interrupt(); + } + + if device.is_stopped() { + trace!("STOPPED INT"); + s.stop_waker.wake(); + device.disable_stopped_interrupt(); + } + } +} + +/// I2S driver. +pub struct I2S<'d, T: Instance> { + i2s: PeripheralRef<'d, T>, + mck: Option>, + sck: PeripheralRef<'d, AnyPin>, + lrck: PeripheralRef<'d, AnyPin>, + sdin: Option>, + sdout: Option>, + master_clock: Option, + config: Config, +} + +impl<'d, T: Instance> I2S<'d, T> { + /// Create a new I2S in master mode + pub fn new_master( + i2s: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + mck: impl Peripheral

+ 'd, + sck: impl Peripheral

+ 'd, + lrck: impl Peripheral

+ 'd, + master_clock: MasterClock, + config: Config, + ) -> Self { + into_ref!(i2s, mck, sck, lrck); + Self { + i2s, + mck: Some(mck.map_into()), + sck: sck.map_into(), + lrck: lrck.map_into(), + sdin: None, + sdout: None, + master_clock: Some(master_clock), + config, + } + } + + /// Create a new I2S in slave mode + pub fn new_slave( + i2s: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + sck: impl Peripheral

+ 'd, + lrck: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(i2s, sck, lrck); + Self { + i2s, + mck: None, + sck: sck.map_into(), + lrck: lrck.map_into(), + sdin: None, + sdout: None, + master_clock: None, + config, + } + } + + /// I2S output only + pub fn output( + mut self, + sdout: impl Peripheral

+ 'd, + buffers: MultiBuffering, + ) -> OutputStream<'d, T, S, NB, NS> { + self.sdout = Some(sdout.into_ref().map_into()); + OutputStream { + _p: self.build(), + buffers, + } + } + + /// I2S input only + pub fn input( + mut self, + sdin: impl Peripheral

+ 'd, + buffers: MultiBuffering, + ) -> InputStream<'d, T, S, NB, NS> { + self.sdin = Some(sdin.into_ref().map_into()); + InputStream { + _p: self.build(), + buffers, + } + } + + /// I2S full duplex (input and output) + pub fn full_duplex( + mut self, + sdin: impl Peripheral

+ 'd, + sdout: impl Peripheral

+ 'd, + buffers_out: MultiBuffering, + buffers_in: MultiBuffering, + ) -> FullDuplexStream<'d, T, S, NB, NS> { + self.sdout = Some(sdout.into_ref().map_into()); + self.sdin = Some(sdin.into_ref().map_into()); + + FullDuplexStream { + _p: self.build(), + buffers_out, + buffers_in, + } + } + + fn build(self) -> PeripheralRef<'d, T> { + self.apply_config(); + self.select_pins(); + self.setup_interrupt(); + + let device = Device::::new(); + device.enable(); + + self.i2s + } + + fn apply_config(&self) { + let c = &T::regs().config; + match &self.master_clock { + Some(MasterClock { freq, ratio }) => { + c.mode.write(|w| w.mode().master()); + c.mcken.write(|w| w.mcken().enabled()); + c.mckfreq + .write(|w| unsafe { w.mckfreq().bits(freq.to_register_value()) }); + c.ratio.write(|w| unsafe { w.ratio().bits(ratio.to_register_value()) }); + } + None => { + c.mode.write(|w| w.mode().slave()); + } + }; + + c.swidth + .write(|w| unsafe { w.swidth().bits(self.config.sample_width.into()) }); + c.align.write(|w| w.align().bit(self.config.align.into())); + c.format.write(|w| w.format().bit(self.config.format.into())); + c.channels + .write(|w| unsafe { w.channels().bits(self.config.channels.into()) }); + } + + fn select_pins(&self) { + let psel = &T::regs().psel; + + if let Some(mck) = &self.mck { + psel.mck.write(|w| { + unsafe { w.bits(mck.psel_bits()) }; + w.connect().connected() + }); + } + + psel.sck.write(|w| { + unsafe { w.bits(self.sck.psel_bits()) }; + w.connect().connected() + }); + + psel.lrck.write(|w| { + unsafe { w.bits(self.lrck.psel_bits()) }; + w.connect().connected() + }); + + if let Some(sdin) = &self.sdin { + psel.sdin.write(|w| { + unsafe { w.bits(sdin.psel_bits()) }; + w.connect().connected() + }); + } + + if let Some(sdout) = &self.sdout { + psel.sdout.write(|w| { + unsafe { w.bits(sdout.psel_bits()) }; + w.connect().connected() + }); + } + } + + fn setup_interrupt(&self) { + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + let device = Device::::new(); + device.disable_tx_ptr_interrupt(); + device.disable_rx_ptr_interrupt(); + device.disable_stopped_interrupt(); + + device.reset_tx_ptr_event(); + device.reset_rx_ptr_event(); + device.reset_stopped_event(); + + device.enable_tx_ptr_interrupt(); + device.enable_rx_ptr_interrupt(); + device.enable_stopped_interrupt(); + } + + async fn stop() { + compiler_fence(Ordering::SeqCst); + + let device = Device::::new(); + device.stop(); + + T::state().started.store(false, Ordering::Relaxed); + + poll_fn(|cx| { + T::state().stop_waker.register(cx.waker()); + + if device.is_stopped() { + trace!("STOP: Ready"); + device.reset_stopped_event(); + Poll::Ready(()) + } else { + trace!("STOP: Pending"); + Poll::Pending + } + }) + .await; + + device.disable(); + } + + async fn send_from_ram(buffer_ptr: *const [S]) -> Result<(), Error> + where + S: Sample, + { + trace!("SEND: {}", buffer_ptr as *const S as u32); + + slice_in_ram_or(buffer_ptr, Error::BufferNotInRAM)?; + + compiler_fence(Ordering::SeqCst); + + let device = Device::::new(); + + device.update_tx(buffer_ptr)?; + + Self::wait_tx_ptr_update().await; + + compiler_fence(Ordering::SeqCst); + + Ok(()) + } + + async fn wait_tx_ptr_update() { + let drop = OnDrop::new(move || { + trace!("TX DROP: Stopping"); + + let device = Device::::new(); + device.disable_tx_ptr_interrupt(); + device.reset_tx_ptr_event(); + device.disable_tx(); + + // TX is stopped almost instantly, spinning is fine. + while !device.is_tx_ptr_updated() {} + + trace!("TX DROP: Stopped"); + }); + + poll_fn(|cx| { + T::state().tx_waker.register(cx.waker()); + + let device = Device::::new(); + if device.is_tx_ptr_updated() { + trace!("TX POLL: Ready"); + device.reset_tx_ptr_event(); + device.enable_tx_ptr_interrupt(); + Poll::Ready(()) + } else { + trace!("TX POLL: Pending"); + Poll::Pending + } + }) + .await; + + drop.defuse(); + } + + async fn receive_from_ram(buffer_ptr: *mut [S]) -> Result<(), Error> + where + S: Sample, + { + trace!("RECEIVE: {}", buffer_ptr as *const S as u32); + + // NOTE: RAM slice check for rx is not necessary, as a mutable + // slice can only be built from data located in RAM. + + compiler_fence(Ordering::SeqCst); + + let device = Device::::new(); + + device.update_rx(buffer_ptr)?; + + Self::wait_rx_ptr_update().await; + + compiler_fence(Ordering::SeqCst); + + Ok(()) + } + + async fn wait_rx_ptr_update() { + let drop = OnDrop::new(move || { + trace!("RX DROP: Stopping"); + + let device = Device::::new(); + device.disable_rx_ptr_interrupt(); + device.reset_rx_ptr_event(); + device.disable_rx(); + + // TX is stopped almost instantly, spinning is fine. + while !device.is_rx_ptr_updated() {} + + trace!("RX DROP: Stopped"); + }); + + poll_fn(|cx| { + T::state().rx_waker.register(cx.waker()); + + let device = Device::::new(); + if device.is_rx_ptr_updated() { + trace!("RX POLL: Ready"); + device.reset_rx_ptr_event(); + device.enable_rx_ptr_interrupt(); + Poll::Ready(()) + } else { + trace!("RX POLL: Pending"); + Poll::Pending + } + }) + .await; + + drop.defuse(); + } +} + +/// I2S output +pub struct OutputStream<'d, T: Instance, S: Sample, const NB: usize, const NS: usize> { + _p: PeripheralRef<'d, T>, + buffers: MultiBuffering, +} + +impl<'d, T: Instance, S: Sample, const NB: usize, const NS: usize> OutputStream<'d, T, S, NB, NS> { + /// Get a mutable reference to the current buffer. + pub fn buffer(&mut self) -> &mut [S] { + self.buffers.get_mut() + } + + /// Prepare the initial buffer and start the I2S transfer. + pub async fn start(&mut self) -> Result<(), Error> + where + S: Sample, + { + let device = Device::::new(); + + let s = T::state(); + if s.started.load(Ordering::Relaxed) { + self.stop().await; + } + + device.enable(); + device.enable_tx(); + + device.update_tx(self.buffers.switch())?; + + s.started.store(true, Ordering::Relaxed); + + device.start(); + + I2S::::wait_tx_ptr_update().await; + + Ok(()) + } + + /// Stops the I2S transfer and waits until it has stopped. + #[inline(always)] + pub async fn stop(&self) { + I2S::::stop().await + } + + /// Sends the current buffer for transmission in the DMA. + /// Switches to use the next available buffer. + pub async fn send(&mut self) -> Result<(), Error> + where + S: Sample, + { + I2S::::send_from_ram(self.buffers.switch()).await + } +} + +/// I2S input +pub struct InputStream<'d, T: Instance, S: Sample, const NB: usize, const NS: usize> { + _p: PeripheralRef<'d, T>, + buffers: MultiBuffering, +} + +impl<'d, T: Instance, S: Sample, const NB: usize, const NS: usize> InputStream<'d, T, S, NB, NS> { + /// Get a mutable reference to the current buffer. + pub fn buffer(&mut self) -> &mut [S] { + self.buffers.get_mut() + } + + /// Prepare the initial buffer and start the I2S transfer. + pub async fn start(&mut self) -> Result<(), Error> + where + S: Sample, + { + let device = Device::::new(); + + let s = T::state(); + if s.started.load(Ordering::Relaxed) { + self.stop().await; + } + + device.enable(); + device.enable_rx(); + + device.update_rx(self.buffers.switch())?; + + s.started.store(true, Ordering::Relaxed); + + device.start(); + + I2S::::wait_rx_ptr_update().await; + + Ok(()) + } + + /// Stops the I2S transfer and waits until it has stopped. + #[inline(always)] + pub async fn stop(&self) { + I2S::::stop().await + } + + /// Sets the current buffer for reception from the DMA. + /// Switches to use the next available buffer. + #[allow(unused_mut)] + pub async fn receive(&mut self) -> Result<(), Error> + where + S: Sample, + { + I2S::::receive_from_ram(self.buffers.switch_mut()).await + } +} + +/// I2S full duplex stream (input & output) +pub struct FullDuplexStream<'d, T: Instance, S: Sample, const NB: usize, const NS: usize> { + _p: PeripheralRef<'d, T>, + buffers_out: MultiBuffering, + buffers_in: MultiBuffering, +} + +impl<'d, T: Instance, S: Sample, const NB: usize, const NS: usize> FullDuplexStream<'d, T, S, NB, NS> { + /// Get the current output and input buffers. + pub fn buffers(&mut self) -> (&mut [S], &[S]) { + (self.buffers_out.get_mut(), self.buffers_in.get()) + } + + /// Prepare the initial buffers and start the I2S transfer. + pub async fn start(&mut self) -> Result<(), Error> + where + S: Sample, + { + let device = Device::::new(); + + let s = T::state(); + if s.started.load(Ordering::Relaxed) { + self.stop().await; + } + + device.enable(); + device.enable_tx(); + device.enable_rx(); + + device.update_tx(self.buffers_out.switch())?; + device.update_rx(self.buffers_in.switch_mut())?; + + s.started.store(true, Ordering::Relaxed); + + device.start(); + + I2S::::wait_tx_ptr_update().await; + I2S::::wait_rx_ptr_update().await; + + Ok(()) + } + + /// Stops the I2S transfer and waits until it has stopped. + #[inline(always)] + pub async fn stop(&self) { + I2S::::stop().await + } + + /// Sets the current buffers for output and input for transmission/reception from the DMA. + /// Switch to use the next available buffers for output/input. + pub async fn send_and_receive(&mut self) -> Result<(), Error> + where + S: Sample, + { + I2S::::send_from_ram(self.buffers_out.switch()).await?; + I2S::::receive_from_ram(self.buffers_in.switch_mut()).await?; + Ok(()) + } +} + +/// Helper encapsulating common I2S device operations. +struct Device(&'static RegisterBlock, PhantomData); + +impl Device { + fn new() -> Self { + Self(T::regs(), PhantomData) + } + + #[inline(always)] + pub fn enable(&self) { + trace!("ENABLED"); + self.0.enable.write(|w| w.enable().enabled()); + } + + #[inline(always)] + pub fn disable(&self) { + trace!("DISABLED"); + self.0.enable.write(|w| w.enable().disabled()); + } + + #[inline(always)] + fn enable_tx(&self) { + trace!("TX ENABLED"); + self.0.config.txen.write(|w| w.txen().enabled()); + } + + #[inline(always)] + fn disable_tx(&self) { + trace!("TX DISABLED"); + self.0.config.txen.write(|w| w.txen().disabled()); + } + + #[inline(always)] + fn enable_rx(&self) { + trace!("RX ENABLED"); + self.0.config.rxen.write(|w| w.rxen().enabled()); + } + + #[inline(always)] + fn disable_rx(&self) { + trace!("RX DISABLED"); + self.0.config.rxen.write(|w| w.rxen().disabled()); + } + + #[inline(always)] + fn start(&self) { + trace!("START"); + self.0.tasks_start.write(|w| unsafe { w.bits(1) }); + } + + #[inline(always)] + fn stop(&self) { + self.0.tasks_stop.write(|w| unsafe { w.bits(1) }); + } + + #[inline(always)] + fn is_stopped(&self) -> bool { + self.0.events_stopped.read().bits() != 0 + } + + #[inline(always)] + fn reset_stopped_event(&self) { + trace!("STOPPED EVENT: Reset"); + self.0.events_stopped.reset(); + } + + #[inline(always)] + fn disable_stopped_interrupt(&self) { + trace!("STOPPED INTERRUPT: Disabled"); + self.0.intenclr.write(|w| w.stopped().clear()); + } + + #[inline(always)] + fn enable_stopped_interrupt(&self) { + trace!("STOPPED INTERRUPT: Enabled"); + self.0.intenset.write(|w| w.stopped().set()); + } + + #[inline(always)] + fn reset_tx_ptr_event(&self) { + trace!("TX PTR EVENT: Reset"); + self.0.events_txptrupd.reset(); + } + + #[inline(always)] + fn reset_rx_ptr_event(&self) { + trace!("RX PTR EVENT: Reset"); + self.0.events_rxptrupd.reset(); + } + + #[inline(always)] + fn disable_tx_ptr_interrupt(&self) { + trace!("TX PTR INTERRUPT: Disabled"); + self.0.intenclr.write(|w| w.txptrupd().clear()); + } + + #[inline(always)] + fn disable_rx_ptr_interrupt(&self) { + trace!("RX PTR INTERRUPT: Disabled"); + self.0.intenclr.write(|w| w.rxptrupd().clear()); + } + + #[inline(always)] + fn enable_tx_ptr_interrupt(&self) { + trace!("TX PTR INTERRUPT: Enabled"); + self.0.intenset.write(|w| w.txptrupd().set()); + } + + #[inline(always)] + fn enable_rx_ptr_interrupt(&self) { + trace!("RX PTR INTERRUPT: Enabled"); + self.0.intenset.write(|w| w.rxptrupd().set()); + } + + #[inline(always)] + fn is_tx_ptr_updated(&self) -> bool { + self.0.events_txptrupd.read().bits() != 0 + } + + #[inline(always)] + fn is_rx_ptr_updated(&self) -> bool { + self.0.events_rxptrupd.read().bits() != 0 + } + + #[inline] + fn update_tx(&self, buffer_ptr: *const [S]) -> Result<(), Error> { + let (ptr, maxcnt) = Self::validated_dma_parts(buffer_ptr)?; + self.0.rxtxd.maxcnt.write(|w| unsafe { w.bits(maxcnt) }); + self.0.txd.ptr.write(|w| unsafe { w.ptr().bits(ptr) }); + Ok(()) + } + + #[inline] + fn update_rx(&self, buffer_ptr: *const [S]) -> Result<(), Error> { + let (ptr, maxcnt) = Self::validated_dma_parts(buffer_ptr)?; + self.0.rxtxd.maxcnt.write(|w| unsafe { w.bits(maxcnt) }); + self.0.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr) }); + Ok(()) + } + + fn validated_dma_parts(buffer_ptr: *const [S]) -> Result<(u32, u32), Error> { + let (ptr, len) = slice_ptr_parts(buffer_ptr); + let ptr = ptr as u32; + let bytes_len = len * size_of::(); + let maxcnt = (bytes_len / size_of::()) as u32; + + trace!("PTR={}, MAXCNT={}", ptr, maxcnt); + + if ptr % 4 != 0 { + Err(Error::BufferMisaligned) + } else if bytes_len % 4 != 0 { + Err(Error::BufferLengthMisaligned) + } else if maxcnt as usize > EASY_DMA_SIZE { + Err(Error::BufferTooLong) + } else { + Ok((ptr, maxcnt)) + } + } +} + +/// Sample details +pub trait Sample: Sized + Copy + Default { + /// Width of this sample type. + const WIDTH: usize; + + /// Scale of this sample. + const SCALE: Self; +} + +impl Sample for i8 { + const WIDTH: usize = 8; + const SCALE: Self = 1 << (Self::WIDTH - 1); +} + +impl Sample for i16 { + const WIDTH: usize = 16; + const SCALE: Self = 1 << (Self::WIDTH - 1); +} + +impl Sample for i32 { + const WIDTH: usize = 24; + const SCALE: Self = 1 << (Self::WIDTH - 1); +} + +/// A 4-bytes aligned buffer. Needed for DMA access. +#[derive(Clone, Copy)] +#[repr(align(4))] +pub struct AlignedBuffer([T; N]); + +impl AlignedBuffer { + /// Create a new `AlignedBuffer`. + pub fn new(array: [T; N]) -> Self { + Self(array) + } +} + +impl Default for AlignedBuffer { + fn default() -> Self { + Self([T::default(); N]) + } +} + +impl Deref for AlignedBuffer { + type Target = [T]; + fn deref(&self) -> &Self::Target { + self.0.as_slice() + } +} + +impl DerefMut for AlignedBuffer { + fn deref_mut(&mut self) -> &mut Self::Target { + self.0.as_mut_slice() + } +} + +/// Set of multiple buffers, for multi-buffering transfers. +pub struct MultiBuffering { + buffers: [AlignedBuffer; NB], + index: usize, +} + +impl MultiBuffering { + /// Create a new `MultiBuffering`. + pub fn new() -> Self { + assert!(NB > 1); + Self { + buffers: [AlignedBuffer::::default(); NB], + index: 0, + } + } + + fn get(&self) -> &[S] { + &self.buffers[self.index] + } + + fn get_mut(&mut self) -> &mut [S] { + &mut self.buffers[self.index] + } + + /// Advance to use the next buffer and return a non mutable pointer to the previous one. + fn switch(&mut self) -> *const [S] { + let prev_index = self.index; + self.index = (self.index + 1) % NB; + self.buffers[prev_index].deref() as *const [S] + } + + /// Advance to use the next buffer and return a mutable pointer to the previous one. + fn switch_mut(&mut self) -> *mut [S] { + let prev_index = self.index; + self.index = (self.index + 1) % NB; + self.buffers[prev_index].deref_mut() as *mut [S] + } +} + +pub(crate) mod sealed { + use core::sync::atomic::AtomicBool; + + use embassy_sync::waitqueue::AtomicWaker; + + /// Peripheral static state + pub struct State { + pub started: AtomicBool, + pub rx_waker: AtomicWaker, + pub tx_waker: AtomicWaker, + pub stop_waker: AtomicWaker, + } + + impl State { + pub const fn new() -> Self { + Self { + started: AtomicBool::new(false), + rx_waker: AtomicWaker::new(), + tx_waker: AtomicWaker::new(), + stop_waker: AtomicWaker::new(), + } + } + } + + pub trait Instance { + fn regs() -> &'static crate::pac::i2s::RegisterBlock; + fn state() -> &'static State; + } +} + +/// I2S peripheral instance. +pub trait Instance: Peripheral

+ sealed::Instance + 'static + Send { + /// Interrupt for this peripheral. + type Interrupt: interrupt::typelevel::Interrupt; +} + +macro_rules! impl_i2s { + ($type:ident, $pac_type:ident, $irq:ident) => { + impl crate::i2s::sealed::Instance for peripherals::$type { + fn regs() -> &'static crate::pac::i2s::RegisterBlock { + unsafe { &*pac::$pac_type::ptr() } + } + fn state() -> &'static crate::i2s::sealed::State { + static STATE: crate::i2s::sealed::State = crate::i2s::sealed::State::new(); + &STATE + } + } + impl crate::i2s::Instance for peripherals::$type { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +} diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs index 205891954..d23759f9d 100644 --- a/embassy-nrf/src/lib.rs +++ b/embassy-nrf/src/lib.rs @@ -1,49 +1,7 @@ -//! # Embassy nRF HAL -//! -//! HALs implement safe, idiomatic Rust APIs to use the hardware capabilities, so raw register manipulation is not needed. -//! -//! The Embassy nRF HAL targets the Nordic Semiconductor nRF family of hardware. The HAL implements both blocking and async APIs -//! for many peripherals. The benefit of using the async APIs is that the HAL takes care of waiting for peripherals to -//! complete operations in low power mod and handling interrupts, so that applications can focus on more important matters. -//! -//! ## EasyDMA considerations -//! -//! On nRF chips, peripherals can use the so called EasyDMA feature to offload the task of interacting -//! with peripherals. It takes care of sending/receiving data over a variety of bus protocols (TWI/I2C, UART, SPI). -//! However, EasyDMA requires the buffers used to transmit and receive data to reside in RAM. Unfortunately, Rust -//! slices will not always do so. The following example using the SPI peripheral shows a common situation where this might happen: -//! -//! ```no_run -//! // As we pass a slice to the function whose contents will not ever change, -//! // the compiler writes it into the flash and thus the pointer to it will -//! // reference static memory. Since EasyDMA requires slices to reside in RAM, -//! // this function call will fail. -//! let result = spim.write_from_ram(&[1, 2, 3]); -//! assert_eq!(result, Err(Error::DMABufferNotInDataMemory)); -//! -//! // The data is still static and located in flash. However, since we are assigning -//! // it to a variable, the compiler will load it into memory. Passing a reference to the -//! // variable will yield a pointer that references dynamic memory, thus making EasyDMA happy. -//! // This function call succeeds. -//! let data = [1, 2, 3]; -//! let result = spim.write_from_ram(&data); -//! assert!(result.is_ok()); -//! ``` -//! -//! Each peripheral struct which uses EasyDMA ([`Spim`](spim::Spim), [`Uarte`](uarte::Uarte), [`Twim`](twim::Twim)) has two variants of their mutating functions: -//! - Functions with the suffix (e.g. [`write_from_ram`](spim::Spim::write_from_ram), [`transfer_from_ram`](spim::Spim::transfer_from_ram)) will return an error if the passed slice does not reside in RAM. -//! - Functions without the suffix (e.g. [`write`](spim::Spim::write), [`transfer`](spim::Spim::transfer)) will check whether the data is in RAM and copy it into memory prior to transmission. -//! -//! Since copying incurs a overhead, you are given the option to choose from `_from_ram` variants which will -//! fail and notify you, or the more convenient versions without the suffix which are potentially a little bit -//! more inefficient. Be aware that this overhead is not only in terms of instruction count but also in terms of memory usage -//! as the methods without the suffix will be allocating a statically sized buffer (up to 512 bytes for the nRF52840). -//! -//! Note that the methods that read data like [`read`](spim::Spim::read) and [`transfer_in_place`](spim::Spim::transfer_in_place) do not have the corresponding `_from_ram` variants as -//! mutable slices always reside in RAM. - #![no_std] -#![cfg_attr(feature = "nightly", feature(generic_associated_types, type_alias_impl_trait))] +#![cfg_attr(feature = "nightly", feature(async_fn_in_trait, impl_trait_projections))] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] #[cfg(not(any( feature = "nrf51", @@ -62,6 +20,12 @@ )))] compile_error!("No chip feature activated. You must activate exactly one of the following features: nrf52810, nrf52811, nrf52832, nrf52833, nrf52840"); +#[cfg(all(feature = "reset-pin-as-gpio", not(feature = "_nrf52")))] +compile_error!("feature `reset-pin-as-gpio` is only valid for nRF52 series chips."); + +#[cfg(all(feature = "nfc-pins-as-gpio", not(any(feature = "_nrf52", feature = "_nrf5340-app"))))] +compile_error!("feature `nfc-pins-as-gpio` is only valid for nRF52, or nRF53's application core."); + // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; pub(crate) mod util; @@ -69,29 +33,41 @@ pub(crate) mod util; #[cfg(feature = "_time-driver")] mod time_driver; -#[cfg(feature = "nightly")] pub mod buffered_uarte; pub mod gpio; #[cfg(feature = "gpiote")] pub mod gpiote; -#[cfg(not(any(feature = "_nrf5340", feature = "_nrf9160")))] +#[cfg(any(feature = "nrf52832", feature = "nrf52833", feature = "nrf52840"))] +pub mod i2s; pub mod nvmc; +#[cfg(any( + feature = "nrf52810", + feature = "nrf52811", + feature = "nrf52832", + feature = "nrf52833", + feature = "nrf52840", + feature = "_nrf5340-app", + feature = "_nrf9160" +))] +pub mod pdm; pub mod ppi; #[cfg(not(any(feature = "nrf52805", feature = "nrf52820", feature = "_nrf5340-net")))] pub mod pwm; -#[cfg(not(any(feature = "nrf51", feature = "_nrf9160", feature = "_nrf5340")))] +#[cfg(not(any(feature = "nrf51", feature = "_nrf9160", feature = "_nrf5340-net")))] pub mod qdec; -#[cfg(feature = "nrf52840")] +#[cfg(any(feature = "nrf52840", feature = "_nrf5340-app"))] pub mod qspi; -#[cfg(not(any(feature = "_nrf5340", feature = "_nrf9160")))] +#[cfg(not(any(feature = "_nrf5340-app", feature = "_nrf9160")))] pub mod rng; #[cfg(not(any(feature = "nrf52820", feature = "_nrf5340-net")))] pub mod saadc; pub mod spim; +pub mod spis; #[cfg(not(any(feature = "_nrf5340", feature = "_nrf9160")))] pub mod temp; pub mod timer; pub mod twim; +pub mod twis; pub mod uarte; #[cfg(any( feature = "_nrf5340-app", @@ -103,14 +79,6 @@ pub mod uarte; pub mod usb; #[cfg(not(feature = "_nrf5340"))] pub mod wdt; -#[cfg(any( - feature = "nrf52810", - feature = "nrf52811", - feature = "nrf52832", - feature = "nrf52833", - feature = "nrf52840", -))] -pub mod pdm; // This mod MUST go last, so that it sees all the `impl_foo!` macros #[cfg_attr(feature = "nrf52805", path = "chips/nrf52805.rs")] @@ -125,15 +93,32 @@ pub mod pdm; #[cfg_attr(feature = "_nrf9160", path = "chips/nrf9160.rs")] mod chip; -pub use chip::EASY_DMA_SIZE; +/// Macro to bind interrupts to handlers. +/// +/// This defines the right interrupt handlers, and creates a unit struct (like `struct Irqs;`) +/// and implements the right [`Binding`]s for it. You can pass this struct to drivers to +/// prove at compile-time that the right interrupts have been bound. +// developer note: this macro can't be in `embassy-hal-common` due to the use of `$crate`. +#[macro_export] +macro_rules! bind_interrupts { + ($vis:vis struct $name:ident { $($irq:ident => $($handler:ty),*;)* }) => { + $vis struct $name; -pub mod interrupt { - //! nRF interrupts for cortex-m devices. - pub use cortex_m::interrupt::{CriticalSection, Mutex}; - pub use embassy_cortex_m::interrupt::*; + $( + #[allow(non_snake_case)] + #[no_mangle] + unsafe extern "C" fn $irq() { + $( + <$handler as $crate::interrupt::typelevel::Handler<$crate::interrupt::typelevel::$irq>>::on_interrupt(); + )* + } - pub use crate::chip::irqs::*; -} + $( + unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {} + )* + )* + }; + } // Reexports @@ -141,11 +126,12 @@ pub mod interrupt { pub use chip::pac; #[cfg(not(feature = "unstable-pac"))] pub(crate) use chip::pac; -pub use chip::{peripherals, Peripherals}; -pub use embassy_cortex_m::executor; -pub use embassy_cortex_m::interrupt::_export::interrupt; +pub use chip::{peripherals, Peripherals, EASY_DMA_SIZE}; pub use embassy_hal_common::{into_ref, Peripheral, PeripheralRef}; +pub use crate::chip::interrupt; +pub use crate::pac::NVIC_PRIO_BITS; + pub mod config { //! Configuration options used when initializing the HAL. @@ -174,6 +160,47 @@ pub mod config { ExternalFullSwing, } + /// SWD access port protection setting. + #[non_exhaustive] + pub enum Debug { + /// Debugging is allowed (APPROTECT is disabled). Default. + Allowed, + /// Debugging is not allowed (APPROTECT is enabled). + Disallowed, + /// APPROTECT is not configured (neither to enable it or disable it). + /// This can be useful if you're already doing it by other means and + /// you don't want embassy-nrf to touch UICR. + NotConfigured, + } + + /// Settings for enabling the built in DCDC converters. + #[cfg(not(any(feature = "_nrf5340", feature = "_nrf9160")))] + pub struct DcdcConfig { + /// Config for the first stage DCDC (VDDH -> VDD), if disabled LDO will be used. + #[cfg(feature = "nrf52840")] + pub reg0: bool, + /// Config for the second stage DCDC (VDD -> DEC4), if disabled LDO will be used. + pub reg1: bool, + } + + /// Settings for enabling the built in DCDC converters. + #[cfg(feature = "_nrf5340-app")] + pub struct DcdcConfig { + /// Config for the high voltage stage, if disabled LDO will be used. + pub regh: bool, + /// Config for the main rail, if disabled LDO will be used. + pub regmain: bool, + /// Config for the radio rail, if disabled LDO will be used. + pub regradio: bool, + } + + /// Settings for enabling the built in DCDC converter. + #[cfg(feature = "_nrf9160")] + pub struct DcdcConfig { + /// Config for the main rail, if disabled LDO will be used. + pub regmain: bool, + } + /// Configuration for peripherals. Default configuration should work on any nRF chip. #[non_exhaustive] pub struct Config { @@ -181,12 +208,17 @@ pub mod config { pub hfclk_source: HfclkSource, /// Low frequency clock source. pub lfclk_source: LfclkSource, + #[cfg(not(feature = "_nrf5340-net"))] + /// DCDC configuration. + pub dcdc: DcdcConfig, /// GPIOTE interrupt priority. Should be lower priority than softdevice if used. #[cfg(feature = "gpiote")] pub gpiote_interrupt_priority: crate::interrupt::Priority, /// Time driver interrupt priority. Should be lower priority than softdevice if used. #[cfg(feature = "_time-driver")] pub time_interrupt_priority: crate::interrupt::Priority, + /// Enable or disable the debug port. + pub debug: Debug, } impl Default for Config { @@ -197,21 +229,218 @@ pub mod config { // xtals if they know they have them. hfclk_source: HfclkSource::Internal, lfclk_source: LfclkSource::InternalRC, + #[cfg(not(any(feature = "_nrf5340", feature = "_nrf9160")))] + dcdc: DcdcConfig { + #[cfg(feature = "nrf52840")] + reg0: false, + reg1: false, + }, + #[cfg(feature = "_nrf5340-app")] + dcdc: DcdcConfig { + regh: false, + regmain: false, + regradio: false, + }, + #[cfg(feature = "_nrf9160")] + dcdc: DcdcConfig { regmain: false }, #[cfg(feature = "gpiote")] gpiote_interrupt_priority: crate::interrupt::Priority::P0, #[cfg(feature = "_time-driver")] time_interrupt_priority: crate::interrupt::Priority::P0, + + // In NS mode, default to NotConfigured, assuming the S firmware will do it. + #[cfg(feature = "_ns")] + debug: Debug::NotConfigured, + #[cfg(not(feature = "_ns"))] + debug: Debug::Allowed, } } } } +#[cfg(feature = "_nrf9160")] +#[allow(unused)] +mod consts { + pub const UICR_APPROTECT: *mut u32 = 0x00FF8000 as *mut u32; + pub const UICR_SECUREAPPROTECT: *mut u32 = 0x00FF802C as *mut u32; + pub const APPROTECT_ENABLED: u32 = 0x0000_0000; +} + +#[cfg(feature = "_nrf5340-app")] +#[allow(unused)] +mod consts { + pub const UICR_APPROTECT: *mut u32 = 0x00FF8000 as *mut u32; + pub const UICR_SECUREAPPROTECT: *mut u32 = 0x00FF801C as *mut u32; + pub const UICR_NFCPINS: *mut u32 = 0x00FF8028 as *mut u32; + pub const APPROTECT_ENABLED: u32 = 0x0000_0000; + pub const APPROTECT_DISABLED: u32 = 0x50FA50FA; +} + +#[cfg(feature = "_nrf5340-net")] +#[allow(unused)] +mod consts { + pub const UICR_APPROTECT: *mut u32 = 0x01FF8000 as *mut u32; + pub const APPROTECT_ENABLED: u32 = 0x0000_0000; + pub const APPROTECT_DISABLED: u32 = 0x50FA50FA; +} + +#[cfg(feature = "_nrf52")] +#[allow(unused)] +mod consts { + pub const UICR_PSELRESET1: *mut u32 = 0x10001200 as *mut u32; + pub const UICR_PSELRESET2: *mut u32 = 0x10001204 as *mut u32; + pub const UICR_NFCPINS: *mut u32 = 0x1000120C as *mut u32; + pub const UICR_APPROTECT: *mut u32 = 0x10001208 as *mut u32; + pub const APPROTECT_ENABLED: u32 = 0x0000_0000; + pub const APPROTECT_DISABLED: u32 = 0x0000_005a; +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +enum WriteResult { + /// Word was written successfully, needs reset. + Written, + /// Word was already set to the value we wanted to write, nothing was done. + Noop, + /// Word is already set to something else, we couldn't write the desired value. + Failed, +} + +unsafe fn uicr_write(address: *mut u32, value: u32) -> WriteResult { + uicr_write_masked(address, value, 0xFFFF_FFFF) +} + +unsafe fn uicr_write_masked(address: *mut u32, value: u32, mask: u32) -> WriteResult { + let curr_val = address.read_volatile(); + if curr_val & mask == value & mask { + return WriteResult::Noop; + } + + // We can only change `1` bits to `0` bits. + if curr_val & value & mask != value & mask { + return WriteResult::Failed; + } + + let nvmc = &*pac::NVMC::ptr(); + nvmc.config.write(|w| w.wen().wen()); + while nvmc.ready.read().ready().is_busy() {} + address.write_volatile(value | !mask); + while nvmc.ready.read().ready().is_busy() {} + nvmc.config.reset(); + while nvmc.ready.read().ready().is_busy() {} + + WriteResult::Written +} + /// Initialize peripherals with the provided configuration. This should only be called once at startup. pub fn init(config: config::Config) -> Peripherals { // Do this first, so that it panics if user is calling `init` a second time // before doing anything important. let peripherals = Peripherals::take(); + let mut needs_reset = false; + + // Setup debug protection. + match config.debug { + config::Debug::Allowed => { + #[cfg(feature = "_nrf52")] + unsafe { + let variant = (0x1000_0104 as *mut u32).read_volatile(); + // Get the letter for the build code (b'A' .. b'F') + let build_code = (variant >> 8) as u8; + + if build_code >= b'F' { + // Chips with build code F and higher (revision 3 and higher) have an + // improved APPROTECT ("hardware and software controlled access port protection") + // which needs explicit action by the firmware to keep it unlocked + + // UICR.APPROTECT = SwDisabled + let res = uicr_write(consts::UICR_APPROTECT, consts::APPROTECT_DISABLED); + needs_reset |= res == WriteResult::Written; + // APPROTECT.DISABLE = SwDisabled + (0x4000_0558 as *mut u32).write_volatile(consts::APPROTECT_DISABLED); + } else { + // nothing to do on older chips, debug is allowed by default. + } + } + + #[cfg(feature = "_nrf5340")] + unsafe { + let p = &*pac::CTRLAP::ptr(); + + let res = uicr_write(consts::UICR_APPROTECT, consts::APPROTECT_DISABLED); + needs_reset |= res == WriteResult::Written; + p.approtect.disable.write(|w| w.bits(consts::APPROTECT_DISABLED)); + + #[cfg(feature = "_nrf5340-app")] + { + let res = uicr_write(consts::UICR_SECUREAPPROTECT, consts::APPROTECT_DISABLED); + needs_reset |= res == WriteResult::Written; + p.secureapprotect.disable.write(|w| w.bits(consts::APPROTECT_DISABLED)); + } + } + + // nothing to do on the nrf9160, debug is allowed by default. + } + config::Debug::Disallowed => unsafe { + // UICR.APPROTECT = Enabled + let res = uicr_write(consts::UICR_APPROTECT, consts::APPROTECT_ENABLED); + needs_reset |= res == WriteResult::Written; + #[cfg(any(feature = "_nrf5340-app", feature = "_nrf9160"))] + { + let res = uicr_write(consts::UICR_SECUREAPPROTECT, consts::APPROTECT_ENABLED); + needs_reset |= res == WriteResult::Written; + } + }, + config::Debug::NotConfigured => {} + } + + #[cfg(feature = "_nrf52")] + unsafe { + let value = if cfg!(feature = "reset-pin-as-gpio") { + !0 + } else { + chip::RESET_PIN + }; + let res1 = uicr_write(consts::UICR_PSELRESET1, value); + let res2 = uicr_write(consts::UICR_PSELRESET2, value); + needs_reset |= res1 == WriteResult::Written || res2 == WriteResult::Written; + if res1 == WriteResult::Failed || res2 == WriteResult::Failed { + #[cfg(not(feature = "reset-pin-as-gpio"))] + warn!( + "You have requested enabling chip reset functionality on the reset pin, by not enabling the Cargo feature `reset-pin-as-gpio`.\n\ + However, UICR is already programmed to some other setting, and can't be changed without erasing it.\n\ + To fix this, erase UICR manually, for example using `probe-rs erase` or `nrfjprog --eraseuicr`." + ); + #[cfg(feature = "reset-pin-as-gpio")] + warn!( + "You have requested using the reset pin as GPIO, by enabling the Cargo feature `reset-pin-as-gpio`.\n\ + However, UICR is already programmed to some other setting, and can't be changed without erasing it.\n\ + To fix this, erase UICR manually, for example using `probe-rs erase` or `nrfjprog --eraseuicr`." + ); + } + } + + #[cfg(any(feature = "_nrf52", feature = "_nrf5340-app"))] + unsafe { + let value = if cfg!(feature = "nfc-pins-as-gpio") { 0 } else { 1 }; + let res = uicr_write_masked(consts::UICR_NFCPINS, value, 1); + needs_reset |= res == WriteResult::Written; + if res == WriteResult::Failed { + // with nfc-pins-as-gpio, this can never fail because we're writing all zero bits. + #[cfg(not(feature = "nfc-pins-as-gpio"))] + warn!( + "You have requested to use P0.09 and P0.10 pins for NFC, by not enabling the Cargo feature `nfc-pins-as-gpio`.\n\ + However, UICR is already programmed to some other setting, and can't be changed without erasing it.\n\ + To fix this, erase UICR manually, for example using `probe-rs erase` or `nrfjprog --eraseuicr`." + ); + } + } + + if needs_reset { + cortex_m::peripheral::SCB::sys_reset(); + } + let r = unsafe { &*pac::CLOCK::ptr() }; // Start HFCLK. @@ -259,6 +488,41 @@ pub fn init(config: config::Config) -> Peripherals { r.tasks_lfclkstart.write(|w| unsafe { w.bits(1) }); while r.events_lfclkstarted.read().bits() == 0 {} + #[cfg(not(any(feature = "_nrf5340", feature = "_nrf9160")))] + { + // Setup DCDCs. + let pwr = unsafe { &*pac::POWER::ptr() }; + #[cfg(feature = "nrf52840")] + if config.dcdc.reg0 { + pwr.dcdcen0.write(|w| w.dcdcen().set_bit()); + } + if config.dcdc.reg1 { + pwr.dcdcen.write(|w| w.dcdcen().set_bit()); + } + } + #[cfg(feature = "_nrf9160")] + { + // Setup DCDC. + let reg = unsafe { &*pac::REGULATORS::ptr() }; + if config.dcdc.regmain { + reg.dcdcen.write(|w| w.dcdcen().set_bit()); + } + } + #[cfg(feature = "_nrf5340-app")] + { + // Setup DCDC. + let reg = unsafe { &*pac::REGULATORS::ptr() }; + if config.dcdc.regh { + reg.vregh.dcdcen.write(|w| w.dcdcen().set_bit()); + } + if config.dcdc.regmain { + reg.vregmain.dcdcen.write(|w| w.dcdcen().set_bit()); + } + if config.dcdc.regradio { + reg.vregradio.dcdcen.write(|w| w.dcdcen().set_bit()); + } + } + // Init GPIOTE #[cfg(feature = "gpiote")] gpiote::init(config.gpiote_interrupt_priority); @@ -267,5 +531,12 @@ pub fn init(config: config::Config) -> Peripherals { #[cfg(feature = "_time-driver")] time_driver::init(config.time_interrupt_priority); + // Disable UARTE (enabled by default for some reason) + #[cfg(feature = "_nrf9160")] + unsafe { + (*pac::UARTE0::ptr()).enable.write(|w| w.enable().disabled()); + (*pac::UARTE1::ptr()).enable.write(|w| w.enable().disabled()); + } + peripherals } diff --git a/embassy-nrf/src/nvmc.rs b/embassy-nrf/src/nvmc.rs index 6f66f7a78..91a5a272f 100644 --- a/embassy-nrf/src/nvmc.rs +++ b/embassy-nrf/src/nvmc.rs @@ -1,4 +1,4 @@ -//! Non-Volatile Memory Controller (NVMC) module. +//! Non-Volatile Memory Controller (NVMC, AKA internal flash) driver. use core::{ptr, slice}; @@ -10,8 +10,12 @@ use embedded_storage::nor_flash::{ use crate::peripherals::NVMC; use crate::{pac, Peripheral}; +#[cfg(not(feature = "_nrf5340-net"))] /// Erase size of NVMC flash in bytes. pub const PAGE_SIZE: usize = 4096; +#[cfg(feature = "_nrf5340-net")] +/// Erase size of NVMC flash in bytes. +pub const PAGE_SIZE: usize = 2048; /// Size of NVMC flash in bytes. pub const FLASH_SIZE: usize = crate::chip::FLASH_SIZE; @@ -20,7 +24,7 @@ pub const FLASH_SIZE: usize = crate::chip::FLASH_SIZE; #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Error { - /// Opration using a location not in flash. + /// Operation using a location not in flash. OutOfBounds, /// Unaligned operation or using unaligned buffers. Unaligned, @@ -55,6 +59,51 @@ impl<'d> Nvmc<'d> { let p = Self::regs(); while p.ready.read().ready().is_busy() {} } + + #[cfg(not(any(feature = "_nrf9160", feature = "_nrf5340")))] + fn wait_ready_write(&mut self) { + self.wait_ready(); + } + + #[cfg(any(feature = "_nrf9160", feature = "_nrf5340"))] + fn wait_ready_write(&mut self) { + let p = Self::regs(); + while p.readynext.read().readynext().is_busy() {} + } + + #[cfg(not(any(feature = "_nrf9160", feature = "_nrf5340")))] + fn erase_page(&mut self, page_addr: u32) { + Self::regs().erasepage().write(|w| unsafe { w.bits(page_addr) }); + } + + #[cfg(any(feature = "_nrf9160", feature = "_nrf5340"))] + fn erase_page(&mut self, page_addr: u32) { + let first_page_word = page_addr as *mut u32; + unsafe { + first_page_word.write_volatile(0xFFFF_FFFF); + } + } + + fn enable_erase(&self) { + #[cfg(not(feature = "_ns"))] + Self::regs().config.write(|w| w.wen().een()); + #[cfg(feature = "_ns")] + Self::regs().configns.write(|w| w.wen().een()); + } + + fn enable_read(&self) { + #[cfg(not(feature = "_ns"))] + Self::regs().config.write(|w| w.wen().ren()); + #[cfg(feature = "_ns")] + Self::regs().configns.write(|w| w.wen().ren()); + } + + fn enable_write(&self) { + #[cfg(not(feature = "_ns"))] + Self::regs().config.write(|w| w.wen().wen()); + #[cfg(feature = "_ns")] + Self::regs().configns.write(|w| w.wen().wen()); + } } impl<'d> MultiwriteNorFlash for Nvmc<'d> {} @@ -93,17 +142,15 @@ impl<'d> NorFlash for Nvmc<'d> { return Err(Error::Unaligned); } - let p = Self::regs(); - - p.config.write(|w| w.wen().een()); + self.enable_erase(); self.wait_ready(); - for page in (from..to).step_by(PAGE_SIZE) { - p.erasepage().write(|w| unsafe { w.bits(page) }); + for page_addr in (from..to).step_by(PAGE_SIZE) { + self.erase_page(page_addr); self.wait_ready(); } - p.config.reset(); + self.enable_read(); self.wait_ready(); Ok(()) @@ -117,9 +164,7 @@ impl<'d> NorFlash for Nvmc<'d> { return Err(Error::Unaligned); } - let p = Self::regs(); - - p.config.write(|w| w.wen().wen()); + self.enable_write(); self.wait_ready(); unsafe { @@ -129,11 +174,11 @@ impl<'d> NorFlash for Nvmc<'d> { for i in 0..words { let w = ptr::read_unaligned(p_src.add(i)); ptr::write_volatile(p_dst.add(i), w); - self.wait_ready(); + self.wait_ready_write(); } } - p.config.reset(); + self.enable_read(); self.wait_ready(); Ok(()) diff --git a/embassy-nrf/src/pdm.rs b/embassy-nrf/src/pdm.rs index c2c54fba9..1fc717fd1 100644 --- a/embassy-nrf/src/pdm.rs +++ b/embassy-nrf/src/pdm.rs @@ -1,66 +1,71 @@ +//! Pulse Density Modulation (PDM) mirophone driver. + #![macro_use] +use core::marker::PhantomData; use core::sync::atomic::{compiler_fence, Ordering}; use core::task::Poll; -use embassy_hal_common::{into_ref, PeripheralRef}; use embassy_hal_common::drop::OnDrop; -use embassy_sync::waitqueue::AtomicWaker; +use embassy_hal_common::{into_ref, PeripheralRef}; use futures::future::poll_fn; -use pac::{pdm, PDM}; -use pdm::mode::{EDGE_A, OPERATION_A}; -pub use pdm::pdmclkctrl::FREQ_A as Frequency; -pub use pdm::ratio::RATIO_A as Ratio; use fixed::types::I7F1; -use crate::interrupt::InterruptExt; -use crate::gpio::Pin as GpioPin; -use crate::{interrupt, pac, peripherals, Peripheral}; +use crate::chip::EASY_DMA_SIZE; +use crate::gpio::sealed::Pin; +use crate::gpio::{AnyPin, Pin as GpioPin}; +use crate::interrupt::typelevel::Interrupt; +use crate::{interrupt, Peripheral}; +use crate::pac::pdm::mode::{EDGE_A, OPERATION_A}; +pub use crate::pac::pdm::pdmclkctrl::FREQ_A as Frequency; +pub use crate::pac::pdm::ratio::RATIO_A as Ratio; +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let r = T::regs(); + + if r.events_end.read().bits() != 0 { + r.intenclr.write(|w| w.end().clear()); + } + + if r.events_started.read().bits() != 0 { + r.intenclr.write(|w| w.started().clear()); + } + + if r.events_stopped.read().bits() != 0 { + r.intenclr.write(|w| w.stopped().clear()); + } + + T::state().waker.wake(); + } +} + +/// PDM microphone interface +pub struct Pdm<'d, T: Instance> { + _peri: PeripheralRef<'d, T>, +} + +/// PDM error. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] -pub enum Error {} - -/// One-shot and continuous PDM. -pub struct Pdm<'d> { - _p: PeripheralRef<'d, peripherals::PDM>, +pub enum Error { + /// Buffer is too long. + BufferTooLong, + /// Buffer is empty + BufferZeroLength, + /// PDM is not running + NotRunning, + /// PDM is already running + AlreadyRunning, } -static WAKER: AtomicWaker = AtomicWaker::new(); - -/// Used to configure the PDM peripheral. -/// -/// See the `Default` impl for suitable default values. -#[non_exhaustive] -pub struct Config { - /// Clock frequency - pub frequency: Frequency, - /// Clock ratio - pub ratio: Ratio, - /// Channels - pub channels: Channels, - /// Edge to sample on - pub left_edge: Edge, - /// Gain left in dB - pub gain_left: I7F1, - /// Gain right in dB - pub gain_right: I7F1, -} - -impl Default for Config { - /// Default configuration for single channel sampling. - fn default() -> Self { - Self { - frequency: Frequency::DEFAULT, - ratio: Ratio::RATIO80, - channels: Channels::Stereo, - left_edge: Edge::FallingEdge, - gain_left: I7F1::ZERO, - gain_right: I7F1::ZERO, - } - } -} +static DUMMY_BUFFER: [i16; 1] = [0; 1]; /// The state of a continuously running sampler. While it reflects /// the progress of a sampler, it also signals what should be done @@ -68,69 +73,66 @@ impl Default for Config { /// can then tear down its infrastructure. #[derive(PartialEq)] pub enum SamplerState { + /// The sampler processed the samples and is ready for more. Sampled, + /// The sampler is done processing samples. Stopped, } -impl<'d> Pdm<'d> { +impl<'d, T: Instance> Pdm<'d, T> { + /// Create PDM driver pub fn new( - pdm: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, - data: impl Peripheral

+ 'd, - clock: impl Peripheral

+ 'd, + pdm: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + clk: impl Peripheral

+ 'd, + din: impl Peripheral

+ 'd, config: Config, ) -> Self { - into_ref!(pdm, irq, data, clock); + into_ref!(pdm, clk, din); + Self::new_inner(pdm, clk.map_into(), din.map_into(), config) + } - let r = unsafe { &*PDM::ptr() }; + fn new_inner( + pdm: PeripheralRef<'d, T>, + clk: PeripheralRef<'d, AnyPin>, + din: PeripheralRef<'d, AnyPin>, + config: Config, + ) -> Self { + into_ref!(pdm); - let Config { frequency, ratio, channels, left_edge, gain_left, gain_right } = config; + let r = T::regs(); - // Configure channels - r.enable.write(|w| w.enable().enabled()); - r.pdmclkctrl.write(|w| w.freq().variant(frequency)); - r.ratio.write(|w| w.ratio().variant(ratio)); + // setup gpio pins + din.conf().write(|w| w.input().set_bit()); + r.psel.din.write(|w| unsafe { w.bits(din.psel_bits()) }); + clk.set_low(); + clk.conf().write(|w| w.dir().output()); + r.psel.clk.write(|w| unsafe { w.bits(clk.psel_bits()) }); + + // configure + r.pdmclkctrl.write(|w| w.freq().variant(config.frequency)); + r.ratio.write(|w| w.ratio().variant(config.ratio)); r.mode.write(|w| { - w.operation().variant(channels.into()); - w.edge().variant(left_edge.into()); + w.operation().variant(config.operation_mode.into()); + w.edge().variant(config.edge.into()); w }); - Self::_set_gain(r, gain_left, gain_right); - - r.psel.din.write(|w| unsafe { w.bits(data.psel_bits()) }); - r.psel.clk.write(|w| unsafe { w.bits(clock.psel_bits()) }); + Self::_set_gain(r, config.gain_left, config.gain_right); // Disable all events interrupts r.intenclr.write(|w| unsafe { w.bits(0x003F_FFFF) }); - irq.set_handler(Self::on_interrupt); - irq.unpend(); - irq.enable(); + // IRQ + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; - Self { _p: pdm } + r.enable.write(|w| w.enable().set_bit()); + + Self { _peri: pdm } } - fn on_interrupt(_ctx: *mut ()) { - let r = Self::regs(); - - if r.events_end.read().bits() != 0 { - r.intenclr.write(|w| w.end().clear()); - WAKER.wake(); - } - - if r.events_started.read().bits() != 0 { - r.intenclr.write(|w| w.started().clear()); - WAKER.wake(); - } - - if r.events_stopped.read().bits() != 0 { - r.intenclr.write(|w| w.stopped().clear()); - WAKER.wake(); - } - } - - fn _set_gain(r: &pdm::RegisterBlock, gain_left: I7F1, gain_right: I7F1) { + fn _set_gain(r: &crate::pac::pdm::RegisterBlock, gain_left: I7F1, gain_right: I7F1) { let gain_left = gain_left.saturating_add(I7F1::from_bits(40)).saturating_to_num::().clamp(0, 0x50); let gain_right = gain_right.saturating_add(I7F1::from_bits(40)).saturating_to_num::().clamp(0, 0x50); @@ -138,81 +140,111 @@ impl<'d> Pdm<'d> { r.gainr.write(|w| unsafe { w.gainr().bits(gain_right) }); } + /// Adjust the gain of the PDM microphone on the fly pub fn set_gain(&mut self, gain_left: I7F1, gain_right: I7F1) { - Self::_set_gain(Self::regs(), gain_left, gain_right) + Self::_set_gain(T::regs(), gain_left, gain_right) } - fn regs() -> &'static pdm::RegisterBlock { - unsafe { &*PDM::ptr() } + /// Start sampling microphon data into a dummy buffer + /// Usefull to start the microphon and keep it active between recording samples + pub async fn start(&mut self) { + let r = T::regs(); + + // start dummy sampling because microphon needs some setup time + r.sample + .ptr + .write(|w| unsafe { w.sampleptr().bits(DUMMY_BUFFER.as_ptr() as u32) }); + r.sample + .maxcnt + .write(|w| unsafe { w.buffsize().bits(DUMMY_BUFFER.len() as _) }); + + r.tasks_start.write(|w| unsafe { w.bits(1) }); } - /// One shot sampling. If the PDM is configured for multiple channels, the samples will be interleaved. - /// The first samples from the PDM peripheral and microphone usually contain garbage data, so the discard parameter sets the number of complete buffers to discard before returning. - pub async fn sample(&mut self, mut discard: usize, buf: &mut [i16; N]) { - let r = Self::regs(); + /// Stop sampling microphon data inta a dummy buffer + pub async fn stop(&mut self) { + let r = T::regs(); + r.tasks_stop.write(|w| unsafe { w.bits(1) }); + r.events_started.reset(); + } - // Set up the DMA - r.sample.ptr.write(|w| unsafe { w.sampleptr().bits(buf.as_mut_ptr() as u32) }); - r.sample.maxcnt.write(|w| unsafe { w.buffsize().bits(N as _) }); + /// Sample data into the given buffer. + pub async fn sample(&mut self, buffer: &mut [i16]) -> Result<(), Error> { + if buffer.len() == 0 { + return Err(Error::BufferZeroLength); + } + if buffer.len() > EASY_DMA_SIZE { + return Err(Error::BufferTooLong); + } - // Reset and enable the events - r.events_end.reset(); - r.events_stopped.reset(); - r.intenset.write(|w| { - w.end().set(); - w.stopped().set(); - w + let r = T::regs(); + + if r.events_started.read().bits() == 0 { + return Err(Error::NotRunning); + } + + let drop = OnDrop::new(move || { + r.intenclr.write(|w| w.end().clear()); + r.events_stopped.reset(); + + // reset to dummy buffer + r.sample + .ptr + .write(|w| unsafe { w.sampleptr().bits(DUMMY_BUFFER.as_ptr() as u32) }); + r.sample + .maxcnt + .write(|w| unsafe { w.buffsize().bits(DUMMY_BUFFER.len() as _) }); + + while r.events_stopped.read().bits() == 0 {} }); - // Don't reorder the start event before the previous writes. Hopefully self - // wouldn't happen anyway. + // setup user buffer + let ptr = buffer.as_ptr(); + let len = buffer.len(); + r.sample.ptr.write(|w| unsafe { w.sampleptr().bits(ptr as u32) }); + r.sample.maxcnt.write(|w| unsafe { w.buffsize().bits(len as _) }); + + // wait till the current sample is finished and the user buffer sample is started + Self::wait_for_sample().await; + + // reset the buffer back to the dummy buffer + r.sample + .ptr + .write(|w| unsafe { w.sampleptr().bits(DUMMY_BUFFER.as_ptr() as u32) }); + r.sample + .maxcnt + .write(|w| unsafe { w.buffsize().bits(DUMMY_BUFFER.len() as _) }); + + // wait till the user buffer is sampled + Self::wait_for_sample().await; + + drop.defuse(); + + Ok(()) + } + + async fn wait_for_sample() { + let r = T::regs(); + + r.events_end.reset(); + r.intenset.write(|w| w.end().set()); + compiler_fence(Ordering::SeqCst); - r.tasks_start.write(|w| w.tasks_start().set_bit()); - - let ondrop = OnDrop::new(|| { - r.tasks_stop.write(|w| w.tasks_stop().set_bit()); - // N.B. It would be better if this were async, but Drop only support sync code. - while r.events_stopped.read().bits() != 0 {} - }); - - // Wait for 'end' event. poll_fn(|cx| { - let r = Self::regs(); - - WAKER.register(cx.waker()); - + T::state().waker.register(cx.waker()); if r.events_end.read().bits() != 0 { - compiler_fence(Ordering::SeqCst); - // END means the whole buffer has been received. - r.events_end.reset(); - r.intenset.write(|w| w.end().set()); - - if discard > 0 { - discard -= 1; - } else { - // Note that the beginning of the buffer might be overwritten before the task fully stops :( - r.tasks_stop.write(|w| w.tasks_stop().set_bit()); - } - } - if r.events_stopped.read().bits() != 0 { return Poll::Ready(()); } - Poll::Pending }) .await; - ondrop.defuse(); + + compiler_fence(Ordering::SeqCst); } /// Continuous sampling with double buffers. /// - /// A TIMER and two PPI peripherals are passed in so that precise sampling - /// can be attained. The sampling interval is expressed by selecting a - /// timer clock frequency to use along with a counter threshold to be reached. - /// For example, 1KHz can be achieved using a frequency of 1MHz and a counter - /// threshold of 1000. - /// /// A sampler closure is provided that receives the buffer of samples, noting /// that the size of this buffer can be less than the original buffer's size. /// A command is return from the closure that indicates whether the sampling @@ -226,10 +258,14 @@ impl<'d> Pdm<'d> { &mut self, bufs: &mut [[i16; N]; 2], mut sampler: S, - ) where + ) -> Result<(), Error> where S: FnMut(&[i16; N]) -> SamplerState, { - let r = Self::regs(); + let r = T::regs(); + + if r.events_started.read().bits() != 0 { + return Err(Error::AlreadyRunning); + } r.sample.ptr.write(|w| unsafe { w.sampleptr().bits(bufs[0].as_mut_ptr() as u32) }); r.sample.maxcnt.write(|w| unsafe { w.buffsize().bits(N as _) }); @@ -255,7 +291,7 @@ impl<'d> Pdm<'d> { let mut done = false; - let ondrop = OnDrop::new(|| { + let drop = OnDrop::new(|| { r.tasks_stop.write(|w| w.tasks_stop().set_bit()); // N.B. It would be better if this were async, but Drop only support sync code. while r.events_stopped.read().bits() != 0 {} @@ -263,9 +299,9 @@ impl<'d> Pdm<'d> { // Wait for events and complete when the sampler indicates it has had enough. poll_fn(|cx| { - let r = Self::regs(); + let r = T::regs(); - WAKER.register(cx.waker()); + T::state().waker.register(cx.waker()); if r.events_end.read().bits() != 0 { compiler_fence(Ordering::SeqCst); @@ -301,43 +337,130 @@ impl<'d> Pdm<'d> { Poll::Pending }) .await; - ondrop.defuse(); + drop.defuse(); + Ok(()) } } -impl<'d> Drop for Pdm<'d> { - fn drop(&mut self) { - let r = Self::regs(); - r.enable.write(|w| w.enable().disabled()); +/// PDM microphone driver Config +pub struct Config { + /// Use stero or mono operation + pub operation_mode: OperationMode, + /// On which edge the left channel should be samples + pub edge: Edge, + /// Clock frequency + pub frequency: Frequency, + /// Clock ratio + pub ratio: Ratio, + /// Gain left in dB + pub gain_left: I7F1, + /// Gain right in dB + pub gain_right: I7F1, +} + +impl Default for Config { + fn default() -> Self { + Self { + operation_mode: OperationMode::Mono, + edge: Edge::LeftFalling, + frequency: Frequency::DEFAULT, + ratio: Ratio::RATIO80, + gain_left: I7F1::ZERO, + gain_right: I7F1::ZERO, + } } } -#[derive(Clone, Copy, PartialEq)] +/// PDM operation mode. +#[derive(PartialEq)] +pub enum OperationMode { + /// Mono (1 channel) + Mono, + /// Stereo (2 channels) + Stereo, +} + +impl From for OPERATION_A { + fn from(mode: OperationMode) -> Self { + match mode { + OperationMode::Mono => OPERATION_A::MONO, + OperationMode::Stereo => OPERATION_A::STEREO, + } + } +} + +/// PDM edge polarity +#[derive(PartialEq)] pub enum Edge { - FallingEdge, - RisingEdge, + /// Left edge is rising + LeftRising, + /// Left edge is falling + LeftFalling, } impl From for EDGE_A { fn from(edge: Edge) -> Self { match edge { - Edge::FallingEdge => EDGE_A::LEFTFALLING, - Edge::RisingEdge => EDGE_A::LEFTRISING, + Edge::LeftRising => EDGE_A::LEFT_RISING, + Edge::LeftFalling => EDGE_A::LEFT_FALLING, } } } -#[derive(Clone, Copy, PartialEq)] -pub enum Channels { - Stereo, - Mono, +impl<'d, T: Instance> Drop for Pdm<'d, T> { + fn drop(&mut self) { + let r = T::regs(); + + r.tasks_stop.write(|w| unsafe { w.bits(1) }); + + r.enable.write(|w| w.enable().disabled()); + + r.psel.din.reset(); + r.psel.clk.reset(); + } } -impl From for OPERATION_A { - fn from(ch: Channels) -> Self { - match ch { - Channels::Stereo => OPERATION_A::STEREO, - Channels::Mono => OPERATION_A::MONO, +pub(crate) mod sealed { + use embassy_sync::waitqueue::AtomicWaker; + + /// Peripheral static state + pub struct State { + pub waker: AtomicWaker, + } + + impl State { + pub const fn new() -> Self { + Self { + waker: AtomicWaker::new(), + } } } + + pub trait Instance { + fn regs() -> &'static crate::pac::pdm::RegisterBlock; + fn state() -> &'static State; + } +} + +/// PDM peripheral instance. +pub trait Instance: Peripheral

+ sealed::Instance + 'static + Send { + /// Interrupt for this peripheral. + type Interrupt: interrupt::typelevel::Interrupt; +} + +macro_rules! impl_pdm { + ($type:ident, $pac_type:ident, $irq:ident) => { + impl crate::pdm::sealed::Instance for peripherals::$type { + fn regs() -> &'static crate::pac::pdm::RegisterBlock { + unsafe { &*pac::$pac_type::ptr() } + } + fn state() -> &'static crate::pdm::sealed::State { + static STATE: crate::pdm::sealed::State = crate::pdm::sealed::State::new(); + &STATE + } + } + impl crate::pdm::Instance for peripherals::$type { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; } \ No newline at end of file diff --git a/embassy-nrf/src/ppi/dppi.rs b/embassy-nrf/src/ppi/dppi.rs index de856c0ca..40ccb2f09 100644 --- a/embassy-nrf/src/ppi/dppi.rs +++ b/embassy-nrf/src/ppi/dppi.rs @@ -6,18 +6,20 @@ use crate::{pac, Peripheral}; const DPPI_ENABLE_BIT: u32 = 0x8000_0000; const DPPI_CHANNEL_MASK: u32 = 0x0000_00FF; -fn regs() -> &'static pac::dppic::RegisterBlock { +pub(crate) fn regs() -> &'static pac::dppic::RegisterBlock { unsafe { &*pac::DPPIC::ptr() } } impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 1> { - pub fn new_one_to_one(ch: impl Peripheral

+ 'd, event: Event, task: Task) -> Self { + /// Configure PPI channel to trigger `task` on `event`. + pub fn new_one_to_one(ch: impl Peripheral

+ 'd, event: Event<'d>, task: Task<'d>) -> Self { Ppi::new_many_to_many(ch, [event], [task]) } } impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 2> { - pub fn new_one_to_two(ch: impl Peripheral

+ 'd, event: Event, task1: Task, task2: Task) -> Self { + /// Configure PPI channel to trigger both `task1` and `task2` on `event`. + pub fn new_one_to_two(ch: impl Peripheral

+ 'd, event: Event<'d>, task1: Task<'d>, task2: Task<'d>) -> Self { Ppi::new_many_to_many(ch, [event], [task1, task2]) } } @@ -25,10 +27,11 @@ impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 2> { impl<'d, C: ConfigurableChannel, const EVENT_COUNT: usize, const TASK_COUNT: usize> Ppi<'d, C, EVENT_COUNT, TASK_COUNT> { + /// Configure a DPPI channel to trigger all `tasks` when any of the `events` fires. pub fn new_many_to_many( ch: impl Peripheral

+ 'd, - events: [Event; EVENT_COUNT], - tasks: [Task; TASK_COUNT], + events: [Event<'d>; EVENT_COUNT], + tasks: [Task<'d>; TASK_COUNT], ) -> Self { into_ref!(ch); diff --git a/embassy-nrf/src/ppi/mod.rs b/embassy-nrf/src/ppi/mod.rs index 8f5ed14cd..ff6593bd5 100644 --- a/embassy-nrf/src/ppi/mod.rs +++ b/embassy-nrf/src/ppi/mod.rs @@ -1,6 +1,6 @@ #![macro_use] -//! HAL interface for the PPI and DPPI peripheral. +//! Programmable Peripheral Interconnect (PPI/DPPI) driver. //! //! The (Distributed) Programmable Peripheral Interconnect interface allows for an autonomous interoperability //! between peripherals through their events and tasks. There are fixed PPI channels and fully @@ -15,24 +15,107 @@ //! many tasks and events, but any single task or event can only be coupled with one channel. //! +use core::marker::PhantomData; use core::ptr::NonNull; -use embassy_hal_common::{impl_peripheral, PeripheralRef}; +use embassy_hal_common::{impl_peripheral, into_ref, PeripheralRef}; use crate::{peripherals, Peripheral}; -#[cfg(feature = "_dppi")] -mod dppi; -#[cfg(feature = "_ppi")] -mod ppi; +#[cfg_attr(feature = "_dppi", path = "dppi.rs")] +#[cfg_attr(feature = "_ppi", path = "ppi.rs")] +mod _version; +pub(crate) use _version::*; -/// An instance of the Programmable peripheral interconnect on nRF devices. +/// PPI channel driver. pub struct Ppi<'d, C: Channel, const EVENT_COUNT: usize, const TASK_COUNT: usize> { ch: PeripheralRef<'d, C>, #[cfg(feature = "_dppi")] - events: [Event; EVENT_COUNT], + events: [Event<'d>; EVENT_COUNT], #[cfg(feature = "_dppi")] - tasks: [Task; TASK_COUNT], + tasks: [Task<'d>; TASK_COUNT], +} + +/// PPI channel group driver. +pub struct PpiGroup<'d, G: Group> { + g: PeripheralRef<'d, G>, +} + +impl<'d, G: Group> PpiGroup<'d, G> { + /// Create a new PPI group driver. + /// + /// The group is initialized as containing no channels. + pub fn new(g: impl Peripheral

+ 'd) -> Self { + into_ref!(g); + + let r = regs(); + let n = g.number(); + r.chg[n].write(|w| unsafe { w.bits(0) }); + + Self { g } + } + + /// Add a PPI channel to this group. + /// + /// If the channel is already in the group, this is a no-op. + pub fn add_channel( + &mut self, + ch: &Ppi<'_, C, EVENT_COUNT, TASK_COUNT>, + ) { + let r = regs(); + let ng = self.g.number(); + let nc = ch.ch.number(); + r.chg[ng].modify(|r, w| unsafe { w.bits(r.bits() | 1 << nc) }); + } + + /// Remove a PPI channel from this group. + /// + /// If the channel is already not in the group, this is a no-op. + pub fn remove_channel( + &mut self, + ch: &Ppi<'_, C, EVENT_COUNT, TASK_COUNT>, + ) { + let r = regs(); + let ng = self.g.number(); + let nc = ch.ch.number(); + r.chg[ng].modify(|r, w| unsafe { w.bits(r.bits() & !(1 << nc)) }); + } + + /// Enable all the channels in this group. + pub fn enable_all(&mut self) { + let n = self.g.number(); + regs().tasks_chg[n].en.write(|w| unsafe { w.bits(1) }); + } + + /// Disable all the channels in this group. + pub fn disable_all(&mut self) { + let n = self.g.number(); + regs().tasks_chg[n].dis.write(|w| unsafe { w.bits(1) }); + } + + /// Get a reference to the "enable all" task. + /// + /// When triggered, it will enable all the channels in this group. + pub fn task_enable_all(&self) -> Task<'d> { + let n = self.g.number(); + Task::from_reg(®s().tasks_chg[n].en) + } + + /// Get a reference to the "disable all" task. + /// + /// When triggered, it will disable all the channels in this group. + pub fn task_disable_all(&self) -> Task<'d> { + let n = self.g.number(); + Task::from_reg(®s().tasks_chg[n].dis) + } +} + +impl<'d, G: Group> Drop for PpiGroup<'d, G> { + fn drop(&mut self) { + let r = regs(); + let n = self.g.number(); + r.chg[n].write(|w| unsafe { w.bits(0) }); + } } #[cfg(feature = "_dppi")] @@ -43,20 +126,28 @@ const REGISTER_DPPI_CONFIG_OFFSET: usize = 0x80 / core::mem::size_of::(); /// When a task is subscribed to a PPI channel, it will run when the channel is triggered by /// a published event. #[derive(PartialEq, Eq, Clone, Copy)] -pub struct Task(NonNull); +pub struct Task<'d>(NonNull, PhantomData<&'d ()>); -impl Task { +impl<'d> Task<'d> { /// Create a new `Task` from a task register pointer /// /// # Safety /// /// `ptr` must be a pointer to a valid `TASKS_*` register from an nRF peripheral. pub unsafe fn new_unchecked(ptr: NonNull) -> Self { - Self(ptr) + Self(ptr, PhantomData) + } + + /// Triggers this task. + pub fn trigger(&mut self) { + unsafe { self.0.as_ptr().write_volatile(1) }; } pub(crate) fn from_reg(reg: &T) -> Self { - Self(unsafe { NonNull::new_unchecked(reg as *const _ as *mut _) }) + Self( + unsafe { NonNull::new_unchecked(reg as *const _ as *mut _) }, + PhantomData, + ) } /// Address of subscription register for this task. @@ -69,26 +160,39 @@ impl Task { /// # Safety /// /// NonNull is not send, but this event is only allowed to point at registers and those exist in any context on the same core. -unsafe impl Send for Task {} +unsafe impl Send for Task<'_> {} /// Represents an event that a peripheral can publish. /// /// An event can be set to publish on a PPI channel when the event happens. #[derive(PartialEq, Eq, Clone, Copy)] -pub struct Event(NonNull); +pub struct Event<'d>(NonNull, PhantomData<&'d ()>); -impl Event { +impl<'d> Event<'d> { /// Create a new `Event` from an event register pointer /// /// # Safety /// /// `ptr` must be a pointer to a valid `EVENTS_*` register from an nRF peripheral. pub unsafe fn new_unchecked(ptr: NonNull) -> Self { - Self(ptr) + Self(ptr, PhantomData) } - pub(crate) fn from_reg(reg: &T) -> Self { - Self(unsafe { NonNull::new_unchecked(reg as *const _ as *mut _) }) + pub(crate) fn from_reg(reg: &'d T) -> Self { + Self( + unsafe { NonNull::new_unchecked(reg as *const _ as *mut _) }, + PhantomData, + ) + } + + /// Describes whether this Event is currently in a triggered state. + pub fn is_triggered(&self) -> bool { + unsafe { self.0.as_ptr().read_volatile() == 1 } + } + + /// Clear the current register's triggered state, reverting it to 0. + pub fn clear(&mut self) { + unsafe { self.0.as_ptr().write_volatile(0) }; } /// Address of publish register for this event. @@ -101,7 +205,7 @@ impl Event { /// # Safety /// /// NonNull is not send, but this event is only allowed to point at registers and those exist in any context on the same core. -unsafe impl Send for Event {} +unsafe impl Send for Event<'_> {} // ====================== // traits @@ -112,7 +216,7 @@ pub(crate) mod sealed { } /// Interface for PPI channels. -pub trait Channel: sealed::Channel + Peripheral

+ Sized { +pub trait Channel: sealed::Channel + Peripheral

+ Sized + 'static { /// Returns the number of the channel fn number(&self) -> usize; } @@ -130,7 +234,7 @@ pub trait StaticChannel: Channel + Into { } /// Interface for a group of PPI channels. -pub trait Group: sealed::Group + Sized { +pub trait Group: sealed::Group + Peripheral

+ Into + Sized + 'static { /// Returns the number of the group. fn number(&self) -> usize; /// Convert into a type erased group. @@ -248,6 +352,12 @@ macro_rules! impl_group { $number } } + + impl From for crate::ppi::AnyGroup { + fn from(val: peripherals::$type) -> Self { + crate::ppi::Group::degrade(val) + } + } }; } diff --git a/embassy-nrf/src/ppi/ppi.rs b/embassy-nrf/src/ppi/ppi.rs index 19abc4e18..1fe898625 100644 --- a/embassy-nrf/src/ppi/ppi.rs +++ b/embassy-nrf/src/ppi/ppi.rs @@ -3,18 +3,18 @@ use embassy_hal_common::into_ref; use super::{Channel, ConfigurableChannel, Event, Ppi, StaticChannel, Task}; use crate::{pac, Peripheral}; -impl Task { +impl<'d> Task<'d> { fn reg_val(&self) -> u32 { self.0.as_ptr() as _ } } -impl Event { +impl<'d> Event<'d> { fn reg_val(&self) -> u32 { self.0.as_ptr() as _ } } -fn regs() -> &'static pac::ppi::RegisterBlock { +pub(crate) fn regs() -> &'static pac::ppi::RegisterBlock { unsafe { &*pac::PPI::ptr() } } @@ -34,7 +34,7 @@ impl<'d, C: StaticChannel> Ppi<'d, C, 0, 1> { impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 1> { /// Configure PPI channel to trigger `task` on `event`. - pub fn new_one_to_one(ch: impl Peripheral

+ 'd, event: Event, task: Task) -> Self { + pub fn new_one_to_one(ch: impl Peripheral

+ 'd, event: Event<'d>, task: Task<'d>) -> Self { into_ref!(ch); let r = regs(); @@ -48,8 +48,8 @@ impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 1> { #[cfg(not(feature = "nrf51"))] // Not for nrf51 because of the fork task impl<'d, C: ConfigurableChannel> Ppi<'d, C, 1, 2> { - /// Configure PPI channel to trigger `task1` and `task2` on `event`. - pub fn new_one_to_two(ch: impl Peripheral

+ 'd, event: Event, task1: Task, task2: Task) -> Self { + /// Configure PPI channel to trigger both `task1` and `task2` on `event`. + pub fn new_one_to_two(ch: impl Peripheral

+ 'd, event: Event<'d>, task1: Task<'d>, task2: Task<'d>) -> Self { into_ref!(ch); let r = regs(); diff --git a/embassy-nrf/src/pwm.rs b/embassy-nrf/src/pwm.rs index 5f750a91e..c8c81fa01 100644 --- a/embassy-nrf/src/pwm.rs +++ b/embassy-nrf/src/pwm.rs @@ -1,3 +1,5 @@ +//! Pulse Width Modulation (PWM) driver. + #![macro_use] use core::sync::atomic::{compiler_fence, Ordering}; @@ -6,10 +8,9 @@ use embassy_hal_common::{into_ref, PeripheralRef}; use crate::gpio::sealed::Pin as _; use crate::gpio::{AnyPin, Pin as GpioPin, PselBits}; -use crate::interrupt::Interrupt; use crate::ppi::{Event, Task}; use crate::util::slice_in_ram_or; -use crate::{pac, Peripheral}; +use crate::{interrupt, pac, Peripheral}; /// SimplePwm is the traditional pwm interface you're probably used to, allowing /// to simply set a duty cycle across up to four channels. @@ -32,6 +33,7 @@ pub struct SequencePwm<'d, T: Instance> { ch3: Option>, } +/// PWM error #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] @@ -41,7 +43,7 @@ pub enum Error { /// Min Sequence count is 1 SequenceTimesAtLeastOne, /// EasyDMA can only read from data memory, read only buffers in flash will fail. - DMABufferNotInDataMemory, + BufferNotInRAM, } const MAX_SEQUENCE_LEN: usize = 32767; @@ -179,7 +181,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> { /// Returns reference to `Stopped` event endpoint for PPI. #[inline(always)] - pub fn event_stopped(&self) -> Event { + pub fn event_stopped(&self) -> Event<'d> { let r = T::regs(); Event::from_reg(&r.events_stopped) @@ -187,7 +189,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> { /// Returns reference to `LoopsDone` event endpoint for PPI. #[inline(always)] - pub fn event_loops_done(&self) -> Event { + pub fn event_loops_done(&self) -> Event<'d> { let r = T::regs(); Event::from_reg(&r.events_loopsdone) @@ -195,7 +197,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> { /// Returns reference to `PwmPeriodEnd` event endpoint for PPI. #[inline(always)] - pub fn event_pwm_period_end(&self) -> Event { + pub fn event_pwm_period_end(&self) -> Event<'d> { let r = T::regs(); Event::from_reg(&r.events_pwmperiodend) @@ -203,7 +205,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> { /// Returns reference to `Seq0 End` event endpoint for PPI. #[inline(always)] - pub fn event_seq_end(&self) -> Event { + pub fn event_seq_end(&self) -> Event<'d> { let r = T::regs(); Event::from_reg(&r.events_seqend[0]) @@ -211,7 +213,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> { /// Returns reference to `Seq1 End` event endpoint for PPI. #[inline(always)] - pub fn event_seq1_end(&self) -> Event { + pub fn event_seq1_end(&self) -> Event<'d> { let r = T::regs(); Event::from_reg(&r.events_seqend[1]) @@ -219,7 +221,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> { /// Returns reference to `Seq0 Started` event endpoint for PPI. #[inline(always)] - pub fn event_seq0_started(&self) -> Event { + pub fn event_seq0_started(&self) -> Event<'d> { let r = T::regs(); Event::from_reg(&r.events_seqstarted[0]) @@ -227,7 +229,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> { /// Returns reference to `Seq1 Started` event endpoint for PPI. #[inline(always)] - pub fn event_seq1_started(&self) -> Event { + pub fn event_seq1_started(&self) -> Event<'d> { let r = T::regs(); Event::from_reg(&r.events_seqstarted[1]) @@ -238,7 +240,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> { /// /// Interacting with the sequence while it runs puts it in an unknown state #[inline(always)] - pub unsafe fn task_start_seq0(&self) -> Task { + pub unsafe fn task_start_seq0(&self) -> Task<'d> { let r = T::regs(); Task::from_reg(&r.tasks_seqstart[0]) @@ -249,7 +251,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> { /// /// Interacting with the sequence while it runs puts it in an unknown state #[inline(always)] - pub unsafe fn task_start_seq1(&self) -> Task { + pub unsafe fn task_start_seq1(&self) -> Task<'d> { let r = T::regs(); Task::from_reg(&r.tasks_seqstart[1]) @@ -260,7 +262,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> { /// /// Interacting with the sequence while it runs puts it in an unknown state #[inline(always)] - pub unsafe fn task_next_step(&self) -> Task { + pub unsafe fn task_next_step(&self) -> Task<'d> { let r = T::regs(); Task::from_reg(&r.tasks_nextstep) @@ -271,7 +273,7 @@ impl<'d, T: Instance> SequencePwm<'d, T> { /// /// Interacting with the sequence while it runs puts it in an unknown state #[inline(always)] - pub unsafe fn task_stop(&self) -> Task { + pub unsafe fn task_stop(&self) -> Task<'d> { let r = T::regs(); Task::from_reg(&r.tasks_stop) @@ -358,6 +360,7 @@ pub struct Sequence<'s> { } impl<'s> Sequence<'s> { + /// Create a new `Sequence` pub fn new(words: &'s [u16], config: SequenceConfig) -> Self { Self { words, config } } @@ -367,7 +370,7 @@ impl<'s> Sequence<'s> { /// Takes at one sequence along with its configuration. #[non_exhaustive] pub struct SingleSequencer<'d, 's, T: Instance> { - pub sequencer: Sequencer<'d, 's, T>, + sequencer: Sequencer<'d, 's, T>, } impl<'d, 's, T: Instance> SingleSequencer<'d, 's, T> { @@ -428,8 +431,8 @@ impl<'d, 's, T: Instance> Sequencer<'d, 's, T> { let sequence0 = &self.sequence0; let alt_sequence = self.sequence1.as_ref().unwrap_or(&self.sequence0); - slice_in_ram_or(sequence0.words, Error::DMABufferNotInDataMemory)?; - slice_in_ram_or(alt_sequence.words, Error::DMABufferNotInDataMemory)?; + slice_in_ram_or(sequence0.words, Error::BufferNotInRAM)?; + slice_in_ram_or(alt_sequence.words, Error::BufferNotInRAM)?; if sequence0.words.len() > MAX_SEQUENCE_LEN || alt_sequence.words.len() > MAX_SEQUENCE_LEN { return Err(Error::SequenceTooLong); @@ -536,13 +539,21 @@ pub enum SequenceMode { /// PWM Base clock is system clock (16MHz) divided by prescaler #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum Prescaler { + /// Divide by 1 Div1, + /// Divide by 2 Div2, + /// Divide by 4 Div4, + /// Divide by 8 Div8, + /// Divide by 16 Div16, + /// Divide by 32 Div32, + /// Divide by 64 Div64, + /// Divide by 128 Div128, } @@ -828,8 +839,10 @@ pub(crate) mod sealed { } } +/// PWM peripheral instance. pub trait Instance: Peripheral

+ sealed::Instance + 'static { - type Interrupt: Interrupt; + /// Interrupt for this peripheral. + type Interrupt: interrupt::typelevel::Interrupt; } macro_rules! impl_pwm { @@ -840,7 +853,7 @@ macro_rules! impl_pwm { } } impl crate::pwm::Instance for peripherals::$type { - type Interrupt = crate::interrupt::$irq; + type Interrupt = crate::interrupt::typelevel::$irq; } }; } diff --git a/embassy-nrf/src/qdec.rs b/embassy-nrf/src/qdec.rs index 762e09715..8bac87d37 100644 --- a/embassy-nrf/src/qdec.rs +++ b/embassy-nrf/src/qdec.rs @@ -1,28 +1,35 @@ -//! Quadrature decoder interface +//! Quadrature decoder (QDEC) driver. +#![macro_use] + +use core::future::poll_fn; +use core::marker::PhantomData; use core::task::Poll; use embassy_hal_common::{into_ref, PeripheralRef}; -use embassy_sync::waitqueue::AtomicWaker; -use futures::future::poll_fn; use crate::gpio::sealed::Pin as _; use crate::gpio::{AnyPin, Pin as GpioPin}; -use crate::interrupt::InterruptExt; -use crate::peripherals::QDEC; -use crate::{interrupt, pac, Peripheral}; +use crate::interrupt::typelevel::Interrupt; +use crate::{interrupt, Peripheral}; -/// Quadrature decoder -pub struct Qdec<'d> { - _p: PeripheralRef<'d, QDEC>, +/// Quadrature decoder driver. +pub struct Qdec<'d, T: Instance> { + _p: PeripheralRef<'d, T>, } +/// QDEC config #[non_exhaustive] pub struct Config { + /// Number of samples pub num_samples: NumSamples, + /// Sample period pub period: SamplePeriod, + /// Set LED output pin polarity pub led_polarity: LedPolarity, + /// Enable/disable input debounce filters pub debounce: bool, + /// Time period the LED is switched ON prior to sampling (0..511 us). pub led_pre_usecs: u16, } @@ -38,42 +45,52 @@ impl Default for Config { } } -static WAKER: AtomicWaker = AtomicWaker::new(); +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} -impl<'d> Qdec<'d> { +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + T::regs().intenclr.write(|w| w.reportrdy().clear()); + T::state().waker.wake(); + } +} + +impl<'d, T: Instance> Qdec<'d, T> { + /// Create a new QDEC. pub fn new( - qdec: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, + qdec: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, a: impl Peripheral

+ 'd, b: impl Peripheral

+ 'd, config: Config, ) -> Self { - into_ref!(a, b); - Self::new_inner(qdec, irq, a.map_into(), b.map_into(), None, config) + into_ref!(qdec, a, b); + Self::new_inner(qdec, a.map_into(), b.map_into(), None, config) } + /// Create a new QDEC, with a pin for LED output. pub fn new_with_led( - qdec: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, + qdec: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, a: impl Peripheral

+ 'd, b: impl Peripheral

+ 'd, led: impl Peripheral

+ 'd, config: Config, ) -> Self { - into_ref!(a, b, led); - Self::new_inner(qdec, irq, a.map_into(), b.map_into(), Some(led.map_into()), config) + into_ref!(qdec, a, b, led); + Self::new_inner(qdec, a.map_into(), b.map_into(), Some(led.map_into()), config) } fn new_inner( - p: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, + p: PeripheralRef<'d, T>, a: PeripheralRef<'d, AnyPin>, b: PeripheralRef<'d, AnyPin>, led: Option>, config: Config, ) -> Self { - into_ref!(p, irq); - let r = Self::regs(); + let r = T::regs(); // Select pins. a.conf().write(|w| w.input().connect().pull().pullup()); @@ -116,20 +133,15 @@ impl<'d> Qdec<'d> { SamplePeriod::_131ms => w.sampleper()._131ms(), }); + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + // Enable peripheral r.enable.write(|w| w.enable().set_bit()); // Start sampling unsafe { r.tasks_start.write(|w| w.bits(1)) }; - irq.disable(); - irq.set_handler(|_| { - let r = Self::regs(); - r.intenclr.write(|w| w.reportrdy().clear()); - WAKER.wake(); - }); - irq.enable(); - Self { _p: p } } @@ -141,18 +153,27 @@ impl<'d> Qdec<'d> { /// # Example /// /// ```no_run - /// let irq = interrupt::take!(QDEC); + /// use embassy_nrf::qdec::{self, Qdec}; + /// use embassy_nrf::{bind_interrupts, peripherals}; + /// + /// bind_interrupts!(struct Irqs { + /// QDEC => qdec::InterruptHandler; + /// }); + /// + /// # async { + /// # let p: embassy_nrf::Peripherals = todo!(); /// let config = qdec::Config::default(); - /// let mut q = Qdec::new(p.QDEC, p.P0_31, p.P0_30, config); + /// let mut q = Qdec::new(p.QDEC, Irqs, p.P0_31, p.P0_30, config); /// let delta = q.read().await; + /// # }; /// ``` pub async fn read(&mut self) -> i16 { - let t = Self::regs(); + let t = T::regs(); t.intenset.write(|w| w.reportrdy().set()); unsafe { t.tasks_readclracc.write(|w| w.bits(1)) }; let value = poll_fn(|cx| { - WAKER.register(cx.waker()); + T::state().waker.register(cx.waker()); if t.events_reportrdy.read().bits() == 0 { return Poll::Pending; } else { @@ -164,42 +185,108 @@ impl<'d> Qdec<'d> { .await; value } - - fn regs() -> &'static pac::qdec::RegisterBlock { - unsafe { &*pac::QDEC::ptr() } - } } +/// Sample period #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum SamplePeriod { + /// 128 us _128us, + /// 256 us _256us, + /// 512 us _512us, + /// 1024 us _1024us, + /// 2048 us _2048us, + /// 4096 us _4096us, + /// 8192 us _8192us, + /// 16384 us _16384us, + /// 32 ms _32ms, + /// 65 ms _65ms, + /// 131 ms _131ms, } +/// Number of samples taken. #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum NumSamples { + /// 10 samples _10smpl, + /// 40 samples _40smpl, + /// 80 samples _80smpl, + /// 120 samples _120smpl, + /// 160 samples _160smpl, + /// 200 samples _200smpl, + /// 240 samples _240smpl, + /// 280 samples _280smpl, + /// 1 sample _1smpl, } +/// LED polarity #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum LedPolarity { + /// Active high (a high output turns on the LED). ActiveHigh, + /// Active low (a low output turns on the LED). ActiveLow, } + +pub(crate) mod sealed { + use embassy_sync::waitqueue::AtomicWaker; + + /// Peripheral static state + pub struct State { + pub waker: AtomicWaker, + } + + impl State { + pub const fn new() -> Self { + Self { + waker: AtomicWaker::new(), + } + } + } + + pub trait Instance { + fn regs() -> &'static crate::pac::qdec::RegisterBlock; + fn state() -> &'static State; + } +} + +/// qdec peripheral instance. +pub trait Instance: Peripheral

+ sealed::Instance + 'static + Send { + /// Interrupt for this peripheral. + type Interrupt: interrupt::typelevel::Interrupt; +} + +macro_rules! impl_qdec { + ($type:ident, $pac_type:ident, $irq:ident) => { + impl crate::qdec::sealed::Instance for peripherals::$type { + fn regs() -> &'static crate::pac::qdec::RegisterBlock { + unsafe { &*pac::$pac_type::ptr() } + } + fn state() -> &'static crate::qdec::sealed::State { + static STATE: crate::qdec::sealed::State = crate::qdec::sealed::State::new(); + &STATE + } + } + impl crate::qdec::Instance for peripherals::$type { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +} diff --git a/embassy-nrf/src/qspi.rs b/embassy-nrf/src/qspi.rs index c97cb1656..baefc7967 100644 --- a/embassy-nrf/src/qspi.rs +++ b/embassy-nrf/src/qspi.rs @@ -1,20 +1,25 @@ +//! Quad Serial Peripheral Interface (QSPI) flash driver. + #![macro_use] +use core::future::poll_fn; +use core::marker::PhantomData; use core::ptr; use core::task::Poll; -use embassy_hal_common::drop::DropBomb; +use embassy_hal_common::drop::OnDrop; use embassy_hal_common::{into_ref, PeripheralRef}; -use futures::future::poll_fn; +use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; use crate::gpio::{self, Pin as GpioPin}; -use crate::interrupt::{Interrupt, InterruptExt}; +use crate::interrupt::typelevel::Interrupt; pub use crate::pac::qspi::ifconfig0::{ ADDRMODE_A as AddressMode, PPSIZE_A as WritePageSize, READOC_A as ReadOpcode, WRITEOC_A as WriteOpcode, }; pub use crate::pac::qspi::ifconfig1::SPIMODE_A as SpiMode; -use crate::{pac, Peripheral}; +use crate::{interrupt, Peripheral}; +/// Deep power-down config. pub struct DeepPowerDownConfig { /// Time required for entering DPM, in units of 16us pub enter_time: u16, @@ -22,38 +27,65 @@ pub struct DeepPowerDownConfig { pub exit_time: u16, } +/// QSPI bus frequency. pub enum Frequency { + /// 32 Mhz M32 = 0, + /// 16 Mhz M16 = 1, + /// 10.7 Mhz M10_7 = 2, + /// 8 Mhz M8 = 3, + /// 6.4 Mhz M6_4 = 4, + /// 5.3 Mhz M5_3 = 5, + /// 4.6 Mhz M4_6 = 6, + /// 4 Mhz M4 = 7, + /// 3.6 Mhz M3_6 = 8, + /// 3.2 Mhz M3_2 = 9, + /// 2.9 Mhz M2_9 = 10, + /// 2.7 Mhz M2_7 = 11, + /// 2.5 Mhz M2_5 = 12, + /// 2.3 Mhz M2_3 = 13, + /// 2.1 Mhz M2_1 = 14, + /// 2 Mhz M2 = 15, } +/// QSPI config. #[non_exhaustive] pub struct Config { + /// XIP offset. pub xip_offset: u32, + /// Opcode used for read operations. pub read_opcode: ReadOpcode, + /// Opcode used for write operations. pub write_opcode: WriteOpcode, + /// Page size for write operations. pub write_page_size: WritePageSize, + /// Configuration for deep power down. If None, deep power down is disabled. pub deep_power_down: Option, + /// QSPI bus frequency. pub frequency: Frequency, /// Value is specified in number of 16 MHz periods (62.5 ns) pub sck_delay: u8, /// Whether data is captured on the clock rising edge and data is output on a falling edge (MODE0) or vice-versa (MODE3) pub spi_mode: SpiMode, + /// Addressing mode (24-bit or 32-bit) pub address_mode: AddressMode, + /// Flash memory capacity in bytes. This is the value reported by the `embedded-storage` traits. + pub capacity: u32, } impl Default for Config { @@ -68,27 +100,50 @@ impl Default for Config { sck_delay: 80, spi_mode: SpiMode::MODE0, address_mode: AddressMode::_24BIT, + capacity: 0, } } } +/// Error #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum Error { + /// Operation address was out of bounds. OutOfBounds, // TODO add "not in data memory" error and check for it } -pub struct Qspi<'d, T: Instance, const FLASH_SIZE: usize> { - irq: PeripheralRef<'d, T::Interrupt>, - dpm_enabled: bool, +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, } -impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> { +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let r = T::regs(); + let s = T::state(); + + if r.events_ready.read().bits() != 0 { + s.waker.wake(); + r.intenclr.write(|w| w.ready().clear()); + } + } +} + +/// QSPI flash driver. +pub struct Qspi<'d, T: Instance> { + _peri: PeripheralRef<'d, T>, + dpm_enabled: bool, + capacity: u32, +} + +impl<'d, T: Instance> Qspi<'d, T> { + /// Create a new QSPI driver. pub fn new( - _qspi: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, + qspi: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, sck: impl Peripheral

+ 'd, csn: impl Peripheral

+ 'd, io0: impl Peripheral

+ 'd, @@ -96,30 +151,31 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> { io2: impl Peripheral

+ 'd, io3: impl Peripheral

+ 'd, config: Config, - ) -> Qspi<'d, T, FLASH_SIZE> { - into_ref!(irq, sck, csn, io0, io1, io2, io3); + ) -> Self { + into_ref!(qspi, sck, csn, io0, io1, io2, io3); let r = T::regs(); - sck.set_high(); - csn.set_high(); - io0.set_high(); - io1.set_high(); - io2.set_high(); - io3.set_high(); - sck.conf().write(|w| w.dir().output().drive().h0h1()); - csn.conf().write(|w| w.dir().output().drive().h0h1()); - io0.conf().write(|w| w.dir().output().drive().h0h1()); - io1.conf().write(|w| w.dir().output().drive().h0h1()); - io2.conf().write(|w| w.dir().output().drive().h0h1()); - io3.conf().write(|w| w.dir().output().drive().h0h1()); + macro_rules! config_pin { + ($pin:ident) => { + $pin.set_high(); + $pin.conf().write(|w| { + w.dir().output(); + w.drive().h0h1(); + #[cfg(feature = "_nrf5340-s")] + w.mcusel().peripheral(); + w + }); + r.psel.$pin.write(|w| unsafe { w.bits($pin.psel_bits()) }); + }; + } - r.psel.sck.write(|w| unsafe { w.bits(sck.psel_bits()) }); - r.psel.csn.write(|w| unsafe { w.bits(csn.psel_bits()) }); - r.psel.io0.write(|w| unsafe { w.bits(io0.psel_bits()) }); - r.psel.io1.write(|w| unsafe { w.bits(io1.psel_bits()) }); - r.psel.io2.write(|w| unsafe { w.bits(io2.psel_bits()) }); - r.psel.io3.write(|w| unsafe { w.bits(io3.psel_bits()) }); + config_pin!(sck); + config_pin!(csn); + config_pin!(io0); + config_pin!(io1); + config_pin!(io2); + config_pin!(io3); r.ifconfig0.write(|w| { w.addrmode().variant(config.address_mode); @@ -151,16 +207,16 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> { w }); - irq.set_handler(Self::on_interrupt); - irq.unpend(); - irq.enable(); + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; // Enable it r.enable.write(|w| w.enable().enabled()); - let mut res = Self { + let res = Self { + _peri: qspi, dpm_enabled: config.deep_power_down.is_some(), - irq, + capacity: config.capacity, }; r.events_ready.reset(); @@ -168,23 +224,14 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> { r.tasks_activate.write(|w| w.tasks_activate().bit(true)); - res.blocking_wait_ready(); + Self::blocking_wait_ready(); res } - fn on_interrupt(_: *mut ()) { - let r = T::regs(); - let s = T::state(); - - if r.events_ready.read().bits() != 0 { - s.ready_waker.wake(); - r.intenclr.write(|w| w.ready().clear()); - } - } - + /// Do a custom QSPI instruction. pub async fn custom_instruction(&mut self, opcode: u8, req: &[u8], resp: &mut [u8]) -> Result<(), Error> { - let bomb = DropBomb::new(); + let ondrop = OnDrop::new(Self::blocking_wait_ready); let len = core::cmp::max(req.len(), resp.len()) as u8; self.custom_instruction_start(opcode, req, len)?; @@ -193,16 +240,17 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> { self.custom_instruction_finish(resp)?; - bomb.defuse(); + ondrop.defuse(); Ok(()) } + /// Do a custom QSPI instruction, blocking version. pub fn blocking_custom_instruction(&mut self, opcode: u8, req: &[u8], resp: &mut [u8]) -> Result<(), Error> { let len = core::cmp::max(req.len(), resp.len()) as u8; self.custom_instruction_start(opcode, req, len)?; - self.blocking_wait_ready(); + Self::blocking_wait_ready(); self.custom_instruction_finish(resp)?; @@ -269,7 +317,7 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> { poll_fn(move |cx| { let r = T::regs(); let s = T::state(); - s.ready_waker.register(cx.waker()); + s.waker.register(cx.waker()); if r.events_ready.read().bits() != 0 { return Poll::Ready(()); } @@ -278,7 +326,7 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> { .await } - fn blocking_wait_ready(&mut self) { + fn blocking_wait_ready() { loop { let r = T::regs(); if r.events_ready.read().bits() != 0 { @@ -287,17 +335,15 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> { } } - fn start_read(&mut self, address: usize, data: &mut [u8]) -> Result<(), Error> { + fn start_read(&mut self, address: u32, data: &mut [u8]) -> Result<(), Error> { + // TODO: Return these as errors instead. assert_eq!(data.as_ptr() as u32 % 4, 0); assert_eq!(data.len() as u32 % 4, 0); - assert_eq!(address as u32 % 4, 0); - if address > FLASH_SIZE { - return Err(Error::OutOfBounds); - } + assert_eq!(address % 4, 0); let r = T::regs(); - r.read.src.write(|w| unsafe { w.src().bits(address as u32) }); + r.read.src.write(|w| unsafe { w.src().bits(address) }); r.read.dst.write(|w| unsafe { w.dst().bits(data.as_ptr() as u32) }); r.read.cnt.write(|w| unsafe { w.cnt().bits(data.len() as u32) }); @@ -308,18 +354,15 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> { Ok(()) } - fn start_write(&mut self, address: usize, data: &[u8]) -> Result<(), Error> { + fn start_write(&mut self, address: u32, data: &[u8]) -> Result<(), Error> { + // TODO: Return these as errors instead. assert_eq!(data.as_ptr() as u32 % 4, 0); assert_eq!(data.len() as u32 % 4, 0); - assert_eq!(address as u32 % 4, 0); - - if address > FLASH_SIZE { - return Err(Error::OutOfBounds); - } + assert_eq!(address % 4, 0); let r = T::regs(); r.write.src.write(|w| unsafe { w.src().bits(data.as_ptr() as u32) }); - r.write.dst.write(|w| unsafe { w.dst().bits(address as u32) }); + r.write.dst.write(|w| unsafe { w.dst().bits(address) }); r.write.cnt.write(|w| unsafe { w.cnt().bits(data.len() as u32) }); r.events_ready.reset(); @@ -329,14 +372,12 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> { Ok(()) } - fn start_erase(&mut self, address: usize) -> Result<(), Error> { - assert_eq!(address as u32 % 4096, 0); - if address > FLASH_SIZE { - return Err(Error::OutOfBounds); - } + fn start_erase(&mut self, address: u32) -> Result<(), Error> { + // TODO: Return these as errors instead. + assert_eq!(address % 4096, 0); let r = T::regs(); - r.erase.ptr.write(|w| unsafe { w.ptr().bits(address as u32) }); + r.erase.ptr.write(|w| unsafe { w.ptr().bits(address) }); r.erase.len.write(|w| w.len()._4kb()); r.events_ready.reset(); @@ -346,59 +387,122 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Qspi<'d, T, FLASH_SIZE> { Ok(()) } - pub async fn read(&mut self, address: usize, data: &mut [u8]) -> Result<(), Error> { - let bomb = DropBomb::new(); + /// Raw QSPI read. + /// + /// The difference with `read` is that this does not do bounds checks + /// against the flash capacity. It is intended for use when QSPI is used as + /// a raw bus, not with flash memory. + pub async fn read_raw(&mut self, address: u32, data: &mut [u8]) -> Result<(), Error> { + let ondrop = OnDrop::new(Self::blocking_wait_ready); self.start_read(address, data)?; self.wait_ready().await; - bomb.defuse(); + ondrop.defuse(); Ok(()) } - pub async fn write(&mut self, address: usize, data: &[u8]) -> Result<(), Error> { - let bomb = DropBomb::new(); + /// Raw QSPI write. + /// + /// The difference with `write` is that this does not do bounds checks + /// against the flash capacity. It is intended for use when QSPI is used as + /// a raw bus, not with flash memory. + pub async fn write_raw(&mut self, address: u32, data: &[u8]) -> Result<(), Error> { + let ondrop = OnDrop::new(Self::blocking_wait_ready); self.start_write(address, data)?; self.wait_ready().await; - bomb.defuse(); + ondrop.defuse(); Ok(()) } - pub async fn erase(&mut self, address: usize) -> Result<(), Error> { - let bomb = DropBomb::new(); - - self.start_erase(address)?; - self.wait_ready().await; - - bomb.defuse(); - - Ok(()) - } - - pub fn blocking_read(&mut self, address: usize, data: &mut [u8]) -> Result<(), Error> { + /// Raw QSPI read, blocking version. + /// + /// The difference with `blocking_read` is that this does not do bounds checks + /// against the flash capacity. It is intended for use when QSPI is used as + /// a raw bus, not with flash memory. + pub fn blocking_read_raw(&mut self, address: u32, data: &mut [u8]) -> Result<(), Error> { self.start_read(address, data)?; - self.blocking_wait_ready(); + Self::blocking_wait_ready(); Ok(()) } - pub fn blocking_write(&mut self, address: usize, data: &[u8]) -> Result<(), Error> { + /// Raw QSPI write, blocking version. + /// + /// The difference with `blocking_write` is that this does not do bounds checks + /// against the flash capacity. It is intended for use when QSPI is used as + /// a raw bus, not with flash memory. + pub fn blocking_write_raw(&mut self, address: u32, data: &[u8]) -> Result<(), Error> { self.start_write(address, data)?; - self.blocking_wait_ready(); + Self::blocking_wait_ready(); Ok(()) } - pub fn blocking_erase(&mut self, address: usize) -> Result<(), Error> { + /// Read data from the flash memory. + pub async fn read(&mut self, address: u32, data: &mut [u8]) -> Result<(), Error> { + self.bounds_check(address, data.len())?; + self.read_raw(address, data).await + } + + /// Write data to the flash memory. + pub async fn write(&mut self, address: u32, data: &[u8]) -> Result<(), Error> { + self.bounds_check(address, data.len())?; + self.write_raw(address, data).await + } + + /// Erase a sector on the flash memory. + pub async fn erase(&mut self, address: u32) -> Result<(), Error> { + if address >= self.capacity { + return Err(Error::OutOfBounds); + } + + let ondrop = OnDrop::new(Self::blocking_wait_ready); + self.start_erase(address)?; - self.blocking_wait_ready(); + self.wait_ready().await; + + ondrop.defuse(); + + Ok(()) + } + + /// Read data from the flash memory, blocking version. + pub fn blocking_read(&mut self, address: u32, data: &mut [u8]) -> Result<(), Error> { + self.bounds_check(address, data.len())?; + self.blocking_read_raw(address, data) + } + + /// Write data to the flash memory, blocking version. + pub fn blocking_write(&mut self, address: u32, data: &[u8]) -> Result<(), Error> { + self.bounds_check(address, data.len())?; + self.blocking_write_raw(address, data) + } + + /// Erase a sector on the flash memory, blocking version. + pub fn blocking_erase(&mut self, address: u32) -> Result<(), Error> { + if address >= self.capacity { + return Err(Error::OutOfBounds); + } + + self.start_erase(address)?; + Self::blocking_wait_ready(); + Ok(()) + } + + fn bounds_check(&self, address: u32, len: usize) -> Result<(), Error> { + let len_u32: u32 = len.try_into().map_err(|_| Error::OutOfBounds)?; + let end_address = address.checked_add(len_u32).ok_or(Error::OutOfBounds)?; + if end_address > self.capacity { + return Err(Error::OutOfBounds); + } Ok(()) } } -impl<'d, T: Instance, const FLASH_SIZE: usize> Drop for Qspi<'d, T, FLASH_SIZE> { +impl<'d, T: Instance> Drop for Qspi<'d, T> { fn drop(&mut self) { let r = T::regs(); @@ -428,8 +532,6 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Drop for Qspi<'d, T, FLASH_SIZE> r.enable.write(|w| w.enable().disabled()); - self.irq.disable(); - // Note: we do NOT deconfigure CSN here. If DPM is in use and we disconnect CSN, // leaving it floating, the flash chip might read it as zero which would cause it to // spuriously exit DPM. @@ -443,9 +545,7 @@ impl<'d, T: Instance, const FLASH_SIZE: usize> Drop for Qspi<'d, T, FLASH_SIZE> } } -use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; - -impl<'d, T: Instance, const FLASH_SIZE: usize> ErrorType for Qspi<'d, T, FLASH_SIZE> { +impl<'d, T: Instance> ErrorType for Qspi<'d, T> { type Error = Error; } @@ -455,72 +555,66 @@ impl NorFlashError for Error { } } -impl<'d, T: Instance, const FLASH_SIZE: usize> ReadNorFlash for Qspi<'d, T, FLASH_SIZE> { +impl<'d, T: Instance> ReadNorFlash for Qspi<'d, T> { const READ_SIZE: usize = 4; fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { - self.blocking_read(offset as usize, bytes)?; + self.blocking_read(offset, bytes)?; Ok(()) } fn capacity(&self) -> usize { - FLASH_SIZE + self.capacity as usize } } -impl<'d, T: Instance, const FLASH_SIZE: usize> NorFlash for Qspi<'d, T, FLASH_SIZE> { +impl<'d, T: Instance> NorFlash for Qspi<'d, T> { const WRITE_SIZE: usize = 4; const ERASE_SIZE: usize = 4096; fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { - for address in (from as usize..to as usize).step_by(::ERASE_SIZE) { + for address in (from..to).step_by(::ERASE_SIZE) { self.blocking_erase(address)?; } Ok(()) } fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { - self.blocking_write(offset as usize, bytes)?; + self.blocking_write(offset, bytes)?; Ok(()) } } -cfg_if::cfg_if! { - if #[cfg(feature = "nightly")] - { - use embedded_storage_async::nor_flash::{AsyncNorFlash, AsyncReadNorFlash}; - use core::future::Future; +#[cfg(feature = "nightly")] +mod _eh1 { + use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; - impl<'d, T: Instance, const FLASH_SIZE: usize> AsyncNorFlash for Qspi<'d, T, FLASH_SIZE> { - const WRITE_SIZE: usize = ::WRITE_SIZE; - const ERASE_SIZE: usize = ::ERASE_SIZE; + use super::*; - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - fn write<'a>(&'a mut self, offset: u32, data: &'a [u8]) -> Self::WriteFuture<'a> { - async move { self.write(offset as usize, data).await } - } + impl<'d, T: Instance> AsyncNorFlash for Qspi<'d, T> { + const WRITE_SIZE: usize = ::WRITE_SIZE; + const ERASE_SIZE: usize = ::ERASE_SIZE; - type EraseFuture<'a> = impl Future> + 'a where Self: 'a; - fn erase<'a>(&'a mut self, from: u32, to: u32) -> Self::EraseFuture<'a> { - async move { - for address in (from as usize..to as usize).step_by(::ERASE_SIZE) { - self.erase(address).await? - } - Ok(()) - } - } + async fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> { + self.write(offset, data).await } - impl<'d, T: Instance, const FLASH_SIZE: usize> AsyncReadNorFlash for Qspi<'d, T, FLASH_SIZE> { - const READ_SIZE: usize = 4; - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; - fn read<'a>(&'a mut self, address: u32, data: &'a mut [u8]) -> Self::ReadFuture<'a> { - async move { self.read(address as usize, data).await } + async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + for address in (from..to).step_by(::ERASE_SIZE) { + self.erase(address).await? } + Ok(()) + } + } - fn capacity(&self) -> usize { - FLASH_SIZE - } + impl<'d, T: Instance> AsyncReadNorFlash for Qspi<'d, T> { + const READ_SIZE: usize = 4; + async fn read(&mut self, address: u32, data: &mut [u8]) -> Result<(), Self::Error> { + self.read(address, data).await + } + + fn capacity(&self) -> usize { + self.capacity as usize } } } @@ -528,33 +622,35 @@ cfg_if::cfg_if! { pub(crate) mod sealed { use embassy_sync::waitqueue::AtomicWaker; - use super::*; - + /// Peripheral static state pub struct State { - pub ready_waker: AtomicWaker, + pub waker: AtomicWaker, } + impl State { pub const fn new() -> Self { Self { - ready_waker: AtomicWaker::new(), + waker: AtomicWaker::new(), } } } pub trait Instance { - fn regs() -> &'static pac::qspi::RegisterBlock; + fn regs() -> &'static crate::pac::qspi::RegisterBlock; fn state() -> &'static State; } } -pub trait Instance: Peripheral

+ sealed::Instance + 'static { - type Interrupt: Interrupt; +/// QSPI peripheral instance. +pub trait Instance: Peripheral

+ sealed::Instance + 'static + Send { + /// Interrupt for this peripheral. + type Interrupt: interrupt::typelevel::Interrupt; } macro_rules! impl_qspi { ($type:ident, $pac_type:ident, $irq:ident) => { impl crate::qspi::sealed::Instance for peripherals::$type { - fn regs() -> &'static pac::qspi::RegisterBlock { + fn regs() -> &'static crate::pac::qspi::RegisterBlock { unsafe { &*pac::$pac_type::ptr() } } fn state() -> &'static crate::qspi::sealed::State { @@ -563,7 +659,7 @@ macro_rules! impl_qspi { } } impl crate::qspi::Instance for peripherals::$type { - type Interrupt = crate::interrupt::$irq; + type Interrupt = crate::interrupt::typelevel::$irq; } }; } diff --git a/embassy-nrf/src/rng.rs b/embassy-nrf/src/rng.rs index 42da51d0f..923b8b467 100644 --- a/embassy-nrf/src/rng.rs +++ b/embassy-nrf/src/rng.rs @@ -1,3 +1,9 @@ +//! Random Number Generator (RNG) driver. + +#![macro_use] + +use core::future::poll_fn; +use core::marker::PhantomData; use core::ptr; use core::sync::atomic::{AtomicPtr, Ordering}; use core::task::Poll; @@ -5,77 +11,37 @@ use core::task::Poll; use embassy_hal_common::drop::OnDrop; use embassy_hal_common::{into_ref, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; -use futures::future::poll_fn; -use crate::interrupt::InterruptExt; -use crate::peripherals::RNG; -use crate::{interrupt, pac, Peripheral}; +use crate::interrupt::typelevel::Interrupt; +use crate::{interrupt, Peripheral}; -impl RNG { - fn regs() -> &'static pac::rng::RegisterBlock { - unsafe { &*pac::RNG::ptr() } - } +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, } -static STATE: State = State { - ptr: AtomicPtr::new(ptr::null_mut()), - end: AtomicPtr::new(ptr::null_mut()), - waker: AtomicWaker::new(), -}; +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let s = T::state(); + let r = T::regs(); -struct State { - ptr: AtomicPtr, - end: AtomicPtr, - waker: AtomicWaker, -} - -/// A wrapper around an nRF RNG peripheral. -/// -/// It has a non-blocking API, and a blocking api through `rand`. -pub struct Rng<'d> { - irq: PeripheralRef<'d, interrupt::RNG>, -} - -impl<'d> Rng<'d> { - /// Creates a new RNG driver from the `RNG` peripheral and interrupt. - /// - /// SAFETY: The future returned from `fill_bytes` must not have its lifetime end without running its destructor, - /// e.g. using `mem::forget`. - /// - /// The synchronous API is safe. - pub fn new(_rng: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd) -> Self { - into_ref!(irq); - - let this = Self { irq }; - - this.stop(); - this.disable_irq(); - - this.irq.set_handler(Self::on_interrupt); - this.irq.unpend(); - this.irq.enable(); - - this - } - - fn on_interrupt(_: *mut ()) { // Clear the event. - RNG::regs().events_valrdy.reset(); + r.events_valrdy.reset(); // Mutate the slice within a critical section, // so that the future isn't dropped in between us loading the pointer and actually dereferencing it. let (ptr, end) = critical_section::with(|_| { - let ptr = STATE.ptr.load(Ordering::Relaxed); + let ptr = s.ptr.load(Ordering::Relaxed); // We need to make sure we haven't already filled the whole slice, // in case the interrupt fired again before the executor got back to the future. - let end = STATE.end.load(Ordering::Relaxed); + let end = s.end.load(Ordering::Relaxed); if !ptr.is_null() && ptr != end { // If the future was dropped, the pointer would have been set to null, // so we're still good to mutate the slice. // The safety contract of `Rng::new` means that the future can't have been dropped // without calling its destructor. unsafe { - *ptr = RNG::regs().value.read().value().bits(); + *ptr = r.value.read().value().bits(); } } (ptr, end) @@ -88,15 +54,15 @@ impl<'d> Rng<'d> { } let new_ptr = unsafe { ptr.add(1) }; - match STATE + match s .ptr .compare_exchange(ptr, new_ptr, Ordering::Relaxed, Ordering::Relaxed) { Ok(_) => { - let end = STATE.end.load(Ordering::Relaxed); + let end = s.end.load(Ordering::Relaxed); // It doesn't matter if `end` was changed under our feet, because then this will just be false. if new_ptr == end { - STATE.waker.wake(); + s.waker.wake(); } } Err(_) => { @@ -105,21 +71,53 @@ impl<'d> Rng<'d> { } } } +} + +/// A wrapper around an nRF RNG peripheral. +/// +/// It has a non-blocking API, and a blocking api through `rand`. +pub struct Rng<'d, T: Instance> { + _peri: PeripheralRef<'d, T>, +} + +impl<'d, T: Instance> Rng<'d, T> { + /// Creates a new RNG driver from the `RNG` peripheral and interrupt. + /// + /// SAFETY: The future returned from `fill_bytes` must not have its lifetime end without running its destructor, + /// e.g. using `mem::forget`. + /// + /// The synchronous API is safe. + pub fn new( + rng: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + ) -> Self { + into_ref!(rng); + + let this = Self { _peri: rng }; + + this.stop(); + this.disable_irq(); + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + this + } fn stop(&self) { - RNG::regs().tasks_stop.write(|w| unsafe { w.bits(1) }) + T::regs().tasks_stop.write(|w| unsafe { w.bits(1) }) } fn start(&self) { - RNG::regs().tasks_start.write(|w| unsafe { w.bits(1) }) + T::regs().tasks_start.write(|w| unsafe { w.bits(1) }) } fn enable_irq(&self) { - RNG::regs().intenset.write(|w| w.valrdy().set()); + T::regs().intenset.write(|w| w.valrdy().set()); } fn disable_irq(&self) { - RNG::regs().intenclr.write(|w| w.valrdy().clear()); + T::regs().intenclr.write(|w| w.valrdy().clear()); } /// Enable or disable the RNG's bias correction. @@ -128,20 +126,23 @@ impl<'d> Rng<'d> { /// However, this makes the generation of numbers slower. /// /// Defaults to disabled. - pub fn bias_correction(&self, enable: bool) { - RNG::regs().config.write(|w| w.dercen().bit(enable)) + pub fn set_bias_correction(&self, enable: bool) { + T::regs().config.write(|w| w.dercen().bit(enable)) } + /// Fill the buffer with random bytes. pub async fn fill_bytes(&mut self, dest: &mut [u8]) { if dest.len() == 0 { return; // Nothing to fill } + let s = T::state(); + let range = dest.as_mut_ptr_range(); // Even if we've preempted the interrupt, it can't preempt us again, // so we don't need to worry about the order we write these in. - STATE.ptr.store(range.start, Ordering::Relaxed); - STATE.end.store(range.end, Ordering::Relaxed); + s.ptr.store(range.start, Ordering::Relaxed); + s.end.store(range.end, Ordering::Relaxed); self.enable_irq(); self.start(); @@ -151,16 +152,16 @@ impl<'d> Rng<'d> { self.disable_irq(); // The interrupt is now disabled and can't preempt us anymore, so the order doesn't matter here. - STATE.ptr.store(ptr::null_mut(), Ordering::Relaxed); - STATE.end.store(ptr::null_mut(), Ordering::Relaxed); + s.ptr.store(ptr::null_mut(), Ordering::Relaxed); + s.end.store(ptr::null_mut(), Ordering::Relaxed); }); poll_fn(|cx| { - STATE.waker.register(cx.waker()); + s.waker.register(cx.waker()); // The interrupt will never modify `end`, so load it first and then get the most up-to-date `ptr`. - let end = STATE.end.load(Ordering::Relaxed); - let ptr = STATE.ptr.load(Ordering::Relaxed); + let end = s.end.load(Ordering::Relaxed); + let ptr = s.ptr.load(Ordering::Relaxed); if ptr == end { // We're done. @@ -175,11 +176,12 @@ impl<'d> Rng<'d> { drop(on_drop); } + /// Fill the buffer with random bytes, blocking version. pub fn blocking_fill_bytes(&mut self, dest: &mut [u8]) { self.start(); for byte in dest.iter_mut() { - let regs = RNG::regs(); + let regs = T::regs(); while regs.events_valrdy.read().bits() == 0 {} regs.events_valrdy.reset(); *byte = regs.value.read().value().bits(); @@ -189,13 +191,16 @@ impl<'d> Rng<'d> { } } -impl<'d> Drop for Rng<'d> { +impl<'d, T: Instance> Drop for Rng<'d, T> { fn drop(&mut self) { - self.irq.disable() + self.stop(); + let s = T::state(); + s.ptr.store(ptr::null_mut(), Ordering::Relaxed); + s.end.store(ptr::null_mut(), Ordering::Relaxed); } } -impl<'d> rand_core::RngCore for Rng<'d> { +impl<'d, T: Instance> rand_core::RngCore for Rng<'d, T> { fn fill_bytes(&mut self, dest: &mut [u8]) { self.blocking_fill_bytes(dest); } @@ -219,4 +224,53 @@ impl<'d> rand_core::RngCore for Rng<'d> { } } -impl<'d> rand_core::CryptoRng for Rng<'d> {} +impl<'d, T: Instance> rand_core::CryptoRng for Rng<'d, T> {} + +pub(crate) mod sealed { + use super::*; + + /// Peripheral static state + pub struct State { + pub ptr: AtomicPtr, + pub end: AtomicPtr, + pub waker: AtomicWaker, + } + + impl State { + pub const fn new() -> Self { + Self { + ptr: AtomicPtr::new(ptr::null_mut()), + end: AtomicPtr::new(ptr::null_mut()), + waker: AtomicWaker::new(), + } + } + } + + pub trait Instance { + fn regs() -> &'static crate::pac::rng::RegisterBlock; + fn state() -> &'static State; + } +} + +/// RNG peripheral instance. +pub trait Instance: Peripheral

+ sealed::Instance + 'static + Send { + /// Interrupt for this peripheral. + type Interrupt: interrupt::typelevel::Interrupt; +} + +macro_rules! impl_rng { + ($type:ident, $pac_type:ident, $irq:ident) => { + impl crate::rng::sealed::Instance for peripherals::$type { + fn regs() -> &'static crate::pac::rng::RegisterBlock { + unsafe { &*pac::$pac_type::ptr() } + } + fn state() -> &'static crate::rng::sealed::State { + static STATE: crate::rng::sealed::State = crate::rng::sealed::State::new(); + &STATE + } + } + impl crate::rng::Instance for peripherals::$type { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +} diff --git a/embassy-nrf/src/saadc.rs b/embassy-nrf/src/saadc.rs index 7dc66349e..23292924c 100644 --- a/embassy-nrf/src/saadc.rs +++ b/embassy-nrf/src/saadc.rs @@ -1,11 +1,14 @@ +//! Successive Approximation Analog-to-Digital Converter (SAADC) driver. + #![macro_use] +use core::future::poll_fn; use core::sync::atomic::{compiler_fence, Ordering}; use core::task::Poll; +use embassy_hal_common::drop::OnDrop; use embassy_hal_common::{impl_peripheral, into_ref, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; -use futures::future::poll_fn; use pac::{saadc, SAADC}; use saadc::ch::config::{GAIN_A, REFSEL_A, RESP_A, TACQ_A}; // We treat the positive and negative channels with the same enum values to keep our type tidy and given they are the same @@ -19,14 +22,36 @@ use crate::ppi::{ConfigurableChannel, Event, Ppi, Task}; use crate::timer::{Frequency, Instance as TimerInstance, Timer}; use crate::{interrupt, pac, peripherals, Peripheral}; +/// SAADC error #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum Error {} -/// One-shot and continuous SAADC. -pub struct Saadc<'d, const N: usize> { - _p: PeripheralRef<'d, peripherals::SAADC>, +/// Interrupt handler. +pub struct InterruptHandler { + _private: (), +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let r = unsafe { &*SAADC::ptr() }; + + if r.events_calibratedone.read().bits() != 0 { + r.intenclr.write(|w| w.calibratedone().clear()); + WAKER.wake(); + } + + if r.events_end.read().bits() != 0 { + r.intenclr.write(|w| w.end().clear()); + WAKER.wake(); + } + + if r.events_started.read().bits() != 0 { + r.intenclr.write(|w| w.started().clear()); + WAKER.wake(); + } + } } static WAKER: AtomicWaker = AtomicWaker::new(); @@ -101,24 +126,29 @@ impl<'d> ChannelConfig<'d> { } } -/// The state of a continuously running sampler. While it reflects -/// the progress of a sampler, it also signals what should be done -/// next. For example, if the sampler has stopped then the Saadc implementation -/// can then tear down its infrastructure. +/// Value returned by the SAADC callback, deciding what happens next. #[derive(PartialEq)] -pub enum SamplerState { - Sampled, - Stopped, +pub enum CallbackResult { + /// The SAADC should keep sampling and calling the callback. + Continue, + /// The SAADC should stop sampling, and return. + Stop, +} + +/// One-shot and continuous SAADC. +pub struct Saadc<'d, const N: usize> { + _p: PeripheralRef<'d, peripherals::SAADC>, } impl<'d, const N: usize> Saadc<'d, N> { + /// Create a new SAADC driver. pub fn new( saadc: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding + 'd, config: Config, channel_configs: [ChannelConfig; N], ) -> Self { - into_ref!(saadc, irq); + into_ref!(saadc); let r = unsafe { &*SAADC::ptr() }; @@ -159,32 +189,12 @@ impl<'d, const N: usize> Saadc<'d, N> { // Disable all events interrupts r.intenclr.write(|w| unsafe { w.bits(0x003F_FFFF) }); - irq.set_handler(Self::on_interrupt); - irq.unpend(); - irq.enable(); + interrupt::SAADC.unpend(); + unsafe { interrupt::SAADC.enable() }; Self { _p: saadc } } - fn on_interrupt(_ctx: *mut ()) { - let r = Self::regs(); - - if r.events_calibratedone.read().bits() != 0 { - r.intenclr.write(|w| w.calibratedone().clear()); - WAKER.wake(); - } - - if r.events_end.read().bits() != 0 { - r.intenclr.write(|w| w.end().clear()); - WAKER.wake(); - } - - if r.events_started.read().bits() != 0 { - r.intenclr.write(|w| w.started().clear()); - WAKER.wake(); - } - } - fn regs() -> &'static saadc::RegisterBlock { unsafe { &*SAADC::ptr() } } @@ -219,7 +229,13 @@ impl<'d, const N: usize> Saadc<'d, N> { } /// One shot sampling. The buffer must be the same size as the number of channels configured. + /// The sampling is stopped prior to returning in order to reduce power consumption (power + /// consumption remains higher if sampling is not stopped explicitly). Cancellation will + /// also cause the sampling to be stopped. pub async fn sample(&mut self, buf: &mut [i16; N]) { + // In case the future is dropped, stop the task and wait for it to end. + let on_drop = OnDrop::new(Self::stop_sampling_immediately); + let r = Self::regs(); // Set up the DMA @@ -251,6 +267,8 @@ impl<'d, const N: usize> Saadc<'d, N> { Poll::Pending }) .await; + + drop(on_drop); } /// Continuous sampling with double buffers. @@ -270,7 +288,13 @@ impl<'d, const N: usize> Saadc<'d, N> { /// taken to acquire the samples into a single buffer. You should measure the /// time taken by the callback and set the sample buffer size accordingly. /// Exceeding this time can lead to samples becoming dropped. - pub async fn run_task_sampler( + /// + /// The sampling is stopped prior to returning in order to reduce power consumption (power + /// consumption remains higher if sampling is not stopped explicitly), and to + /// free the buffers from being used by the peripheral. Cancellation will + /// also cause the sampling to be stopped. + + pub async fn run_task_sampler( &mut self, timer: &mut T, ppi_ch1: &mut impl ConfigurableChannel, @@ -278,9 +302,9 @@ impl<'d, const N: usize> Saadc<'d, N> { frequency: Frequency, sample_counter: u32, bufs: &mut [[[i16; N]; N0]; 2], - sampler: S, + callback: F, ) where - S: FnMut(&[[i16; N]]) -> SamplerState, + F: FnMut(&[[i16; N]]) -> CallbackResult, { let r = Self::regs(); @@ -291,12 +315,14 @@ impl<'d, const N: usize> Saadc<'d, N> { Ppi::new_one_to_one(ppi_ch1, Event::from_reg(&r.events_end), Task::from_reg(&r.tasks_start)); start_ppi.enable(); - let mut timer = Timer::new(timer); + let timer = Timer::new(timer); timer.set_frequency(frequency); timer.cc(0).write(sample_counter); timer.cc(0).short_compare_clear(); - let mut sample_ppi = Ppi::new_one_to_one(ppi_ch2, timer.cc(0).event_compare(), Task::from_reg(&r.tasks_sample)); + let timer_cc = timer.cc(0); + + let mut sample_ppi = Ppi::new_one_to_one(ppi_ch2, timer_cc.event_compare(), Task::from_reg(&r.tasks_sample)); timer.start(); @@ -306,21 +332,24 @@ impl<'d, const N: usize> Saadc<'d, N> { || { sample_ppi.enable(); }, - sampler, + callback, ) .await; } - async fn run_sampler( + async fn run_sampler( &mut self, bufs: &mut [[[i16; N]; N0]; 2], sample_rate_divisor: Option, mut init: I, - mut sampler: S, + mut callback: F, ) where I: FnMut(), - S: FnMut(&[[i16; N]]) -> SamplerState, + F: FnMut(&[[i16; N]]) -> CallbackResult, { + // In case the future is dropped, stop the task and wait for it to end. + let on_drop = OnDrop::new(Self::stop_sampling_immediately); + let r = Self::regs(); // Establish mode and sample rate @@ -366,7 +395,7 @@ impl<'d, const N: usize> Saadc<'d, N> { let mut current_buffer = 0; // Wait for events and complete when the sampler indicates it has had enough. - poll_fn(|cx| { + let r = poll_fn(|cx| { let r = Self::regs(); WAKER.register(cx.waker()); @@ -377,12 +406,15 @@ impl<'d, const N: usize> Saadc<'d, N> { r.events_end.reset(); r.intenset.write(|w| w.end().set()); - if sampler(&bufs[current_buffer]) == SamplerState::Sampled { - let next_buffer = 1 - current_buffer; - current_buffer = next_buffer; - } else { - return Poll::Ready(()); - }; + match callback(&bufs[current_buffer]) { + CallbackResult::Continue => { + let next_buffer = 1 - current_buffer; + current_buffer = next_buffer; + } + CallbackResult::Stop => { + return Poll::Ready(()); + } + } } if r.events_started.read().bits() != 0 { @@ -403,6 +435,23 @@ impl<'d, const N: usize> Saadc<'d, N> { Poll::Pending }) .await; + + drop(on_drop); + + r + } + + // Stop sampling and wait for it to stop in a blocking fashion + fn stop_sampling_immediately() { + let r = Self::regs(); + + compiler_fence(Ordering::SeqCst); + + r.events_stopped.reset(); + r.tasks_stop.write(|w| unsafe { w.bits(1) }); + + while r.events_stopped.read().bits() == 0 {} + r.events_stopped.reset(); } } @@ -423,7 +472,7 @@ impl<'d> Saadc<'d, 1> { sample_rate_divisor: u16, sampler: S, ) where - S: FnMut(&[[i16; 1]]) -> SamplerState, + S: FnMut(&[[i16; 1]]) -> CallbackResult, { self.run_sampler(bufs, Some(sample_rate_divisor), || {}, sampler).await; } @@ -623,6 +672,10 @@ pub(crate) mod sealed { /// An input that can be used as either or negative end of a ADC differential in the SAADC periperhal. pub trait Input: sealed::Input + Into + Peripheral

+ Sized + 'static { + /// Convert this SAADC input to a type-erased `AnyInput`. + /// + /// This allows using several inputs in situations that might require + /// them to be the same type, like putting them in an array. fn degrade_saadc(self) -> AnyInput { AnyInput { channel: self.channel(), @@ -630,6 +683,10 @@ pub trait Input: sealed::Input + Into + Peripheral

+ Sized + } } +/// A type-erased SAADC input. +/// +/// This allows using several inputs in situations that might require +/// them to be the same type, like putting them in an array. pub struct AnyInput { channel: InputChannel, } diff --git a/embassy-nrf/src/spim.rs b/embassy-nrf/src/spim.rs index be2fc02fc..b7dc332e9 100644 --- a/embassy-nrf/src/spim.rs +++ b/embassy-nrf/src/spim.rs @@ -1,42 +1,50 @@ +//! Serial Peripheral Instance in master mode (SPIM) driver. + #![macro_use] +use core::future::poll_fn; +use core::marker::PhantomData; use core::sync::atomic::{compiler_fence, Ordering}; use core::task::Poll; use embassy_embedded_hal::SetConfig; use embassy_hal_common::{into_ref, PeripheralRef}; pub use embedded_hal_02::spi::{Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3}; -use futures::future::poll_fn; pub use pac::spim0::frequency::FREQUENCY_A as Frequency; use crate::chip::FORCE_COPY_BUFFER_SIZE; use crate::gpio::sealed::Pin as _; use crate::gpio::{self, AnyPin, Pin as GpioPin, PselBits}; -use crate::interrupt::{Interrupt, InterruptExt}; +use crate::interrupt::typelevel::Interrupt; use crate::util::{slice_in_ram_or, slice_ptr_parts, slice_ptr_parts_mut}; -use crate::{pac, Peripheral}; +use crate::{interrupt, pac, Peripheral}; +/// SPIM error #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum Error { + /// TX buffer was too long. TxBufferTooLong, + /// RX buffer was too long. RxBufferTooLong, /// EasyDMA can only read from data memory, read only buffers in flash will fail. - DMABufferNotInDataMemory, -} - -/// Interface for the SPIM peripheral using EasyDMA to offload the transmission and reception workload. -/// -/// For more details about EasyDMA, consult the module documentation. -pub struct Spim<'d, T: Instance> { - _p: PeripheralRef<'d, T>, + BufferNotInRAM, } +/// SPIM configuration. #[non_exhaustive] pub struct Config { + /// Frequency pub frequency: Frequency, + + /// SPI mode pub mode: Mode, + + /// Overread character. + /// + /// When doing bidirectional transfers, if the TX buffer is shorter than the RX buffer, + /// this byte will be transmitted in the MOSI line for the left-over bytes. pub orc: u8, } @@ -50,10 +58,33 @@ impl Default for Config { } } +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let r = T::regs(); + let s = T::state(); + + if r.events_end.read().bits() != 0 { + s.end_waker.wake(); + r.intenclr.write(|w| w.end().clear()); + } + } +} + +/// SPIM driver. +pub struct Spim<'d, T: Instance> { + _p: PeripheralRef<'d, T>, +} + impl<'d, T: Instance> Spim<'d, T> { + /// Create a new SPIM driver. pub fn new( spim: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, sck: impl Peripheral

+ 'd, miso: impl Peripheral

+ 'd, mosi: impl Peripheral

+ 'd, @@ -62,7 +93,6 @@ impl<'d, T: Instance> Spim<'d, T> { into_ref!(sck, miso, mosi); Self::new_inner( spim, - irq, sck.map_into(), Some(miso.map_into()), Some(mosi.map_into()), @@ -70,37 +100,38 @@ impl<'d, T: Instance> Spim<'d, T> { ) } + /// Create a new SPIM driver, capable of TX only (MOSI only). pub fn new_txonly( spim: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, sck: impl Peripheral

+ 'd, mosi: impl Peripheral

+ 'd, config: Config, ) -> Self { into_ref!(sck, mosi); - Self::new_inner(spim, irq, sck.map_into(), None, Some(mosi.map_into()), config) + Self::new_inner(spim, sck.map_into(), None, Some(mosi.map_into()), config) } + /// Create a new SPIM driver, capable of RX only (MISO only). pub fn new_rxonly( spim: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, sck: impl Peripheral

+ 'd, miso: impl Peripheral

+ 'd, config: Config, ) -> Self { into_ref!(sck, miso); - Self::new_inner(spim, irq, sck.map_into(), Some(miso.map_into()), None, config) + Self::new_inner(spim, sck.map_into(), Some(miso.map_into()), None, config) } fn new_inner( spim: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, sck: PeripheralRef<'d, AnyPin>, miso: Option>, mosi: Option>, config: Config, ) -> Self { - into_ref!(spim, irq); + into_ref!(spim); let r = T::regs(); @@ -176,25 +207,14 @@ impl<'d, T: Instance> Spim<'d, T> { // Disable all events interrupts r.intenclr.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); - irq.set_handler(Self::on_interrupt); - irq.unpend(); - irq.enable(); + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; Self { _p: spim } } - fn on_interrupt(_: *mut ()) { - let r = T::regs(); - let s = T::state(); - - if r.events_end.read().bits() != 0 { - s.end_waker.wake(); - r.intenclr.write(|w| w.end().clear()); - } - } - fn prepare(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(), Error> { - slice_in_ram_or(tx, Error::DMABufferNotInDataMemory)?; + slice_in_ram_or(tx, Error::BufferNotInRAM)?; // NOTE: RAM slice check for rx is not necessary, as a mutable // slice can only be built from data located in RAM. @@ -236,7 +256,7 @@ impl<'d, T: Instance> Spim<'d, T> { fn blocking_inner(&mut self, rx: &mut [u8], tx: &[u8]) -> Result<(), Error> { match self.blocking_inner_from_ram(rx, tx) { Ok(_) => Ok(()), - Err(Error::DMABufferNotInDataMemory) => { + Err(Error::BufferNotInRAM) => { trace!("Copying SPIM tx buffer into RAM for DMA"); let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..tx.len()]; tx_ram_buf.copy_from_slice(tx); @@ -268,7 +288,7 @@ impl<'d, T: Instance> Spim<'d, T> { async fn async_inner(&mut self, rx: &mut [u8], tx: &[u8]) -> Result<(), Error> { match self.async_inner_from_ram(rx, tx).await { Ok(_) => Ok(()), - Err(Error::DMABufferNotInDataMemory) => { + Err(Error::BufferNotInRAM) => { trace!("Copying SPIM tx buffer into RAM for DMA"); let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..tx.len()]; tx_ram_buf.copy_from_slice(tx); @@ -385,8 +405,10 @@ pub(crate) mod sealed { } } +/// SPIM peripheral instance pub trait Instance: Peripheral

+ sealed::Instance + 'static { - type Interrupt: Interrupt; + /// Interrupt for this peripheral. + type Interrupt: interrupt::typelevel::Interrupt; } macro_rules! impl_spim { @@ -401,7 +423,7 @@ macro_rules! impl_spim { } } impl crate::spim::Instance for peripherals::$type { - type Interrupt = crate::interrupt::$irq; + type Interrupt = crate::interrupt::typelevel::$irq; } }; } @@ -437,7 +459,7 @@ mod eh1 { match *self { Self::TxBufferTooLong => embedded_hal_1::spi::ErrorKind::Other, Self::RxBufferTooLong => embedded_hal_1::spi::ErrorKind::Other, - Self::DMABufferNotInDataMemory => embedded_hal_1::spi::ErrorKind::Other, + Self::BufferNotInRAM => embedded_hal_1::spi::ErrorKind::Other, } } } @@ -446,25 +468,19 @@ mod eh1 { type Error = Error; } - impl<'d, T: Instance> embedded_hal_1::spi::blocking::SpiBusFlush for Spim<'d, T> { + impl<'d, T: Instance> embedded_hal_1::spi::SpiBus for Spim<'d, T> { fn flush(&mut self) -> Result<(), Self::Error> { Ok(()) } - } - impl<'d, T: Instance> embedded_hal_1::spi::blocking::SpiBusRead for Spim<'d, T> { fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { self.blocking_transfer(words, &[]) } - } - impl<'d, T: Instance> embedded_hal_1::spi::blocking::SpiBusWrite for Spim<'d, T> { fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { self.blocking_write(words) } - } - impl<'d, T: Instance> embedded_hal_1::spi::blocking::SpiBus for Spim<'d, T> { fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { self.blocking_transfer(read, write) } @@ -475,49 +491,30 @@ mod eh1 { } } -cfg_if::cfg_if! { - if #[cfg(all(feature = "unstable-traits", feature = "nightly"))] { - use core::future::Future; +#[cfg(all(feature = "unstable-traits", feature = "nightly"))] +mod eha { - impl<'d, T: Instance> embedded_hal_async::spi::SpiBusFlush for Spim<'d, T> { - type FlushFuture<'a> = impl Future> + 'a where Self: 'a; + use super::*; - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - async move { Ok(()) } - } + impl<'d, T: Instance> embedded_hal_async::spi::SpiBus for Spim<'d, T> { + async fn flush(&mut self) -> Result<(), Error> { + Ok(()) } - impl<'d, T: Instance> embedded_hal_async::spi::SpiBusRead for Spim<'d, T> { - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn read<'a>(&'a mut self, words: &'a mut [u8]) -> Self::ReadFuture<'a> { - self.read(words) - } + async fn read(&mut self, words: &mut [u8]) -> Result<(), Error> { + self.read(words).await } - impl<'d, T: Instance> embedded_hal_async::spi::SpiBusWrite for Spim<'d, T> { - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - - fn write<'a>(&'a mut self, data: &'a [u8]) -> Self::WriteFuture<'a> { - self.write(data) - } + async fn write(&mut self, data: &[u8]) -> Result<(), Error> { + self.write(data).await } - impl<'d, T: Instance> embedded_hal_async::spi::SpiBus for Spim<'d, T> { - type TransferFuture<'a> = impl Future> + 'a where Self: 'a; + async fn transfer(&mut self, rx: &mut [u8], tx: &[u8]) -> Result<(), Error> { + self.transfer(rx, tx).await + } - fn transfer<'a>(&'a mut self, rx: &'a mut [u8], tx: &'a [u8]) -> Self::TransferFuture<'a> { - self.transfer(rx, tx) - } - - type TransferInPlaceFuture<'a> = impl Future> + 'a where Self: 'a; - - fn transfer_in_place<'a>( - &'a mut self, - words: &'a mut [u8], - ) -> Self::TransferInPlaceFuture<'a> { - self.transfer_in_place(words) - } + async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Error> { + self.transfer_in_place(words).await } } } diff --git a/embassy-nrf/src/spis.rs b/embassy-nrf/src/spis.rs new file mode 100644 index 000000000..aa438415a --- /dev/null +++ b/embassy-nrf/src/spis.rs @@ -0,0 +1,550 @@ +//! Serial Peripheral Instance in slave mode (SPIS) driver. + +#![macro_use] +use core::future::poll_fn; +use core::marker::PhantomData; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; + +use embassy_embedded_hal::SetConfig; +use embassy_hal_common::{into_ref, PeripheralRef}; +pub use embedded_hal_02::spi::{Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3}; + +use crate::chip::FORCE_COPY_BUFFER_SIZE; +use crate::gpio::sealed::Pin as _; +use crate::gpio::{self, AnyPin, Pin as GpioPin}; +use crate::interrupt::typelevel::Interrupt; +use crate::util::{slice_in_ram_or, slice_ptr_parts, slice_ptr_parts_mut}; +use crate::{interrupt, pac, Peripheral}; + +/// SPIS error +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + /// TX buffer was too long. + TxBufferTooLong, + /// RX buffer was too long. + RxBufferTooLong, + /// EasyDMA can only read from data memory, read only buffers in flash will fail. + BufferNotInRAM, +} + +/// SPIS configuration. +#[non_exhaustive] +pub struct Config { + /// SPI mode + pub mode: Mode, + + /// Overread character. + /// + /// If the master keeps clocking the bus after all the bytes in the TX buffer have + /// already been transmitted, this byte will be constantly transmitted in the MISO line. + pub orc: u8, + + /// Default byte. + /// + /// This is the byte clocked out in the MISO line for ignored transactions (if the master + /// sets CSN low while the semaphore is owned by the firmware) + pub def: u8, + + /// Automatically make the firmware side acquire the semaphore on transfer end. + pub auto_acquire: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + mode: MODE_0, + orc: 0x00, + def: 0x00, + auto_acquire: true, + } + } +} + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let r = T::regs(); + let s = T::state(); + + if r.events_end.read().bits() != 0 { + s.waker.wake(); + r.intenclr.write(|w| w.end().clear()); + } + + if r.events_acquired.read().bits() != 0 { + s.waker.wake(); + r.intenclr.write(|w| w.acquired().clear()); + } + } +} + +/// SPIS driver. +pub struct Spis<'d, T: Instance> { + _p: PeripheralRef<'d, T>, +} + +impl<'d, T: Instance> Spis<'d, T> { + /// Create a new SPIS driver. + pub fn new( + spis: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + cs: impl Peripheral

+ 'd, + sck: impl Peripheral

+ 'd, + miso: impl Peripheral

+ 'd, + mosi: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(cs, sck, miso, mosi); + Self::new_inner( + spis, + cs.map_into(), + sck.map_into(), + Some(miso.map_into()), + Some(mosi.map_into()), + config, + ) + } + + /// Create a new SPIS driver, capable of TX only (MISO only). + pub fn new_txonly( + spis: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + cs: impl Peripheral

+ 'd, + sck: impl Peripheral

+ 'd, + miso: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(cs, sck, miso); + Self::new_inner(spis, cs.map_into(), sck.map_into(), Some(miso.map_into()), None, config) + } + + /// Create a new SPIS driver, capable of RX only (MOSI only). + pub fn new_rxonly( + spis: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + cs: impl Peripheral

+ 'd, + sck: impl Peripheral

+ 'd, + mosi: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(cs, sck, mosi); + Self::new_inner(spis, cs.map_into(), sck.map_into(), None, Some(mosi.map_into()), config) + } + + fn new_inner( + spis: impl Peripheral

+ 'd, + cs: PeripheralRef<'d, AnyPin>, + sck: PeripheralRef<'d, AnyPin>, + miso: Option>, + mosi: Option>, + config: Config, + ) -> Self { + compiler_fence(Ordering::SeqCst); + + into_ref!(spis, cs, sck); + + let r = T::regs(); + + // Configure pins. + sck.conf().write(|w| w.input().connect().drive().h0h1()); + r.psel.sck.write(|w| unsafe { w.bits(sck.psel_bits()) }); + cs.conf().write(|w| w.input().connect().drive().h0h1()); + r.psel.csn.write(|w| unsafe { w.bits(cs.psel_bits()) }); + if let Some(mosi) = &mosi { + mosi.conf().write(|w| w.input().connect().drive().h0h1()); + r.psel.mosi.write(|w| unsafe { w.bits(mosi.psel_bits()) }); + } + if let Some(miso) = &miso { + miso.conf().write(|w| w.dir().output().drive().h0h1()); + r.psel.miso.write(|w| unsafe { w.bits(miso.psel_bits()) }); + } + + // Enable SPIS instance. + r.enable.write(|w| w.enable().enabled()); + + // Configure mode. + let mode = config.mode; + r.config.write(|w| { + match mode { + MODE_0 => { + w.order().msb_first(); + w.cpol().active_high(); + w.cpha().leading(); + } + MODE_1 => { + w.order().msb_first(); + w.cpol().active_high(); + w.cpha().trailing(); + } + MODE_2 => { + w.order().msb_first(); + w.cpol().active_low(); + w.cpha().leading(); + } + MODE_3 => { + w.order().msb_first(); + w.cpol().active_low(); + w.cpha().trailing(); + } + } + + w + }); + + // Set over-read character. + let orc = config.orc; + r.orc.write(|w| unsafe { w.orc().bits(orc) }); + + // Set default character. + let def = config.def; + r.def.write(|w| unsafe { w.def().bits(def) }); + + // Configure auto-acquire on 'transfer end' event. + if config.auto_acquire { + r.shorts.write(|w| w.end_acquire().bit(true)); + } + + // Disable all events interrupts. + r.intenclr.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + Self { _p: spis } + } + + fn prepare(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(), Error> { + slice_in_ram_or(tx, Error::BufferNotInRAM)?; + // NOTE: RAM slice check for rx is not necessary, as a mutable + // slice can only be built from data located in RAM. + + compiler_fence(Ordering::SeqCst); + + let r = T::regs(); + + // Set up the DMA write. + let (ptr, len) = slice_ptr_parts(tx); + r.txd.ptr.write(|w| unsafe { w.ptr().bits(ptr as _) }); + r.txd.maxcnt.write(|w| unsafe { w.maxcnt().bits(len as _) }); + + // Set up the DMA read. + let (ptr, len) = slice_ptr_parts_mut(rx); + r.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as _) }); + r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(len as _) }); + + // Reset end event. + r.events_end.reset(); + + // Release the semaphore. + r.tasks_release.write(|w| unsafe { w.bits(1) }); + + Ok(()) + } + + fn blocking_inner_from_ram(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(usize, usize), Error> { + compiler_fence(Ordering::SeqCst); + let r = T::regs(); + + // Acquire semaphore. + if r.semstat.read().bits() != 1 { + r.events_acquired.reset(); + r.tasks_acquire.write(|w| unsafe { w.bits(1) }); + // Wait until CPU has acquired the semaphore. + while r.semstat.read().bits() != 1 {} + } + + self.prepare(rx, tx)?; + + // Wait for 'end' event. + while r.events_end.read().bits() == 0 {} + + let n_rx = r.rxd.amount.read().bits() as usize; + let n_tx = r.txd.amount.read().bits() as usize; + + compiler_fence(Ordering::SeqCst); + + Ok((n_rx, n_tx)) + } + + fn blocking_inner(&mut self, rx: &mut [u8], tx: &[u8]) -> Result<(usize, usize), Error> { + match self.blocking_inner_from_ram(rx, tx) { + Ok(n) => Ok(n), + Err(Error::BufferNotInRAM) => { + trace!("Copying SPIS tx buffer into RAM for DMA"); + let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..tx.len()]; + tx_ram_buf.copy_from_slice(tx); + self.blocking_inner_from_ram(rx, tx_ram_buf) + } + Err(error) => Err(error), + } + } + + async fn async_inner_from_ram(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(usize, usize), Error> { + let r = T::regs(); + let s = T::state(); + + // Clear status register. + r.status.write(|w| w.overflow().clear().overread().clear()); + + // Acquire semaphore. + if r.semstat.read().bits() != 1 { + // Reset and enable the acquire event. + r.events_acquired.reset(); + r.intenset.write(|w| w.acquired().set()); + + // Request acquiring the SPIS semaphore. + r.tasks_acquire.write(|w| unsafe { w.bits(1) }); + + // Wait until CPU has acquired the semaphore. + poll_fn(|cx| { + s.waker.register(cx.waker()); + if r.events_acquired.read().bits() == 1 { + r.events_acquired.reset(); + return Poll::Ready(()); + } + Poll::Pending + }) + .await; + } + + self.prepare(rx, tx)?; + + // Wait for 'end' event. + r.intenset.write(|w| w.end().set()); + poll_fn(|cx| { + s.waker.register(cx.waker()); + if r.events_end.read().bits() != 0 { + r.events_end.reset(); + return Poll::Ready(()); + } + Poll::Pending + }) + .await; + + let n_rx = r.rxd.amount.read().bits() as usize; + let n_tx = r.txd.amount.read().bits() as usize; + + compiler_fence(Ordering::SeqCst); + + Ok((n_rx, n_tx)) + } + + async fn async_inner(&mut self, rx: &mut [u8], tx: &[u8]) -> Result<(usize, usize), Error> { + match self.async_inner_from_ram(rx, tx).await { + Ok(n) => Ok(n), + Err(Error::BufferNotInRAM) => { + trace!("Copying SPIS tx buffer into RAM for DMA"); + let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..tx.len()]; + tx_ram_buf.copy_from_slice(tx); + self.async_inner_from_ram(rx, tx_ram_buf).await + } + Err(error) => Err(error), + } + } + + /// Reads data from the SPI bus without sending anything. Blocks until `cs` is deasserted. + /// Returns number of bytes read. + pub fn blocking_read(&mut self, data: &mut [u8]) -> Result { + self.blocking_inner(data, &[]).map(|n| n.0) + } + + /// Simultaneously sends and receives data. Blocks until the transmission is completed. + /// If necessary, the write buffer will be copied into RAM (see struct description for detail). + /// Returns number of bytes transferred `(n_rx, n_tx)`. + pub fn blocking_transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(usize, usize), Error> { + self.blocking_inner(read, write) + } + + /// Same as [`blocking_transfer`](Spis::blocking_transfer) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. + /// Returns number of bytes transferred `(n_rx, n_tx)`. + pub fn blocking_transfer_from_ram(&mut self, read: &mut [u8], write: &[u8]) -> Result<(usize, usize), Error> { + self.blocking_inner_from_ram(read, write) + } + + /// Simultaneously sends and receives data. + /// Places the received data into the same buffer and blocks until the transmission is completed. + /// Returns number of bytes transferred. + pub fn blocking_transfer_in_place(&mut self, data: &mut [u8]) -> Result { + self.blocking_inner_from_ram(data, data).map(|n| n.0) + } + + /// Sends data, discarding any received data. Blocks until the transmission is completed. + /// If necessary, the write buffer will be copied into RAM (see struct description for detail). + /// Returns number of bytes written. + pub fn blocking_write(&mut self, data: &[u8]) -> Result { + self.blocking_inner(&mut [], data).map(|n| n.1) + } + + /// Same as [`blocking_write`](Spis::blocking_write) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. + /// Returns number of bytes written. + pub fn blocking_write_from_ram(&mut self, data: &[u8]) -> Result { + self.blocking_inner_from_ram(&mut [], data).map(|n| n.1) + } + + /// Reads data from the SPI bus without sending anything. + /// Returns number of bytes read. + pub async fn read(&mut self, data: &mut [u8]) -> Result { + self.async_inner(data, &[]).await.map(|n| n.0) + } + + /// Simultaneously sends and receives data. + /// If necessary, the write buffer will be copied into RAM (see struct description for detail). + /// Returns number of bytes transferred `(n_rx, n_tx)`. + pub async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(usize, usize), Error> { + self.async_inner(read, write).await + } + + /// Same as [`transfer`](Spis::transfer) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. + /// Returns number of bytes transferred `(n_rx, n_tx)`. + pub async fn transfer_from_ram(&mut self, read: &mut [u8], write: &[u8]) -> Result<(usize, usize), Error> { + self.async_inner_from_ram(read, write).await + } + + /// Simultaneously sends and receives data. Places the received data into the same buffer. + /// Returns number of bytes transferred. + pub async fn transfer_in_place(&mut self, data: &mut [u8]) -> Result { + self.async_inner_from_ram(data, data).await.map(|n| n.0) + } + + /// Sends data, discarding any received data. + /// If necessary, the write buffer will be copied into RAM (see struct description for detail). + /// Returns number of bytes written. + pub async fn write(&mut self, data: &[u8]) -> Result { + self.async_inner(&mut [], data).await.map(|n| n.1) + } + + /// Same as [`write`](Spis::write) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. + /// Returns number of bytes written. + pub async fn write_from_ram(&mut self, data: &[u8]) -> Result { + self.async_inner_from_ram(&mut [], data).await.map(|n| n.1) + } + + /// Checks if last transaction overread. + pub fn is_overread(&mut self) -> bool { + T::regs().status.read().overread().is_present() + } + + /// Checks if last transaction overflowed. + pub fn is_overflow(&mut self) -> bool { + T::regs().status.read().overflow().is_present() + } +} + +impl<'d, T: Instance> Drop for Spis<'d, T> { + fn drop(&mut self) { + trace!("spis drop"); + + // Disable + let r = T::regs(); + r.enable.write(|w| w.enable().disabled()); + + gpio::deconfigure_pin(r.psel.sck.read().bits()); + gpio::deconfigure_pin(r.psel.csn.read().bits()); + gpio::deconfigure_pin(r.psel.miso.read().bits()); + gpio::deconfigure_pin(r.psel.mosi.read().bits()); + + trace!("spis drop: done"); + } +} + +pub(crate) mod sealed { + use embassy_sync::waitqueue::AtomicWaker; + + use super::*; + + pub struct State { + pub waker: AtomicWaker, + } + + impl State { + pub const fn new() -> Self { + Self { + waker: AtomicWaker::new(), + } + } + } + + pub trait Instance { + fn regs() -> &'static pac::spis0::RegisterBlock; + fn state() -> &'static State; + } +} + +/// SPIS peripheral instance +pub trait Instance: Peripheral

+ sealed::Instance + 'static { + /// Interrupt for this peripheral. + type Interrupt: interrupt::typelevel::Interrupt; +} + +macro_rules! impl_spis { + ($type:ident, $pac_type:ident, $irq:ident) => { + impl crate::spis::sealed::Instance for peripherals::$type { + fn regs() -> &'static pac::spis0::RegisterBlock { + unsafe { &*pac::$pac_type::ptr() } + } + fn state() -> &'static crate::spis::sealed::State { + static STATE: crate::spis::sealed::State = crate::spis::sealed::State::new(); + &STATE + } + } + impl crate::spis::Instance for peripherals::$type { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +} + +// ==================== + +impl<'d, T: Instance> SetConfig for Spis<'d, T> { + type Config = Config; + fn set_config(&mut self, config: &Self::Config) { + let r = T::regs(); + // Configure mode. + let mode = config.mode; + r.config.write(|w| { + match mode { + MODE_0 => { + w.order().msb_first(); + w.cpol().active_high(); + w.cpha().leading(); + } + MODE_1 => { + w.order().msb_first(); + w.cpol().active_high(); + w.cpha().trailing(); + } + MODE_2 => { + w.order().msb_first(); + w.cpol().active_low(); + w.cpha().leading(); + } + MODE_3 => { + w.order().msb_first(); + w.cpol().active_low(); + w.cpha().trailing(); + } + } + + w + }); + + // Set over-read character. + let orc = config.orc; + r.orc.write(|w| unsafe { w.orc().bits(orc) }); + + // Set default character. + let def = config.def; + r.def.write(|w| unsafe { w.def().bits(def) }); + + // Configure auto-acquire on 'transfer end' event. + let auto_acquire = config.auto_acquire; + r.shorts.write(|w| w.end_acquire().bit(auto_acquire)); + } +} diff --git a/embassy-nrf/src/temp.rs b/embassy-nrf/src/temp.rs index d520fd686..491e92c04 100644 --- a/embassy-nrf/src/temp.rs +++ b/embassy-nrf/src/temp.rs @@ -1,37 +1,50 @@ -//! Temperature sensor interface. +//! Builtin temperature sensor driver. +use core::future::poll_fn; use core::task::Poll; use embassy_hal_common::drop::OnDrop; use embassy_hal_common::{into_ref, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; use fixed::types::I30F2; -use futures::future::poll_fn; use crate::interrupt::InterruptExt; use crate::peripherals::TEMP; use crate::{interrupt, pac, Peripheral}; -/// Integrated temperature sensor. +/// Interrupt handler. +pub struct InterruptHandler { + _private: (), +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let r = unsafe { &*pac::TEMP::PTR }; + r.intenclr.write(|w| w.datardy().clear()); + WAKER.wake(); + } +} + +/// Builtin temperature sensor driver. pub struct Temp<'d> { - _irq: PeripheralRef<'d, interrupt::TEMP>, + _peri: PeripheralRef<'d, TEMP>, } static WAKER: AtomicWaker = AtomicWaker::new(); impl<'d> Temp<'d> { - pub fn new(_t: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd) -> Self { - into_ref!(_t, irq); + /// Create a new temperature sensor driver. + pub fn new( + _peri: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding + 'd, + ) -> Self { + into_ref!(_peri); // Enable interrupt that signals temperature values - irq.disable(); - irq.set_handler(|_| { - let t = Self::regs(); - t.intenclr.write(|w| w.datardy().clear()); - WAKER.wake(); - }); - irq.enable(); - Self { _irq: irq } + interrupt::TEMP.unpend(); + unsafe { interrupt::TEMP.enable() }; + + Self { _peri } } /// Perform an asynchronous temperature measurement. The returned future @@ -42,8 +55,19 @@ impl<'d> Temp<'d> { /// # Example /// /// ```no_run - /// let mut t = Temp::new(p.TEMP, interrupt::take!(TEMP)); + /// use embassy_nrf::{bind_interrupts, temp}; + /// use embassy_nrf::temp::Temp; + /// use embassy_time::{Duration, Timer}; + /// + /// bind_interrupts!(struct Irqs { + /// TEMP => temp::InterruptHandler; + /// }); + /// + /// # async { + /// # let p: embassy_nrf::Peripherals = todo!(); + /// let mut t = Temp::new(p.TEMP, Irqs); /// let v: u16 = t.read().await.to_num::(); + /// # }; /// ``` pub async fn read(&mut self) -> I30F2 { // In case the future is dropped, stop the task and reset events. diff --git a/embassy-nrf/src/time_driver.rs b/embassy-nrf/src/time_driver.rs index c32a44637..f1ab4f8fd 100644 --- a/embassy-nrf/src/time_driver.rs +++ b/embassy-nrf/src/time_driver.rs @@ -7,7 +7,7 @@ use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::blocking_mutex::CriticalSectionMutex as Mutex; use embassy_time::driver::{AlarmHandle, Driver}; -use crate::interrupt::{Interrupt, InterruptExt}; +use crate::interrupt::InterruptExt; use crate::{interrupt, pac}; fn rtc() -> &'static pac::rtc0::RegisterBlock { @@ -67,7 +67,7 @@ fn compare_n(n: usize) -> u32 { 1 << (n + 16) } -#[cfg(tests)] +#[cfg(test)] mod test { use super::*; @@ -142,9 +142,8 @@ impl RtcDriver { // Wait for clear while r.counter.read().bits() != 0 {} - let irq = unsafe { interrupt::RTC1::steal() }; - irq.set_priority(irq_prio); - irq.enable(); + interrupt::RTC1.set_priority(irq_prio); + unsafe { interrupt::RTC1.enable() }; } fn on_interrupt(&self) { @@ -243,22 +242,25 @@ impl Driver for RtcDriver { }) } - fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) { + fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool { critical_section::with(|cs| { let n = alarm.id() as _; let alarm = self.get_alarm(cs, alarm); alarm.timestamp.set(timestamp); - let t = self.now(); - - // If alarm timestamp has passed, trigger it instantly. - if timestamp <= t { - self.trigger_alarm(n, cs); - return; - } - let r = rtc(); + let t = self.now(); + if timestamp <= t { + // If alarm timestamp has passed the alarm will not fire. + // Disarm the alarm and return `false` to indicate that. + r.intenclr.write(|w| unsafe { w.bits(compare_n(n)) }); + + alarm.timestamp.set(u64::MAX); + + return false; + } + // If it hasn't triggered yet, setup it in the compare channel. // Write the CC value regardless of whether we're going to enable it now or not. @@ -287,10 +289,13 @@ impl Driver for RtcDriver { // It will be setup later by `next_period`. r.intenclr.write(|w| unsafe { w.bits(compare_n(n)) }); } + + true }) } } +#[cfg(feature = "rt")] #[interrupt] fn RTC1() { DRIVER.on_interrupt() diff --git a/embassy-nrf/src/timer.rs b/embassy-nrf/src/timer.rs index 3de5a8962..04748238d 100644 --- a/embassy-nrf/src/timer.rs +++ b/embassy-nrf/src/timer.rs @@ -1,14 +1,13 @@ +//! Timer driver. +//! +//! Important note! This driver is very low level. For most time-related use cases, like +//! "sleep for X seconds", "do something every X seconds", or measuring time, you should +//! use [`embassy-time`](https://crates.io/crates/embassy-time) instead! + #![macro_use] -use core::marker::PhantomData; -use core::task::Poll; - -use embassy_hal_common::drop::OnDrop; use embassy_hal_common::{into_ref, PeripheralRef}; -use embassy_sync::waitqueue::AtomicWaker; -use futures::future::poll_fn; -use crate::interrupt::{Interrupt, InterruptExt}; use crate::ppi::{Event, Task}; use crate::{pac, Peripheral}; @@ -20,17 +19,19 @@ pub(crate) mod sealed { /// The number of CC registers this instance has. const CCS: usize; fn regs() -> &'static pac::timer0::RegisterBlock; - /// Storage for the waker for CC register `n`. - fn waker(n: usize) -> &'static AtomicWaker; } pub trait ExtendedInstance {} pub trait TimerType {} } +/// Basic Timer instance. pub trait Instance: Peripheral

+ sealed::Instance + 'static + Send { - type Interrupt: Interrupt; + /// Interrupt for this peripheral. + type Interrupt: crate::interrupt::typelevel::Interrupt; } + +/// Extended timer instance. pub trait ExtendedInstance: Instance + sealed::ExtendedInstance {} macro_rules! impl_timer { @@ -40,15 +41,9 @@ macro_rules! impl_timer { fn regs() -> &'static pac::timer0::RegisterBlock { unsafe { &*(pac::$pac_type::ptr() as *const pac::timer0::RegisterBlock) } } - fn waker(n: usize) -> &'static ::embassy_sync::waitqueue::AtomicWaker { - use ::embassy_sync::waitqueue::AtomicWaker; - const NEW_AW: AtomicWaker = AtomicWaker::new(); - static WAKERS: [AtomicWaker; $ccs] = [NEW_AW; $ccs]; - &WAKERS[n] - } } impl crate::timer::Instance for peripherals::$type { - type Interrupt = crate::interrupt::$irq; + type Interrupt = crate::interrupt::typelevel::$irq; } }; ($type:ident, $pac_type:ident, $irq:ident) => { @@ -61,85 +56,77 @@ macro_rules! impl_timer { }; } +/// Timer frequency #[repr(u8)] pub enum Frequency { - // I'd prefer not to prefix these with `F`, but Rust identifiers can't start with digits. + /// 16MHz F16MHz = 0, + /// 8MHz F8MHz = 1, + /// 4MHz F4MHz = 2, + /// 2MHz F2MHz = 3, + /// 1MHz F1MHz = 4, + /// 500kHz F500kHz = 5, + /// 250kHz F250kHz = 6, + /// 125kHz F125kHz = 7, + /// 62500Hz F62500Hz = 8, + /// 31250Hz F31250Hz = 9, } /// nRF Timer driver. /// /// The timer has an internal counter, which is incremented for every tick of the timer. -/// The counter is 32-bit, so it wraps back to 0 at 4294967296. +/// The counter is 32-bit, so it wraps back to 0 when it reaches 2^32. /// /// It has either 4 or 6 Capture/Compare registers, which can be used to capture the current state of the counter /// or trigger an event when the counter reaches a certain value. -pub trait TimerType: sealed::TimerType {} - -pub enum Awaitable {} -pub enum NotAwaitable {} - -impl sealed::TimerType for Awaitable {} -impl sealed::TimerType for NotAwaitable {} -impl TimerType for Awaitable {} -impl TimerType for NotAwaitable {} - -pub struct Timer<'d, T: Instance, I: TimerType = NotAwaitable> { +/// Timer driver. +pub struct Timer<'d, T: Instance> { _p: PeripheralRef<'d, T>, - _i: PhantomData, } -impl<'d, T: Instance> Timer<'d, T, Awaitable> { - pub fn new_awaitable(timer: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd) -> Self { - into_ref!(irq); - - irq.set_handler(Self::on_interrupt); - irq.unpend(); - irq.enable(); - - Self::new_irqless(timer) - } -} -impl<'d, T: Instance> Timer<'d, T, NotAwaitable> { - /// Create a `Timer` without an interrupt, meaning `Cc::wait` won't work. +impl<'d, T: Instance> Timer<'d, T> { + /// Create a new `Timer` driver. /// /// This can be useful for triggering tasks via PPI /// `Uarte` uses this internally. pub fn new(timer: impl Peripheral

+ 'd) -> Self { - Self::new_irqless(timer) + Self::new_inner(timer, false) } -} -impl<'d, T: Instance, I: TimerType> Timer<'d, T, I> { - /// Create a `Timer` without an interrupt, meaning `Cc::wait` won't work. + /// Create a new `Timer` driver in counter mode. /// - /// This is used by the public constructors. - fn new_irqless(timer: impl Peripheral

+ 'd) -> Self { + /// This can be useful for triggering tasks via PPI + /// `Uarte` uses this internally. + pub fn new_counter(timer: impl Peripheral

+ 'd) -> Self { + Self::new_inner(timer, true) + } + + fn new_inner(timer: impl Peripheral

+ 'd, is_counter: bool) -> Self { into_ref!(timer); let regs = T::regs(); - let mut this = Self { - _p: timer, - _i: PhantomData, - }; + let this = Self { _p: timer }; // Stop the timer before doing anything else, // since changing BITMODE while running can cause 'unpredictable behaviour' according to the specification. this.stop(); - // Set the instance to timer mode. - regs.mode.write(|w| w.mode().timer()); + if is_counter { + regs.mode.write(|w| w.mode().low_power_counter()); + } else { + regs.mode.write(|w| w.mode().timer()); + } // Make the counter's max value as high as possible. // TODO: is there a reason someone would want to set this lower? @@ -181,24 +168,32 @@ impl<'d, T: Instance, I: TimerType> Timer<'d, T, I> { /// Returns the START task, for use with PPI. /// /// When triggered, this task starts the timer. - pub fn task_start(&self) -> Task { + pub fn task_start(&self) -> Task<'d> { Task::from_reg(&T::regs().tasks_start) } /// Returns the STOP task, for use with PPI. /// /// When triggered, this task stops the timer. - pub fn task_stop(&self) -> Task { + pub fn task_stop(&self) -> Task<'d> { Task::from_reg(&T::regs().tasks_stop) } /// Returns the CLEAR task, for use with PPI. /// /// When triggered, this task resets the timer's counter to 0. - pub fn task_clear(&self) -> Task { + pub fn task_clear(&self) -> Task<'d> { Task::from_reg(&T::regs().tasks_clear) } + /// Returns the COUNT task, for use with PPI. + /// + /// When triggered, this task increments the timer's counter by 1. + /// Only works in counter mode. + pub fn task_count(&self) -> Task<'d> { + Task::from_reg(&T::regs().tasks_count) + } + /// Change the timer's frequency. /// /// This will stop the timer if it isn't already stopped, @@ -213,31 +208,17 @@ impl<'d, T: Instance, I: TimerType> Timer<'d, T, I> { .write(|w| unsafe { w.prescaler().bits(frequency as u8) }) } - fn on_interrupt(_: *mut ()) { - let regs = T::regs(); - for n in 0..T::CCS { - if regs.events_compare[n].read().bits() != 0 { - // Clear the interrupt, otherwise the interrupt will be repeatedly raised as soon as the interrupt handler exits. - // We can't clear the event, because it's used to poll whether the future is done or still pending. - regs.intenclr - .modify(|r, w| unsafe { w.bits(r.bits() | (1 << (16 + n))) }); - T::waker(n).wake(); - } - } - } - /// Returns this timer's `n`th CC register. /// /// # Panics /// Panics if `n` >= the number of CC registers this timer has (4 for a normal timer, 6 for an extended timer). - pub fn cc(&mut self, n: usize) -> Cc { + pub fn cc(&self, n: usize) -> Cc<'d, T> { if n >= T::CCS { panic!("Cannot get CC register {} of timer with {} CC registers.", n, T::CCS); } Cc { n, - _p: self._p.reborrow(), - _i: PhantomData, + _p: unsafe { self._p.clone_unchecked() }, } } } @@ -249,49 +230,12 @@ impl<'d, T: Instance, I: TimerType> Timer<'d, T, I> { /// /// The timer will fire the register's COMPARE event when its counter reaches the value stored in the register. /// When the register's CAPTURE task is triggered, the timer will store the current value of its counter in the register -pub struct Cc<'d, T: Instance, I: TimerType = NotAwaitable> { +pub struct Cc<'d, T: Instance> { n: usize, _p: PeripheralRef<'d, T>, - _i: PhantomData, } -impl<'d, T: Instance> Cc<'d, T, Awaitable> { - /// Wait until the timer's counter reaches the value stored in this register. - /// - /// This requires a mutable reference so that this task's waker cannot be overwritten by a second call to `wait`. - pub async fn wait(&mut self) { - let regs = T::regs(); - - // Enable the interrupt for this CC's COMPARE event. - regs.intenset - .modify(|r, w| unsafe { w.bits(r.bits() | (1 << (16 + self.n))) }); - - // Disable the interrupt if the future is dropped. - let on_drop = OnDrop::new(|| { - regs.intenclr - .modify(|r, w| unsafe { w.bits(r.bits() | (1 << (16 + self.n))) }); - }); - - poll_fn(|cx| { - T::waker(self.n).register(cx.waker()); - - if regs.events_compare[self.n].read().bits() != 0 { - // Reset the register for next time - regs.events_compare[self.n].reset(); - Poll::Ready(()) - } else { - Poll::Pending - } - }) - .await; - - // The interrupt was already disabled in the interrupt handler, so there's no need to disable it again. - on_drop.defuse(); - } -} -impl<'d, T: Instance> Cc<'d, T, NotAwaitable> {} - -impl<'d, T: Instance, I: TimerType> Cc<'d, T, I> { +impl<'d, T: Instance> Cc<'d, T> { /// Get the current value stored in the register. pub fn read(&self) -> u32 { T::regs().cc[self.n].read().cc().bits() @@ -314,14 +258,14 @@ impl<'d, T: Instance, I: TimerType> Cc<'d, T, I> { /// Returns this CC register's CAPTURE task, for use with PPI. /// /// When triggered, this task will capture the current value of the timer's counter in this register. - pub fn task_capture(&self) -> Task { + pub fn task_capture(&self) -> Task<'d> { Task::from_reg(&T::regs().tasks_capture) } /// Returns this CC register's COMPARE event, for use with PPI. /// /// This event will fire when the timer's counter reaches the value in this CC register. - pub fn event_compare(&self) -> Event { + pub fn event_compare(&self) -> Event<'d> { Event::from_reg(&T::regs().events_compare[self.n]) } diff --git a/embassy-nrf/src/twim.rs b/embassy-nrf/src/twim.rs index 850f6d0fa..2ad0d19b1 100644 --- a/embassy-nrf/src/twim.rs +++ b/embassy-nrf/src/twim.rs @@ -1,12 +1,9 @@ +//! I2C-compatible Two Wire Interface in master mode (TWIM) driver. + #![macro_use] -//! HAL interface to the TWIM peripheral. -//! -//! See product specification: -//! -//! - nRF52832: Section 33 -//! - nRF52840: Section 6.31 -use core::future::Future; +use core::future::{poll_fn, Future}; +use core::marker::PhantomData; use core::sync::atomic::compiler_fence; use core::sync::atomic::Ordering::SeqCst; use core::task::Poll; @@ -16,30 +13,46 @@ use embassy_hal_common::{into_ref, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; #[cfg(feature = "time")] use embassy_time::{Duration, Instant}; -use futures::future::poll_fn; use crate::chip::{EASY_DMA_SIZE, FORCE_COPY_BUFFER_SIZE}; use crate::gpio::Pin as GpioPin; -use crate::interrupt::{Interrupt, InterruptExt}; +use crate::interrupt::typelevel::Interrupt; use crate::util::{slice_in_ram, slice_in_ram_or}; -use crate::{gpio, pac, Peripheral}; +use crate::{gpio, interrupt, pac, Peripheral}; +/// TWI frequency #[derive(Clone, Copy)] pub enum Frequency { - #[doc = "26738688: 100 kbps"] + /// 100 kbps K100 = 26738688, - #[doc = "67108864: 250 kbps"] + /// 250 kbps K250 = 67108864, - #[doc = "104857600: 400 kbps"] + /// 400 kbps K400 = 104857600, } +/// TWIM config. #[non_exhaustive] pub struct Config { + /// Frequency pub frequency: Frequency, + + /// Enable high drive for the SDA line. pub sda_high_drive: bool, + + /// Enable internal pullup for the SDA line. + /// + /// Note that using external pullups is recommended for I2C, and + /// most boards already have them. pub sda_pullup: bool, + + /// Enable high drive for the SCL line. pub scl_high_drive: bool, + + /// Enable internal pullup for the SCL line. + /// + /// Note that using external pullups is recommended for I2C, and + /// most boards already have them. pub scl_pullup: bool, } @@ -55,37 +68,67 @@ impl Default for Config { } } +/// TWI error. #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum Error { + /// TX buffer was too long. TxBufferTooLong, + /// RX buffer was too long. RxBufferTooLong, + /// Data transmit failed. Transmit, + /// Data reception failed. Receive, - DMABufferNotInDataMemory, + /// The buffer is not in data RAM. It's most likely in flash, and nRF's DMA cannot access flash. + BufferNotInRAM, + /// Didn't receive an ACK bit after the address byte. Address might be wrong, or the i2c device chip might not be connected properly. AddressNack, + /// Didn't receive an ACK bit after a data byte. DataNack, + /// Overrun error. Overrun, + /// Timeout error. Timeout, } -/// Interface to a TWIM instance using EasyDMA to offload the transmission and reception workload. -/// -/// For more details about EasyDMA, consult the module documentation. +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let r = T::regs(); + let s = T::state(); + + if r.events_stopped.read().bits() != 0 { + s.end_waker.wake(); + r.intenclr.write(|w| w.stopped().clear()); + } + if r.events_error.read().bits() != 0 { + s.end_waker.wake(); + r.intenclr.write(|w| w.error().clear()); + } + } +} + +/// TWI driver. pub struct Twim<'d, T: Instance> { _p: PeripheralRef<'d, T>, } impl<'d, T: Instance> Twim<'d, T> { + /// Create a new TWI driver. pub fn new( twim: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, sda: impl Peripheral

+ 'd, scl: impl Peripheral

+ 'd, config: Config, ) -> Self { - into_ref!(twim, irq, sda, scl); + into_ref!(twim, sda, scl); let r = T::regs(); @@ -131,30 +174,15 @@ impl<'d, T: Instance> Twim<'d, T> { // Disable all events interrupts r.intenclr.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); - irq.set_handler(Self::on_interrupt); - irq.unpend(); - irq.enable(); + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; Self { _p: twim } } - fn on_interrupt(_: *mut ()) { - let r = T::regs(); - let s = T::state(); - - if r.events_stopped.read().bits() != 0 { - s.end_waker.wake(); - r.intenclr.write(|w| w.stopped().clear()); - } - if r.events_error.read().bits() != 0 { - s.end_waker.wake(); - r.intenclr.write(|w| w.error().clear()); - } - } - /// Set TX buffer, checking that it is in RAM and has suitable length. unsafe fn set_tx_buffer(&mut self, buffer: &[u8]) -> Result<(), Error> { - slice_in_ram_or(buffer, Error::DMABufferNotInDataMemory)?; + slice_in_ram_or(buffer, Error::BufferNotInRAM)?; if buffer.len() > EASY_DMA_SIZE { return Err(Error::TxBufferTooLong); @@ -234,7 +262,7 @@ impl<'d, T: Instance> Twim<'d, T> { return Err(Error::DataNack); } if err.overrun().is_received() { - return Err(Error::DataNack); + return Err(Error::Overrun); } Ok(()) } @@ -308,7 +336,7 @@ impl<'d, T: Instance> Twim<'d, T> { return Poll::Ready(()); } - // stop if an error occured + // stop if an error occurred if r.events_error.read().bits() != 0 { r.events_error.reset(); r.tasks_stop.write(|w| unsafe { w.bits(1) }); @@ -436,7 +464,7 @@ impl<'d, T: Instance> Twim<'d, T> { ) -> Result<(), Error> { match self.setup_write_read_from_ram(address, wr_buffer, rd_buffer, inten) { Ok(_) => Ok(()), - Err(Error::DMABufferNotInDataMemory) => { + Err(Error::BufferNotInRAM) => { trace!("Copying TWIM tx buffer into RAM for DMA"); let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..wr_buffer.len()]; tx_ram_buf.copy_from_slice(wr_buffer); @@ -449,7 +477,7 @@ impl<'d, T: Instance> Twim<'d, T> { fn setup_write(&mut self, address: u8, wr_buffer: &[u8], inten: bool) -> Result<(), Error> { match self.setup_write_from_ram(address, wr_buffer, inten) { Ok(_) => Ok(()), - Err(Error::DMABufferNotInDataMemory) => { + Err(Error::BufferNotInRAM) => { trace!("Copying TWIM tx buffer into RAM for DMA"); let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..wr_buffer.len()]; tx_ram_buf.copy_from_slice(wr_buffer); @@ -613,6 +641,10 @@ impl<'d, T: Instance> Twim<'d, T> { // =========================================== + /// Read from an I2C slave. + /// + /// The buffer must have a length of at most 255 bytes on the nRF52832 + /// and at most 65535 bytes on the nRF52840. pub async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> { self.setup_read(address, buffer, true)?; self.async_wait().await; @@ -622,6 +654,10 @@ impl<'d, T: Instance> Twim<'d, T> { Ok(()) } + /// Write to an I2C slave. + /// + /// The buffer must have a length of at most 255 bytes on the nRF52832 + /// and at most 65535 bytes on the nRF52840. pub async fn write(&mut self, address: u8, buffer: &[u8]) -> Result<(), Error> { self.setup_write(address, buffer, true)?; self.async_wait().await; @@ -641,6 +677,11 @@ impl<'d, T: Instance> Twim<'d, T> { Ok(()) } + /// Write data to an I2C slave, then read data from the slave without + /// triggering a stop condition between the two. + /// + /// The buffers must have a length of at most 255 bytes on the nRF52832 + /// and at most 65535 bytes on the nRF52840. pub async fn write_read(&mut self, address: u8, wr_buffer: &[u8], rd_buffer: &mut [u8]) -> Result<(), Error> { self.setup_write_read(address, wr_buffer, rd_buffer, true)?; self.async_wait().await; @@ -706,8 +747,10 @@ pub(crate) mod sealed { } } +/// TWIM peripheral instance. pub trait Instance: Peripheral

+ sealed::Instance + 'static { - type Interrupt: Interrupt; + /// Interrupt for this peripheral. + type Interrupt: interrupt::typelevel::Interrupt; } macro_rules! impl_twim { @@ -722,7 +765,7 @@ macro_rules! impl_twim { } } impl crate::twim::Instance for peripherals::$type { - type Interrupt = crate::interrupt::$irq; + type Interrupt = crate::interrupt::typelevel::$irq; } }; } @@ -777,7 +820,7 @@ mod eh1 { Self::RxBufferTooLong => embedded_hal_1::i2c::ErrorKind::Other, Self::Transmit => embedded_hal_1::i2c::ErrorKind::Other, Self::Receive => embedded_hal_1::i2c::ErrorKind::Other, - Self::DMABufferNotInDataMemory => embedded_hal_1::i2c::ErrorKind::Other, + Self::BufferNotInRAM => embedded_hal_1::i2c::ErrorKind::Other, Self::AddressNack => { embedded_hal_1::i2c::ErrorKind::NoAcknowledge(embedded_hal_1::i2c::NoAcknowledgeSource::Address) } @@ -794,7 +837,7 @@ mod eh1 { type Error = Error; } - impl<'d, T: Instance> embedded_hal_1::i2c::blocking::I2c for Twim<'d, T> { + impl<'d, T: Instance> embedded_hal_1::i2c::I2c for Twim<'d, T> { fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { self.blocking_read(address, buffer) } @@ -803,20 +846,6 @@ mod eh1 { self.blocking_write(address, buffer) } - 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 write_read(&mut self, address: u8, wr_buffer: &[u8], rd_buffer: &mut [u8]) -> Result<(), Self::Error> { self.blocking_write_read(address, wr_buffer, rd_buffer) } @@ -824,57 +853,36 @@ mod eh1 { fn transaction<'a>( &mut self, _address: u8, - _operations: &mut [embedded_hal_1::i2c::blocking::Operation<'a>], + _operations: &mut [embedded_hal_1::i2c::Operation<'a>], ) -> Result<(), Self::Error> { todo!(); } - - fn transaction_iter<'a, O>(&mut self, _address: u8, _operations: O) -> Result<(), Self::Error> - where - O: IntoIterator>, - { - todo!(); - } } } -cfg_if::cfg_if! { - if #[cfg(all(feature = "unstable-traits", feature = "nightly"))] { - impl<'d, T: Instance> embedded_hal_async::i2c::I2c for Twim<'d, T> { - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; +#[cfg(all(feature = "unstable-traits", feature = "nightly"))] +mod eha { + use super::*; + impl<'d, T: Instance> embedded_hal_async::i2c::I2c for Twim<'d, T> { + async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> { + self.read(address, read).await + } - fn read<'a>(&'a mut self, address: u8, buffer: &'a mut [u8]) -> Self::ReadFuture<'a> { - self.read(address, buffer) - } + async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> { + self.write(address, write).await + } + async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> { + self.write_read(address, write, read).await + } - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - - fn write<'a>(&'a mut self, address: u8, bytes: &'a [u8]) -> Self::WriteFuture<'a> { - self.write(address, bytes) - } - - type WriteReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn write_read<'a>( - &'a mut self, - address: u8, - wr_buffer: &'a [u8], - rd_buffer: &'a mut [u8], - ) -> Self::WriteReadFuture<'a> { - self.write_read(address, wr_buffer, rd_buffer) - } - - type TransactionFuture<'a, 'b> = impl Future> + 'a where Self: 'a, 'b: 'a; - - fn transaction<'a, 'b>( - &'a mut self, - address: u8, - operations: &'a mut [embedded_hal_async::i2c::Operation<'b>], - ) -> Self::TransactionFuture<'a, 'b> { - let _ = address; - let _ = operations; - async move { todo!() } - } + async fn transaction( + &mut self, + address: u8, + operations: &mut [embedded_hal_1::i2c::Operation<'_>], + ) -> Result<(), Self::Error> { + let _ = address; + let _ = operations; + todo!() } } } diff --git a/embassy-nrf/src/twis.rs b/embassy-nrf/src/twis.rs new file mode 100644 index 000000000..a115d5616 --- /dev/null +++ b/embassy-nrf/src/twis.rs @@ -0,0 +1,799 @@ +//! I2C-compatible Two Wire Interface in slave mode (TWIM) driver. + +#![macro_use] + +use core::future::{poll_fn, Future}; +use core::marker::PhantomData; +use core::sync::atomic::compiler_fence; +use core::sync::atomic::Ordering::SeqCst; +use core::task::Poll; + +use embassy_hal_common::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; +#[cfg(feature = "time")] +use embassy_time::{Duration, Instant}; + +use crate::chip::{EASY_DMA_SIZE, FORCE_COPY_BUFFER_SIZE}; +use crate::gpio::Pin as GpioPin; +use crate::interrupt::typelevel::Interrupt; +use crate::util::slice_in_ram_or; +use crate::{gpio, interrupt, pac, Peripheral}; + +/// TWIS config. +#[non_exhaustive] +pub struct Config { + /// First address + pub address0: u8, + + /// Second address, optional. + pub address1: Option, + + /// Overread character. + /// + /// If the master keeps clocking the bus after all the bytes in the TX buffer have + /// already been transmitted, this byte will be constantly transmitted. + pub orc: u8, + + /// Enable high drive for the SDA line. + pub sda_high_drive: bool, + + /// Enable internal pullup for the SDA line. + /// + /// Note that using external pullups is recommended for I2C, and + /// most boards already have them. + pub sda_pullup: bool, + + /// Enable high drive for the SCL line. + pub scl_high_drive: bool, + + /// Enable internal pullup for the SCL line. + /// + /// Note that using external pullups is recommended for I2C, and + /// most boards already have them. + pub scl_pullup: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + address0: 0x55, + address1: None, + orc: 0x00, + scl_high_drive: false, + sda_pullup: false, + sda_high_drive: false, + scl_pullup: false, + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +enum Status { + Read, + Write, +} + +/// TWIS error. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + /// TX buffer was too long. + TxBufferTooLong, + /// RX buffer was too long. + RxBufferTooLong, + /// Didn't receive an ACK bit after a data byte. + DataNack, + /// Bus error. + Bus, + /// The buffer is not in data RAM. It's most likely in flash, and nRF's DMA cannot access flash. + BufferNotInRAM, + /// Overflow + Overflow, + /// Overread + OverRead, + /// Timeout + Timeout, +} + +/// Received command +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Command { + /// Read + Read, + /// Write+read + WriteRead(usize), + /// Write + Write(usize), +} + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let r = T::regs(); + let s = T::state(); + + if r.events_read.read().bits() != 0 || r.events_write.read().bits() != 0 { + s.waker.wake(); + r.intenclr.modify(|_r, w| w.read().clear().write().clear()); + } + if r.events_stopped.read().bits() != 0 { + s.waker.wake(); + r.intenclr.modify(|_r, w| w.stopped().clear()); + } + if r.events_error.read().bits() != 0 { + s.waker.wake(); + r.intenclr.modify(|_r, w| w.error().clear()); + } + } +} + +/// TWIS driver. +pub struct Twis<'d, T: Instance> { + _p: PeripheralRef<'d, T>, +} + +impl<'d, T: Instance> Twis<'d, T> { + /// Create a new TWIS driver. + pub fn new( + twis: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + sda: impl Peripheral

+ 'd, + scl: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(twis, sda, scl); + + let r = T::regs(); + + // Configure pins + sda.conf().write(|w| { + w.dir().input(); + w.input().connect(); + if config.sda_high_drive { + w.drive().h0d1(); + } else { + w.drive().s0d1(); + } + if config.sda_pullup { + w.pull().pullup(); + } + w + }); + scl.conf().write(|w| { + w.dir().input(); + w.input().connect(); + if config.scl_high_drive { + w.drive().h0d1(); + } else { + w.drive().s0d1(); + } + if config.scl_pullup { + w.pull().pullup(); + } + w + }); + + // Select pins. + r.psel.sda.write(|w| unsafe { w.bits(sda.psel_bits()) }); + r.psel.scl.write(|w| unsafe { w.bits(scl.psel_bits()) }); + + // Enable TWIS instance. + r.enable.write(|w| w.enable().enabled()); + + // Disable all events interrupts + r.intenclr.write(|w| unsafe { w.bits(0xFFFF_FFFF) }); + + // Set address + r.address[0].write(|w| unsafe { w.address().bits(config.address0) }); + r.config.write(|w| w.address0().enabled()); + if let Some(address1) = config.address1 { + r.address[1].write(|w| unsafe { w.address().bits(address1) }); + r.config.modify(|_r, w| w.address1().enabled()); + } + + // Set over-read character + r.orc.write(|w| unsafe { w.orc().bits(config.orc) }); + + // Generate suspend on read event + r.shorts.write(|w| w.read_suspend().enabled()); + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + Self { _p: twis } + } + + /// Set TX buffer, checking that it is in RAM and has suitable length. + unsafe fn set_tx_buffer(&mut self, buffer: &[u8]) -> Result<(), Error> { + slice_in_ram_or(buffer, Error::BufferNotInRAM)?; + + if buffer.len() > EASY_DMA_SIZE { + return Err(Error::TxBufferTooLong); + } + + let r = T::regs(); + + r.txd.ptr.write(|w| + // We're giving the register a pointer to the stack. Since we're + // waiting for the I2C transaction to end before this stack pointer + // becomes invalid, there's nothing wrong here. + // + // The PTR field is a full 32 bits wide and accepts the full range + // of values. + w.ptr().bits(buffer.as_ptr() as u32)); + r.txd.maxcnt.write(|w| + // We're giving it the length of the buffer, so no danger of + // accessing invalid memory. We have verified that the length of the + // buffer fits in an `u8`, so the cast to `u8` is also fine. + // + // The MAXCNT field is 8 bits wide and accepts the full range of + // values. + w.maxcnt().bits(buffer.len() as _)); + + Ok(()) + } + + /// Set RX buffer, checking that it has suitable length. + unsafe fn set_rx_buffer(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + // NOTE: RAM slice check is not necessary, as a mutable + // slice can only be built from data located in RAM. + + if buffer.len() > EASY_DMA_SIZE { + return Err(Error::RxBufferTooLong); + } + + let r = T::regs(); + + r.rxd.ptr.write(|w| + // We're giving the register a pointer to the stack. Since we're + // waiting for the I2C transaction to end before this stack pointer + // becomes invalid, there's nothing wrong here. + // + // The PTR field is a full 32 bits wide and accepts the full range + // of values. + w.ptr().bits(buffer.as_mut_ptr() as u32)); + r.rxd.maxcnt.write(|w| + // We're giving it the length of the buffer, so no danger of + // accessing invalid memory. We have verified that the length of the + // buffer fits in an `u8`, so the cast to the type of maxcnt + // is also fine. + // + // Note that that nrf52840 maxcnt is a wider + // type than a u8, so we use a `_` cast rather than a `u8` cast. + // The MAXCNT field is thus at least 8 bits wide and accepts the + // full range of values that fit in a `u8`. + w.maxcnt().bits(buffer.len() as _)); + + Ok(()) + } + + fn clear_errorsrc(&mut self) { + let r = T::regs(); + r.errorsrc + .write(|w| w.overflow().bit(true).overread().bit(true).dnack().bit(true)); + } + + /// Returns matched address for latest command. + pub fn address_match(&self) -> u8 { + let r = T::regs(); + r.address[r.match_.read().bits() as usize].read().address().bits() + } + + /// Returns the index of the address matched in the latest command. + pub fn address_match_index(&self) -> usize { + T::regs().match_.read().bits() as _ + } + + /// Wait for read, write, stop or error + fn blocking_listen_wait(&mut self) -> Result { + let r = T::regs(); + loop { + if r.events_error.read().bits() != 0 { + r.events_error.reset(); + r.tasks_stop.write(|w| unsafe { w.bits(1) }); + while r.events_stopped.read().bits() == 0 {} + return Err(Error::Overflow); + } + if r.events_stopped.read().bits() != 0 { + r.events_stopped.reset(); + return Err(Error::Bus); + } + if r.events_read.read().bits() != 0 { + r.events_read.reset(); + return Ok(Status::Read); + } + if r.events_write.read().bits() != 0 { + r.events_write.reset(); + return Ok(Status::Write); + } + } + } + + /// Wait for stop, repeated start or error + fn blocking_listen_wait_end(&mut self, status: Status) -> Result { + let r = T::regs(); + loop { + // stop if an error occurred + if r.events_error.read().bits() != 0 { + r.events_error.reset(); + r.tasks_stop.write(|w| unsafe { w.bits(1) }); + return Err(Error::Overflow); + } else if r.events_stopped.read().bits() != 0 { + r.events_stopped.reset(); + return match status { + Status::Read => Ok(Command::Read), + Status::Write => { + let n = r.rxd.amount.read().bits() as usize; + Ok(Command::Write(n)) + } + }; + } else if r.events_read.read().bits() != 0 { + r.events_read.reset(); + let n = r.rxd.amount.read().bits() as usize; + return Ok(Command::WriteRead(n)); + } + } + } + + /// Wait for stop or error + fn blocking_wait(&mut self) -> Result { + let r = T::regs(); + loop { + // stop if an error occurred + if r.events_error.read().bits() != 0 { + r.events_error.reset(); + r.tasks_stop.write(|w| unsafe { w.bits(1) }); + let errorsrc = r.errorsrc.read(); + if errorsrc.overread().is_detected() { + return Err(Error::OverRead); + } else if errorsrc.dnack().is_received() { + return Err(Error::DataNack); + } else { + return Err(Error::Bus); + } + } else if r.events_stopped.read().bits() != 0 { + r.events_stopped.reset(); + let n = r.txd.amount.read().bits() as usize; + return Ok(n); + } + } + } + + /// Wait for stop or error with timeout + #[cfg(feature = "time")] + fn blocking_wait_timeout(&mut self, timeout: Duration) -> Result { + let r = T::regs(); + let deadline = Instant::now() + timeout; + loop { + // stop if an error occurred + if r.events_error.read().bits() != 0 { + r.events_error.reset(); + r.tasks_stop.write(|w| unsafe { w.bits(1) }); + let errorsrc = r.errorsrc.read(); + if errorsrc.overread().is_detected() { + return Err(Error::OverRead); + } else if errorsrc.dnack().is_received() { + return Err(Error::DataNack); + } else { + return Err(Error::Bus); + } + } else if r.events_stopped.read().bits() != 0 { + r.events_stopped.reset(); + let n = r.txd.amount.read().bits() as usize; + return Ok(n); + } else if Instant::now() > deadline { + r.tasks_stop.write(|w| unsafe { w.bits(1) }); + return Err(Error::Timeout); + } + } + } + + /// Wait for read, write, stop or error with timeout + #[cfg(feature = "time")] + fn blocking_listen_wait_timeout(&mut self, timeout: Duration) -> Result { + let r = T::regs(); + let deadline = Instant::now() + timeout; + loop { + if r.events_error.read().bits() != 0 { + r.events_error.reset(); + r.tasks_stop.write(|w| unsafe { w.bits(1) }); + while r.events_stopped.read().bits() == 0 {} + return Err(Error::Overflow); + } + if r.events_stopped.read().bits() != 0 { + r.events_stopped.reset(); + return Err(Error::Bus); + } + if r.events_read.read().bits() != 0 { + r.events_read.reset(); + return Ok(Status::Read); + } + if r.events_write.read().bits() != 0 { + r.events_write.reset(); + return Ok(Status::Write); + } + if Instant::now() > deadline { + r.tasks_stop.write(|w| unsafe { w.bits(1) }); + return Err(Error::Timeout); + } + } + } + + /// Wait for stop, repeated start or error with timeout + #[cfg(feature = "time")] + fn blocking_listen_wait_end_timeout(&mut self, status: Status, timeout: Duration) -> Result { + let r = T::regs(); + let deadline = Instant::now() + timeout; + loop { + // stop if an error occurred + if r.events_error.read().bits() != 0 { + r.events_error.reset(); + r.tasks_stop.write(|w| unsafe { w.bits(1) }); + return Err(Error::Overflow); + } else if r.events_stopped.read().bits() != 0 { + r.events_stopped.reset(); + return match status { + Status::Read => Ok(Command::Read), + Status::Write => { + let n = r.rxd.amount.read().bits() as usize; + Ok(Command::Write(n)) + } + }; + } else if r.events_read.read().bits() != 0 { + r.events_read.reset(); + let n = r.rxd.amount.read().bits() as usize; + return Ok(Command::WriteRead(n)); + } else if Instant::now() > deadline { + r.tasks_stop.write(|w| unsafe { w.bits(1) }); + return Err(Error::Timeout); + } + } + } + + /// Wait for stop or error + fn async_wait(&mut self) -> impl Future> { + poll_fn(move |cx| { + let r = T::regs(); + let s = T::state(); + + s.waker.register(cx.waker()); + + // stop if an error occurred + if r.events_error.read().bits() != 0 { + r.events_error.reset(); + r.tasks_stop.write(|w| unsafe { w.bits(1) }); + let errorsrc = r.errorsrc.read(); + if errorsrc.overread().is_detected() { + return Poll::Ready(Err(Error::OverRead)); + } else if errorsrc.dnack().is_received() { + return Poll::Ready(Err(Error::DataNack)); + } else { + return Poll::Ready(Err(Error::Bus)); + } + } else if r.events_stopped.read().bits() != 0 { + r.events_stopped.reset(); + let n = r.txd.amount.read().bits() as usize; + return Poll::Ready(Ok(n)); + } + + Poll::Pending + }) + } + + /// Wait for read or write + fn async_listen_wait(&mut self) -> impl Future> { + poll_fn(move |cx| { + let r = T::regs(); + let s = T::state(); + + s.waker.register(cx.waker()); + + // stop if an error occurred + if r.events_error.read().bits() != 0 { + r.events_error.reset(); + r.tasks_stop.write(|w| unsafe { w.bits(1) }); + return Poll::Ready(Err(Error::Overflow)); + } else if r.events_read.read().bits() != 0 { + r.events_read.reset(); + return Poll::Ready(Ok(Status::Read)); + } else if r.events_write.read().bits() != 0 { + r.events_write.reset(); + return Poll::Ready(Ok(Status::Write)); + } else if r.events_stopped.read().bits() != 0 { + r.events_stopped.reset(); + return Poll::Ready(Err(Error::Bus)); + } + Poll::Pending + }) + } + + /// Wait for stop, repeated start or error + fn async_listen_wait_end(&mut self, status: Status) -> impl Future> { + poll_fn(move |cx| { + let r = T::regs(); + let s = T::state(); + + s.waker.register(cx.waker()); + + // stop if an error occurred + if r.events_error.read().bits() != 0 { + r.events_error.reset(); + r.tasks_stop.write(|w| unsafe { w.bits(1) }); + return Poll::Ready(Err(Error::Overflow)); + } else if r.events_stopped.read().bits() != 0 { + r.events_stopped.reset(); + return match status { + Status::Read => Poll::Ready(Ok(Command::Read)), + Status::Write => { + let n = r.rxd.amount.read().bits() as usize; + Poll::Ready(Ok(Command::Write(n))) + } + }; + } else if r.events_read.read().bits() != 0 { + r.events_read.reset(); + let n = r.rxd.amount.read().bits() as usize; + return Poll::Ready(Ok(Command::WriteRead(n))); + } + Poll::Pending + }) + } + + fn setup_respond_from_ram(&mut self, buffer: &[u8], inten: bool) -> Result<(), Error> { + let r = T::regs(); + + compiler_fence(SeqCst); + + // Set up the DMA write. + unsafe { self.set_tx_buffer(buffer)? }; + + // Clear events + r.events_stopped.reset(); + r.events_error.reset(); + self.clear_errorsrc(); + + if inten { + r.intenset.write(|w| w.stopped().set().error().set()); + } else { + r.intenclr.write(|w| w.stopped().clear().error().clear()); + } + + // Start write operation. + r.tasks_preparetx.write(|w| unsafe { w.bits(1) }); + r.tasks_resume.write(|w| unsafe { w.bits(1) }); + Ok(()) + } + + fn setup_respond(&mut self, wr_buffer: &[u8], inten: bool) -> Result<(), Error> { + match self.setup_respond_from_ram(wr_buffer, inten) { + Ok(_) => Ok(()), + Err(Error::BufferNotInRAM) => { + trace!("Copying TWIS tx buffer into RAM for DMA"); + let tx_ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..wr_buffer.len()]; + tx_ram_buf.copy_from_slice(wr_buffer); + self.setup_respond_from_ram(&tx_ram_buf, inten) + } + Err(error) => Err(error), + } + } + + fn setup_listen(&mut self, buffer: &mut [u8], inten: bool) -> Result<(), Error> { + let r = T::regs(); + compiler_fence(SeqCst); + + // Set up the DMA read. + unsafe { self.set_rx_buffer(buffer)? }; + + // Clear events + r.events_read.reset(); + r.events_write.reset(); + r.events_stopped.reset(); + r.events_error.reset(); + self.clear_errorsrc(); + + if inten { + r.intenset + .write(|w| w.stopped().set().error().set().read().set().write().set()); + } else { + r.intenclr + .write(|w| w.stopped().clear().error().clear().read().clear().write().clear()); + } + + // Start read operation. + r.tasks_preparerx.write(|w| unsafe { w.bits(1) }); + + Ok(()) + } + + fn setup_listen_end(&mut self, inten: bool) -> Result<(), Error> { + let r = T::regs(); + compiler_fence(SeqCst); + + // Clear events + r.events_read.reset(); + r.events_write.reset(); + r.events_stopped.reset(); + r.events_error.reset(); + self.clear_errorsrc(); + + if inten { + r.intenset.write(|w| w.stopped().set().error().set().read().set()); + } else { + r.intenclr.write(|w| w.stopped().clear().error().clear().read().clear()); + } + + Ok(()) + } + + /// Wait for commands from an I2C master. + /// `buffer` is provided in case master does a 'write' and is unused for 'read'. + /// The buffer must have a length of at most 255 bytes on the nRF52832 + /// and at most 65535 bytes on the nRF52840. + /// To know which one of the addresses were matched, call `address_match` or `address_match_index` + pub fn blocking_listen(&mut self, buffer: &mut [u8]) -> Result { + self.setup_listen(buffer, false)?; + let status = self.blocking_listen_wait()?; + if status == Status::Write { + self.setup_listen_end(false)?; + let command = self.blocking_listen_wait_end(status)?; + return Ok(command); + } + Ok(Command::Read) + } + + /// Respond to an I2C master READ command. + /// Returns the number of bytes written. + /// The buffer must have a length of at most 255 bytes on the nRF52832 + /// and at most 65535 bytes on the nRF52840. + pub fn blocking_respond_to_read(&mut self, buffer: &[u8]) -> Result { + self.setup_respond(buffer, false)?; + self.blocking_wait() + } + + /// Same as [`blocking_respond_to_read`](Twis::blocking_respond_to_read) but will fail instead of copying data into RAM. + /// Consult the module level documentation to learn more. + pub fn blocking_respond_to_read_from_ram(&mut self, buffer: &[u8]) -> Result { + self.setup_respond_from_ram(buffer, false)?; + self.blocking_wait() + } + + // =========================================== + + /// Wait for commands from an I2C master, with timeout. + /// `buffer` is provided in case master does a 'write' and is unused for 'read'. + /// The buffer must have a length of at most 255 bytes on the nRF52832 + /// and at most 65535 bytes on the nRF52840. + /// To know which one of the addresses were matched, call `address_match` or `address_match_index` + #[cfg(feature = "time")] + pub fn blocking_listen_timeout(&mut self, buffer: &mut [u8], timeout: Duration) -> Result { + self.setup_listen(buffer, false)?; + let status = self.blocking_listen_wait_timeout(timeout)?; + if status == Status::Write { + self.setup_listen_end(false)?; + let command = self.blocking_listen_wait_end_timeout(status, timeout)?; + return Ok(command); + } + Ok(Command::Read) + } + + /// Respond to an I2C master READ command with timeout. + /// Returns the number of bytes written. + /// See [`blocking_respond_to_read`]. + #[cfg(feature = "time")] + pub fn blocking_respond_to_read_timeout(&mut self, buffer: &[u8], timeout: Duration) -> Result { + self.setup_respond(buffer, false)?; + self.blocking_wait_timeout(timeout) + } + + /// Same as [`blocking_respond_to_read_timeout`](Twis::blocking_respond_to_read_timeout) but will fail instead of copying data into RAM. + /// Consult the module level documentation to learn more. + #[cfg(feature = "time")] + pub fn blocking_respond_to_read_from_ram_timeout( + &mut self, + buffer: &[u8], + timeout: Duration, + ) -> Result { + self.setup_respond_from_ram(buffer, false)?; + self.blocking_wait_timeout(timeout) + } + + // =========================================== + + /// Wait asynchronously for commands from an I2C master. + /// `buffer` is provided in case master does a 'write' and is unused for 'read'. + /// The buffer must have a length of at most 255 bytes on the nRF52832 + /// and at most 65535 bytes on the nRF52840. + /// To know which one of the addresses were matched, call `address_match` or `address_match_index` + pub async fn listen(&mut self, buffer: &mut [u8]) -> Result { + self.setup_listen(buffer, true)?; + let status = self.async_listen_wait().await?; + if status == Status::Write { + self.setup_listen_end(true)?; + let command = self.async_listen_wait_end(status).await?; + return Ok(command); + } + Ok(Command::Read) + } + + /// Respond to an I2C master READ command, asynchronously. + /// Returns the number of bytes written. + /// The buffer must have a length of at most 255 bytes on the nRF52832 + /// and at most 65535 bytes on the nRF52840. + pub async fn respond_to_read(&mut self, buffer: &[u8]) -> Result { + self.setup_respond(buffer, true)?; + self.async_wait().await + } + + /// Same as [`respond_to_read`](Twis::respond_to_read) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. + pub async fn respond_to_read_from_ram(&mut self, buffer: &[u8]) -> Result { + self.setup_respond_from_ram(buffer, true)?; + self.async_wait().await + } +} + +impl<'a, T: Instance> Drop for Twis<'a, T> { + fn drop(&mut self) { + trace!("twis drop"); + + // TODO: check for abort + + // disable! + let r = T::regs(); + r.enable.write(|w| w.enable().disabled()); + + gpio::deconfigure_pin(r.psel.sda.read().bits()); + gpio::deconfigure_pin(r.psel.scl.read().bits()); + + trace!("twis drop: done"); + } +} + +pub(crate) mod sealed { + use super::*; + + pub struct State { + pub waker: AtomicWaker, + } + + impl State { + pub const fn new() -> Self { + Self { + waker: AtomicWaker::new(), + } + } + } + + pub trait Instance { + fn regs() -> &'static pac::twis0::RegisterBlock; + fn state() -> &'static State; + } +} + +/// TWIS peripheral instance. +pub trait Instance: Peripheral

+ sealed::Instance + 'static { + /// Interrupt for this peripheral. + type Interrupt: interrupt::typelevel::Interrupt; +} + +macro_rules! impl_twis { + ($type:ident, $pac_type:ident, $irq:ident) => { + impl crate::twis::sealed::Instance for peripherals::$type { + fn regs() -> &'static pac::twis0::RegisterBlock { + unsafe { &*pac::$pac_type::ptr() } + } + fn state() -> &'static crate::twis::sealed::State { + static STATE: crate::twis::sealed::State = crate::twis::sealed::State::new(); + &STATE + } + } + impl crate::twis::Instance for peripherals::$type { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +} diff --git a/embassy-nrf/src/uarte.rs b/embassy-nrf/src/uarte.rs index 4347ea558..48d57fea4 100644 --- a/embassy-nrf/src/uarte.rs +++ b/embassy-nrf/src/uarte.rs @@ -1,8 +1,6 @@ -#![macro_use] - -//! Async UART +//! Universal Asynchronous Receiver Transmitter (UART) driver. //! -//! Async UART is provided in two flavors - this one and also [crate::buffered_uarte::BufferedUarte]. +//! The UART driver is provided in two flavors - this one and also [crate::buffered_uarte::BufferedUarte]. //! The [Uarte] here is useful for those use-cases where reading the UARTE peripheral is //! exclusively awaited on. If the [Uarte] is required to be awaited on with some other future, //! for example when using `futures_util::future::select`, then you should consider @@ -13,12 +11,15 @@ //! memory may be used given that buffers are passed in directly to its read and write //! methods. +#![macro_use] + +use core::future::poll_fn; +use core::marker::PhantomData; use core::sync::atomic::{compiler_fence, Ordering}; use core::task::Poll; use embassy_hal_common::drop::OnDrop; use embassy_hal_common::{into_ref, PeripheralRef}; -use futures::future::poll_fn; use pac::uarte0::RegisterBlock; // Re-export SVD variants to allow user to directly set values. pub use pac::uarte0::{baudrate::BAUDRATE_A as Baudrate, config::PARITY_A as Parity}; @@ -26,16 +27,19 @@ pub use pac::uarte0::{baudrate::BAUDRATE_A as Baudrate, config::PARITY_A as Pari use crate::chip::{EASY_DMA_SIZE, FORCE_COPY_BUFFER_SIZE}; use crate::gpio::sealed::Pin as _; use crate::gpio::{self, AnyPin, Pin as GpioPin, PselBits}; -use crate::interrupt::{Interrupt, InterruptExt}; +use crate::interrupt::typelevel::Interrupt; use crate::ppi::{AnyConfigurableChannel, ConfigurableChannel, Event, Ppi, Task}; use crate::timer::{Frequency, Instance as TimerInstance, Timer}; use crate::util::slice_in_ram_or; -use crate::{pac, Peripheral}; +use crate::{interrupt, pac, Peripheral}; +/// UARTE config. #[derive(Clone)] #[non_exhaustive] pub struct Config { + /// Parity bit. pub parity: Parity, + /// Baud rate. pub baudrate: Baudrate, } @@ -48,32 +52,54 @@ impl Default for Config { } } +/// UART error. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum Error { + /// Buffer was too long. BufferTooLong, - BufferZeroLength, - DMABufferNotInDataMemory, - // TODO: add other error variants. + /// The buffer is not in data RAM. It's most likely in flash, and nRF's DMA cannot access flash. + BufferNotInRAM, } -/// Interface to the UARTE peripheral using EasyDMA to offload the transmission and reception workload. -/// -/// For more details about EasyDMA, consult the module documentation. +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let r = T::regs(); + let s = T::state(); + + if r.events_endrx.read().bits() != 0 { + s.endrx_waker.wake(); + r.intenclr.write(|w| w.endrx().clear()); + } + if r.events_endtx.read().bits() != 0 { + s.endtx_waker.wake(); + r.intenclr.write(|w| w.endtx().clear()); + } + } +} + +/// UARTE driver. pub struct Uarte<'d, T: Instance> { tx: UarteTx<'d, T>, rx: UarteRx<'d, T>, } -/// Transmitter interface to the UARTE peripheral obtained -/// via [Uarte]::split. +/// Transmitter part of the UARTE driver. +/// +/// This can be obtained via [`Uarte::split`], or created directly. pub struct UarteTx<'d, T: Instance> { _p: PeripheralRef<'d, T>, } -/// Receiver interface to the UARTE peripheral obtained -/// via [Uarte]::split. +/// Receiver part of the UARTE driver. +/// +/// This can be obtained via [`Uarte::split`], or created directly. pub struct UarteRx<'d, T: Instance> { _p: PeripheralRef<'d, T>, } @@ -82,19 +108,19 @@ impl<'d, T: Instance> Uarte<'d, T> { /// Create a new UARTE without hardware flow control pub fn new( uarte: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, rxd: impl Peripheral

+ 'd, txd: impl Peripheral

+ 'd, config: Config, ) -> Self { into_ref!(rxd, txd); - Self::new_inner(uarte, irq, rxd.map_into(), txd.map_into(), None, None, config) + Self::new_inner(uarte, rxd.map_into(), txd.map_into(), None, None, config) } /// Create a new UARTE with hardware flow control (RTS/CTS) pub fn new_with_rtscts( uarte: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, rxd: impl Peripheral

+ 'd, txd: impl Peripheral

+ 'd, cts: impl Peripheral

+ 'd, @@ -104,7 +130,6 @@ impl<'d, T: Instance> Uarte<'d, T> { into_ref!(rxd, txd, cts, rts); Self::new_inner( uarte, - irq, rxd.map_into(), txd.map_into(), Some(cts.map_into()), @@ -115,14 +140,13 @@ impl<'d, T: Instance> Uarte<'d, T> { fn new_inner( uarte: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, rxd: PeripheralRef<'d, AnyPin>, txd: PeripheralRef<'d, AnyPin>, cts: Option>, rts: Option>, config: Config, ) -> Self { - into_ref!(uarte, irq); + into_ref!(uarte); let r = T::regs(); @@ -144,9 +168,8 @@ impl<'d, T: Instance> Uarte<'d, T> { } r.psel.rts.write(|w| unsafe { w.bits(rts.psel_bits()) }); - irq.set_handler(Self::on_interrupt); - irq.unpend(); - irq.enable(); + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; let hardware_flow_control = match (rts.is_some(), cts.is_some()) { (false, false) => false, @@ -166,37 +189,37 @@ impl<'d, T: Instance> Uarte<'d, T> { } } - /// Split the Uarte into a transmitter and receiver, which is - /// particuarly useful when having two tasks correlating to - /// transmitting and receiving. + /// Split the Uarte into the transmitter and receiver parts. + /// + /// This is useful to concurrently transmit and receive from independent tasks. pub fn split(self) -> (UarteTx<'d, T>, UarteRx<'d, T>) { (self.tx, self.rx) } + /// Split the Uarte into the transmitter and receiver with idle support parts. + /// + /// This is useful to concurrently transmit and receive from independent tasks. + pub fn split_with_idle( + self, + timer: impl Peripheral

+ 'd, + ppi_ch1: impl Peripheral

+ 'd, + ppi_ch2: impl Peripheral

+ 'd, + ) -> (UarteTx<'d, T>, UarteRxWithIdle<'d, T, U>) { + (self.tx, self.rx.with_idle(timer, ppi_ch1, ppi_ch2)) + } + /// Return the endtx event for use with PPI pub fn event_endtx(&self) -> Event { let r = T::regs(); Event::from_reg(&r.events_endtx) } - fn on_interrupt(_: *mut ()) { - let r = T::regs(); - let s = T::state(); - - if r.events_endrx.read().bits() != 0 { - s.endrx_waker.wake(); - r.intenclr.write(|w| w.endrx().clear()); - } - if r.events_endtx.read().bits() != 0 { - s.endtx_waker.wake(); - r.intenclr.write(|w| w.endtx().clear()); - } - } - + /// Read bytes until the buffer is filled. pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { self.rx.read(buffer).await } + /// Write all bytes in the buffer. pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { self.tx.write(buffer).await } @@ -206,10 +229,12 @@ impl<'d, T: Instance> Uarte<'d, T> { self.tx.write_from_ram(buffer).await } + /// Read bytes until the buffer is filled. pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { self.rx.blocking_read(buffer) } + /// Write all bytes in the buffer. pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { self.tx.blocking_write(buffer) } @@ -245,34 +270,33 @@ impl<'d, T: Instance> UarteTx<'d, T> { /// Create a new tx-only UARTE without hardware flow control pub fn new( uarte: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, txd: impl Peripheral

+ 'd, config: Config, ) -> Self { into_ref!(txd); - Self::new_inner(uarte, irq, txd.map_into(), None, config) + Self::new_inner(uarte, txd.map_into(), None, config) } /// Create a new tx-only UARTE with hardware flow control (RTS/CTS) pub fn new_with_rtscts( uarte: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, txd: impl Peripheral

+ 'd, cts: impl Peripheral

+ 'd, config: Config, ) -> Self { into_ref!(txd, cts); - Self::new_inner(uarte, irq, txd.map_into(), Some(cts.map_into()), config) + Self::new_inner(uarte, txd.map_into(), Some(cts.map_into()), config) } fn new_inner( uarte: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, txd: PeripheralRef<'d, AnyPin>, cts: Option>, config: Config, ) -> Self { - into_ref!(uarte, irq); + into_ref!(uarte); let r = T::regs(); @@ -291,9 +315,8 @@ impl<'d, T: Instance> UarteTx<'d, T> { let hardware_flow_control = cts.is_some(); configure(r, config, hardware_flow_control); - irq.set_handler(Uarte::::on_interrupt); - irq.unpend(); - irq.enable(); + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; let s = T::state(); s.tx_rx_refcount.store(1, Ordering::Relaxed); @@ -301,10 +324,11 @@ impl<'d, T: Instance> UarteTx<'d, T> { Self { _p: uarte } } + /// Write all bytes in the buffer. pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { match self.write_from_ram(buffer).await { Ok(_) => Ok(()), - Err(Error::DMABufferNotInDataMemory) => { + Err(Error::BufferNotInRAM) => { trace!("Copying UARTE tx buffer into RAM for DMA"); let ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..buffer.len()]; ram_buf.copy_from_slice(buffer); @@ -314,11 +338,13 @@ impl<'d, T: Instance> UarteTx<'d, T> { } } + /// Same as [`write`](Self::write) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. pub async fn write_from_ram(&mut self, buffer: &[u8]) -> Result<(), Error> { - slice_in_ram_or(buffer, Error::DMABufferNotInDataMemory)?; if buffer.len() == 0 { - return Err(Error::BufferZeroLength); + return Ok(()); } + + slice_in_ram_or(buffer, Error::BufferNotInRAM)?; if buffer.len() > EASY_DMA_SIZE { return Err(Error::BufferTooLong); } @@ -368,10 +394,11 @@ impl<'d, T: Instance> UarteTx<'d, T> { Ok(()) } + /// Write all bytes in the buffer. pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { match self.blocking_write_from_ram(buffer) { Ok(_) => Ok(()), - Err(Error::DMABufferNotInDataMemory) => { + Err(Error::BufferNotInRAM) => { trace!("Copying UARTE tx buffer into RAM for DMA"); let ram_buf = &mut [0; FORCE_COPY_BUFFER_SIZE][..buffer.len()]; ram_buf.copy_from_slice(buffer); @@ -381,11 +408,13 @@ impl<'d, T: Instance> UarteTx<'d, T> { } } + /// Same as [`write_from_ram`](Self::write_from_ram) but will fail instead of copying data into RAM. Consult the module level documentation to learn more. pub fn blocking_write_from_ram(&mut self, buffer: &[u8]) -> Result<(), Error> { - slice_in_ram_or(buffer, Error::DMABufferNotInDataMemory)?; if buffer.len() == 0 { - return Err(Error::BufferZeroLength); + return Ok(()); } + + slice_in_ram_or(buffer, Error::BufferNotInRAM)?; if buffer.len() > EASY_DMA_SIZE { return Err(Error::BufferTooLong); } @@ -437,34 +466,33 @@ impl<'d, T: Instance> UarteRx<'d, T> { /// Create a new rx-only UARTE without hardware flow control pub fn new( uarte: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, rxd: impl Peripheral

+ 'd, config: Config, ) -> Self { into_ref!(rxd); - Self::new_inner(uarte, irq, rxd.map_into(), None, config) + Self::new_inner(uarte, rxd.map_into(), None, config) } /// Create a new rx-only UARTE with hardware flow control (RTS/CTS) pub fn new_with_rtscts( uarte: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, rxd: impl Peripheral

+ 'd, rts: impl Peripheral

+ 'd, config: Config, ) -> Self { into_ref!(rxd, rts); - Self::new_inner(uarte, irq, rxd.map_into(), Some(rts.map_into()), config) + Self::new_inner(uarte, rxd.map_into(), Some(rts.map_into()), config) } fn new_inner( uarte: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, rxd: PeripheralRef<'d, AnyPin>, rts: Option>, config: Config, ) -> Self { - into_ref!(uarte, irq); + into_ref!(uarte); let r = T::regs(); @@ -480,9 +508,8 @@ impl<'d, T: Instance> UarteRx<'d, T> { r.psel.txd.write(|w| w.connect().disconnected()); r.psel.cts.write(|w| w.connect().disconnected()); - irq.set_handler(Uarte::::on_interrupt); - irq.unpend(); - irq.enable(); + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; let hardware_flow_control = rts.is_some(); configure(r, config, hardware_flow_control); @@ -493,9 +520,60 @@ impl<'d, T: Instance> UarteRx<'d, T> { Self { _p: uarte } } + /// Upgrade to an instance that supports idle line detection. + pub fn with_idle( + self, + timer: impl Peripheral

+ 'd, + ppi_ch1: impl Peripheral

+ 'd, + ppi_ch2: impl Peripheral

+ 'd, + ) -> UarteRxWithIdle<'d, T, U> { + let timer = Timer::new(timer); + + into_ref!(ppi_ch1, ppi_ch2); + + let r = T::regs(); + + // BAUDRATE register values are `baudrate * 2^32 / 16000000` + // source: https://devzone.nordicsemi.com/f/nordic-q-a/391/uart-baudrate-register-values + // + // We want to stop RX if line is idle for 2 bytes worth of time + // That is 20 bits (each byte is 1 start bit + 8 data bits + 1 stop bit) + // This gives us the amount of 16M ticks for 20 bits. + let baudrate = r.baudrate.read().baudrate().variant().unwrap(); + let timeout = 0x8000_0000 / (baudrate as u32 / 40); + + timer.set_frequency(Frequency::F16MHz); + timer.cc(0).write(timeout); + timer.cc(0).short_compare_clear(); + timer.cc(0).short_compare_stop(); + + let mut ppi_ch1 = Ppi::new_one_to_two( + ppi_ch1.map_into(), + Event::from_reg(&r.events_rxdrdy), + timer.task_clear(), + timer.task_start(), + ); + ppi_ch1.enable(); + + let mut ppi_ch2 = Ppi::new_one_to_one( + ppi_ch2.map_into(), + timer.cc(0).event_compare(), + Task::from_reg(&r.tasks_stoprx), + ); + ppi_ch2.enable(); + + UarteRxWithIdle { + rx: self, + timer, + ppi_ch1: ppi_ch1, + _ppi_ch2: ppi_ch2, + } + } + + /// Read bytes until the buffer is filled. pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { if buffer.len() == 0 { - return Err(Error::BufferZeroLength); + return Ok(()); } if buffer.len() > EASY_DMA_SIZE { return Err(Error::BufferTooLong); @@ -546,9 +624,10 @@ impl<'d, T: Instance> UarteRx<'d, T> { Ok(()) } + /// Read bytes until the buffer is filled. pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { if buffer.len() == 0 { - return Err(Error::BufferZeroLength); + return Ok(()); } if buffer.len() > EASY_DMA_SIZE { return Err(Error::BufferTooLong); @@ -597,20 +676,140 @@ impl<'a, T: Instance> Drop for UarteRx<'a, T> { } } -#[cfg(not(any(feature = "_nrf9160", feature = "nrf5340")))] +/// Receiver part of the UARTE driver, with `read_until_idle` support. +/// +/// This can be obtained via [`Uarte::split_with_idle`]. +pub struct UarteRxWithIdle<'d, T: Instance, U: TimerInstance> { + rx: UarteRx<'d, T>, + timer: Timer<'d, U>, + ppi_ch1: Ppi<'d, AnyConfigurableChannel, 1, 2>, + _ppi_ch2: Ppi<'d, AnyConfigurableChannel, 1, 1>, +} + +impl<'d, T: Instance, U: TimerInstance> UarteRxWithIdle<'d, T, U> { + /// Read bytes until the buffer is filled. + pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + self.ppi_ch1.disable(); + self.rx.read(buffer).await + } + + /// Read bytes until the buffer is filled. + pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + self.ppi_ch1.disable(); + self.rx.blocking_read(buffer) + } + + /// Read bytes until the buffer is filled, or the line becomes idle. + /// + /// Returns the amount of bytes read. + pub async fn read_until_idle(&mut self, buffer: &mut [u8]) -> Result { + if buffer.len() == 0 { + return Ok(0); + } + if buffer.len() > EASY_DMA_SIZE { + return Err(Error::BufferTooLong); + } + + let ptr = buffer.as_ptr(); + let len = buffer.len(); + + let r = T::regs(); + let s = T::state(); + + self.ppi_ch1.enable(); + + let drop = OnDrop::new(|| { + self.timer.stop(); + + r.intenclr.write(|w| w.endrx().clear()); + r.events_rxto.reset(); + r.tasks_stoprx.write(|w| unsafe { w.bits(1) }); + + while r.events_endrx.read().bits() == 0 {} + }); + + r.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) }); + r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(len as _) }); + + r.events_endrx.reset(); + r.intenset.write(|w| w.endrx().set()); + + compiler_fence(Ordering::SeqCst); + + r.tasks_startrx.write(|w| unsafe { w.bits(1) }); + + poll_fn(|cx| { + s.endrx_waker.register(cx.waker()); + if r.events_endrx.read().bits() != 0 { + return Poll::Ready(()); + } + Poll::Pending + }) + .await; + + compiler_fence(Ordering::SeqCst); + let n = r.rxd.amount.read().amount().bits() as usize; + + self.timer.stop(); + r.events_rxstarted.reset(); + + drop.defuse(); + + Ok(n) + } + + /// Read bytes until the buffer is filled, or the line becomes idle. + /// + /// Returns the amount of bytes read. + pub fn blocking_read_until_idle(&mut self, buffer: &mut [u8]) -> Result { + if buffer.len() == 0 { + return Ok(0); + } + if buffer.len() > EASY_DMA_SIZE { + return Err(Error::BufferTooLong); + } + + let ptr = buffer.as_ptr(); + let len = buffer.len(); + + let r = T::regs(); + + self.ppi_ch1.enable(); + + r.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) }); + r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(len as _) }); + + r.events_endrx.reset(); + r.intenclr.write(|w| w.endrx().clear()); + + compiler_fence(Ordering::SeqCst); + + r.tasks_startrx.write(|w| unsafe { w.bits(1) }); + + while r.events_endrx.read().bits() == 0 {} + + compiler_fence(Ordering::SeqCst); + let n = r.rxd.amount.read().amount().bits() as usize; + + self.timer.stop(); + r.events_rxstarted.reset(); + + Ok(n) + } +} + +#[cfg(not(any(feature = "_nrf9160", feature = "_nrf5340")))] pub(crate) fn apply_workaround_for_enable_anomaly(_r: &crate::pac::uarte0::RegisterBlock) { // Do nothing } -#[cfg(any(feature = "_nrf9160", feature = "nrf5340"))] +#[cfg(any(feature = "_nrf9160", feature = "_nrf5340"))] pub(crate) fn apply_workaround_for_enable_anomaly(r: &crate::pac::uarte0::RegisterBlock) { - use core::ops::Deref; - // Apply workaround for anomalies: // - nRF9160 - anomaly 23 // - nRF5340 - anomaly 44 - let rxenable_reg: *const u32 = ((r.deref() as *const _ as usize) + 0x564) as *const u32; - let txenable_reg: *const u32 = ((r.deref() as *const _ as usize) + 0x568) as *const u32; + let rxenable_reg: *const u32 = ((r as *const _ as usize) + 0x564) as *const u32; + let txenable_reg: *const u32 = ((r as *const _ as usize) + 0x568) as *const u32; // NB Safety: This is taken from Nordic's driver - // https://github.com/NordicSemiconductor/nrfx/blob/master/drivers/src/nrfx_uarte.c#L197 @@ -665,270 +864,6 @@ pub(crate) fn drop_tx_rx(r: &pac::uarte0::RegisterBlock, s: &sealed::State) { } } -/// Interface to an UARTE peripheral that uses an additional timer and two PPI channels, -/// allowing it to implement the ReadUntilIdle trait. -pub struct UarteWithIdle<'d, U: Instance, T: TimerInstance> { - tx: UarteTx<'d, U>, - rx: UarteRxWithIdle<'d, U, T>, -} - -impl<'d, U: Instance, T: TimerInstance> UarteWithIdle<'d, U, T> { - /// Create a new UARTE without hardware flow control - pub fn new( - uarte: impl Peripheral

+ 'd, - timer: impl Peripheral

+ 'd, - ppi_ch1: impl Peripheral

+ 'd, - ppi_ch2: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, - rxd: impl Peripheral

+ 'd, - txd: impl Peripheral

+ 'd, - config: Config, - ) -> Self { - into_ref!(rxd, txd); - Self::new_inner( - uarte, - timer, - ppi_ch1, - ppi_ch2, - irq, - rxd.map_into(), - txd.map_into(), - None, - None, - config, - ) - } - - /// Create a new UARTE with hardware flow control (RTS/CTS) - pub fn new_with_rtscts( - uarte: impl Peripheral

+ 'd, - timer: impl Peripheral

+ 'd, - ppi_ch1: impl Peripheral

+ 'd, - ppi_ch2: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, - rxd: impl Peripheral

+ 'd, - txd: impl Peripheral

+ 'd, - cts: impl Peripheral

+ 'd, - rts: impl Peripheral

+ 'd, - config: Config, - ) -> Self { - into_ref!(rxd, txd, cts, rts); - Self::new_inner( - uarte, - timer, - ppi_ch1, - ppi_ch2, - irq, - rxd.map_into(), - txd.map_into(), - Some(cts.map_into()), - Some(rts.map_into()), - config, - ) - } - - fn new_inner( - uarte: impl Peripheral

+ 'd, - timer: impl Peripheral

+ 'd, - ppi_ch1: impl Peripheral

+ 'd, - ppi_ch2: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, - rxd: PeripheralRef<'d, AnyPin>, - txd: PeripheralRef<'d, AnyPin>, - cts: Option>, - rts: Option>, - config: Config, - ) -> Self { - let baudrate = config.baudrate; - let (tx, rx) = Uarte::new_inner(uarte, irq, rxd, txd, cts, rts, config).split(); - - let mut timer = Timer::new(timer); - - into_ref!(ppi_ch1, ppi_ch2); - - let r = U::regs(); - - // BAUDRATE register values are `baudrate * 2^32 / 16000000` - // source: https://devzone.nordicsemi.com/f/nordic-q-a/391/uart-baudrate-register-values - // - // We want to stop RX if line is idle for 2 bytes worth of time - // That is 20 bits (each byte is 1 start bit + 8 data bits + 1 stop bit) - // This gives us the amount of 16M ticks for 20 bits. - let timeout = 0x8000_0000 / (baudrate as u32 / 40); - - timer.set_frequency(Frequency::F16MHz); - timer.cc(0).write(timeout); - timer.cc(0).short_compare_clear(); - timer.cc(0).short_compare_stop(); - - let mut ppi_ch1 = Ppi::new_one_to_two( - ppi_ch1.map_into(), - Event::from_reg(&r.events_rxdrdy), - timer.task_clear(), - timer.task_start(), - ); - ppi_ch1.enable(); - - let mut ppi_ch2 = Ppi::new_one_to_one( - ppi_ch2.map_into(), - timer.cc(0).event_compare(), - Task::from_reg(&r.tasks_stoprx), - ); - ppi_ch2.enable(); - - Self { - tx, - rx: UarteRxWithIdle { - rx, - timer, - ppi_ch1: ppi_ch1, - _ppi_ch2: ppi_ch2, - }, - } - } - - /// Split the Uarte into a transmitter and receiver, which is - /// particuarly useful when having two tasks correlating to - /// transmitting and receiving. - pub fn split(self) -> (UarteTx<'d, U>, UarteRxWithIdle<'d, U, T>) { - (self.tx, self.rx) - } - - pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { - self.rx.read(buffer).await - } - - pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { - self.tx.write(buffer).await - } - - pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { - self.rx.blocking_read(buffer) - } - - pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { - self.tx.blocking_write(buffer) - } - - pub async fn read_until_idle(&mut self, buffer: &mut [u8]) -> Result { - self.rx.read_until_idle(buffer).await - } - - pub fn blocking_read_until_idle(&mut self, buffer: &mut [u8]) -> Result { - self.rx.blocking_read_until_idle(buffer) - } -} - -pub struct UarteRxWithIdle<'d, U: Instance, T: TimerInstance> { - rx: UarteRx<'d, U>, - timer: Timer<'d, T>, - ppi_ch1: Ppi<'d, AnyConfigurableChannel, 1, 2>, - _ppi_ch2: Ppi<'d, AnyConfigurableChannel, 1, 1>, -} - -impl<'d, U: Instance, T: TimerInstance> UarteRxWithIdle<'d, U, T> { - pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { - self.ppi_ch1.disable(); - self.rx.read(buffer).await - } - - pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { - self.ppi_ch1.disable(); - self.rx.blocking_read(buffer) - } - - pub async fn read_until_idle(&mut self, buffer: &mut [u8]) -> Result { - if buffer.len() == 0 { - return Err(Error::BufferZeroLength); - } - if buffer.len() > EASY_DMA_SIZE { - return Err(Error::BufferTooLong); - } - - let ptr = buffer.as_ptr(); - let len = buffer.len(); - - let r = U::regs(); - let s = U::state(); - - self.ppi_ch1.enable(); - - let drop = OnDrop::new(|| { - self.timer.stop(); - - r.intenclr.write(|w| w.endrx().clear()); - r.events_rxto.reset(); - r.tasks_stoprx.write(|w| unsafe { w.bits(1) }); - - while r.events_endrx.read().bits() == 0 {} - }); - - r.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) }); - r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(len as _) }); - - r.events_endrx.reset(); - r.intenset.write(|w| w.endrx().set()); - - compiler_fence(Ordering::SeqCst); - - r.tasks_startrx.write(|w| unsafe { w.bits(1) }); - - poll_fn(|cx| { - s.endrx_waker.register(cx.waker()); - if r.events_endrx.read().bits() != 0 { - return Poll::Ready(()); - } - Poll::Pending - }) - .await; - - compiler_fence(Ordering::SeqCst); - let n = r.rxd.amount.read().amount().bits() as usize; - - self.timer.stop(); - r.events_rxstarted.reset(); - - drop.defuse(); - - Ok(n) - } - - pub fn blocking_read_until_idle(&mut self, buffer: &mut [u8]) -> Result { - if buffer.len() == 0 { - return Err(Error::BufferZeroLength); - } - if buffer.len() > EASY_DMA_SIZE { - return Err(Error::BufferTooLong); - } - - let ptr = buffer.as_ptr(); - let len = buffer.len(); - - let r = U::regs(); - - self.ppi_ch1.enable(); - - r.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as u32) }); - r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(len as _) }); - - r.events_endrx.reset(); - r.intenclr.write(|w| w.endrx().clear()); - - compiler_fence(Ordering::SeqCst); - - r.tasks_startrx.write(|w| unsafe { w.bits(1) }); - - while r.events_endrx.read().bits() == 0 {} - - compiler_fence(Ordering::SeqCst); - let n = r.rxd.amount.read().amount().bits() as usize; - - self.timer.stop(); - r.events_rxstarted.reset(); - - Ok(n) - } -} pub(crate) mod sealed { use core::sync::atomic::AtomicU8; @@ -954,11 +889,14 @@ pub(crate) mod sealed { pub trait Instance { fn regs() -> &'static pac::uarte0::RegisterBlock; fn state() -> &'static State; + fn buffered_state() -> &'static crate::buffered_uarte::State; } } +/// UARTE peripheral instance. pub trait Instance: Peripheral

+ sealed::Instance + 'static + Send { - type Interrupt: Interrupt; + /// Interrupt for this peripheral. + type Interrupt: interrupt::typelevel::Interrupt; } macro_rules! impl_uarte { @@ -971,9 +909,13 @@ macro_rules! impl_uarte { static STATE: crate::uarte::sealed::State = crate::uarte::sealed::State::new(); &STATE } + fn buffered_state() -> &'static crate::buffered_uarte::State { + static STATE: crate::buffered_uarte::State = crate::buffered_uarte::State::new(); + &STATE + } } impl crate::uarte::Instance for peripherals::$type { - type Interrupt = crate::interrupt::$irq; + type Interrupt = crate::interrupt::typelevel::$irq; } }; } @@ -1006,18 +948,6 @@ mod eh02 { Ok(()) } } - - impl<'d, U: Instance, T: TimerInstance> embedded_hal_02::blocking::serial::Write for UarteWithIdle<'d, U, T> { - type Error = Error; - - fn bwrite_all(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { - self.blocking_write(buffer) - } - - fn bflush(&mut self) -> Result<(), Self::Error> { - Ok(()) - } - } } #[cfg(feature = "unstable-traits")] @@ -1028,8 +958,7 @@ mod eh1 { fn kind(&self) -> embedded_hal_1::serial::ErrorKind { match *self { Self::BufferTooLong => embedded_hal_1::serial::ErrorKind::Other, - Self::BufferZeroLength => embedded_hal_1::serial::ErrorKind::Other, - Self::DMABufferNotInDataMemory => embedded_hal_1::serial::ErrorKind::Other, + Self::BufferNotInRAM => embedded_hal_1::serial::ErrorKind::Other, } } } @@ -1040,7 +969,7 @@ mod eh1 { type Error = Error; } - impl<'d, T: Instance> embedded_hal_1::serial::blocking::Write for Uarte<'d, T> { + impl<'d, T: Instance> embedded_hal_1::serial::Write for Uarte<'d, T> { fn write(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { self.blocking_write(buffer) } @@ -1054,7 +983,7 @@ mod eh1 { type Error = Error; } - impl<'d, T: Instance> embedded_hal_1::serial::blocking::Write for UarteTx<'d, T> { + impl<'d, T: Instance> embedded_hal_1::serial::Write for UarteTx<'d, T> { fn write(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { self.blocking_write(buffer) } @@ -1067,84 +996,4 @@ mod eh1 { impl<'d, T: Instance> embedded_hal_1::serial::ErrorType for UarteRx<'d, T> { type Error = Error; } - - impl<'d, U: Instance, T: TimerInstance> embedded_hal_1::serial::ErrorType for UarteWithIdle<'d, U, T> { - type Error = Error; - } -} - -cfg_if::cfg_if! { - if #[cfg(all(feature = "unstable-traits", feature = "nightly", feature = "_todo_embedded_hal_serial"))] { - use core::future::Future; - - impl<'d, T: Instance> embedded_hal_async::serial::Read for Uarte<'d, T> { - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn read<'a>(&'a mut self, buffer: &'a mut [u8]) -> Self::ReadFuture<'a> { - self.read(buffer) - } - } - - impl<'d, T: Instance> embedded_hal_async::serial::Write for Uarte<'d, T> { - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - - fn write<'a>(&'a mut self, buffer: &'a [u8]) -> Self::WriteFuture<'a> { - self.write(buffer) - } - - type FlushFuture<'a> = impl Future> + 'a where Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - async move { Ok(()) } - } - } - - impl<'d, T: Instance> embedded_hal_async::serial::Write for UarteTx<'d, T> { - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - - fn write<'a>(&'a mut self, buffer: &'a [u8]) -> Self::WriteFuture<'a> { - self.write(buffer) - } - - type FlushFuture<'a> = impl Future> + 'a where Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - async move { Ok(()) } - } - } - - impl<'d, T: Instance> embedded_hal_async::serial::Read for UarteRx<'d, T> { - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn read<'a>(&'a mut self, buffer: &'a mut [u8]) -> Self::ReadFuture<'a> { - self.read(buffer) - } - } - - impl<'d, U: Instance, T: TimerInstance> embedded_hal_async::serial::Read - for UarteWithIdle<'d, U, T> - { - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn read<'a>(&'a mut self, buffer: &'a mut [u8]) -> Self::ReadFuture<'a> { - self.read(buffer) - } - } - - impl<'d, U: Instance, T: TimerInstance> embedded_hal_async::serial::Write - for UarteWithIdle<'d, U, T> - { - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - - fn write<'a>(&'a mut self, buffer: &'a [u8]) -> Self::WriteFuture<'a> { - self.write(buffer) - } - - type FlushFuture<'a> = impl Future> + 'a where Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - async move { Ok(()) } - } - } - } } diff --git a/embassy-nrf/src/usb.rs b/embassy-nrf/src/usb/mod.rs similarity index 55% rename from embassy-nrf/src/usb.rs rename to embassy-nrf/src/usb/mod.rs index 688326e9c..76cf40ac7 100644 --- a/embassy-nrf/src/usb.rs +++ b/embassy-nrf/src/usb/mod.rs @@ -1,23 +1,26 @@ +//! Universal Serial Bus (USB) driver. + #![macro_use] +pub mod vbus_detect; + +use core::future::poll_fn; use core::marker::PhantomData; use core::mem::MaybeUninit; -use core::sync::atomic::{compiler_fence, AtomicBool, AtomicU32, Ordering}; +use core::sync::atomic::{compiler_fence, AtomicU32, Ordering}; use core::task::Poll; use cortex_m::peripheral::NVIC; use embassy_hal_common::{into_ref, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; -pub use embassy_usb; -use embassy_usb::driver::{self, EndpointError, Event, Unsupported}; -use embassy_usb::types::{EndpointAddress, EndpointInfo, EndpointType, UsbDirection}; -use futures::future::poll_fn; -use futures::Future; +use embassy_usb_driver as driver; +use embassy_usb_driver::{Direction, EndpointAddress, EndpointError, EndpointInfo, EndpointType, Event, Unsupported}; use pac::usbd::RegisterBlock; -use crate::interrupt::{Interrupt, InterruptExt}; +use self::vbus_detect::VbusDetect; +use crate::interrupt::typelevel::Interrupt; use crate::util::slice_in_ram; -use crate::{pac, Peripheral}; +use crate::{interrupt, pac, Peripheral}; const NEW_AW: AtomicWaker = AtomicWaker::new(); static BUS_WAKER: AtomicWaker = NEW_AW; @@ -26,161 +29,13 @@ static EP_IN_WAKERS: [AtomicWaker; 8] = [NEW_AW; 8]; static EP_OUT_WAKERS: [AtomicWaker; 8] = [NEW_AW; 8]; static READY_ENDPOINTS: AtomicU32 = AtomicU32::new(0); -/// There are multiple ways to detect USB power. The behavior -/// here provides a hook into determining whether it is. -pub trait UsbSupply { - fn is_usb_detected(&self) -> bool; - - type UsbPowerReadyFuture<'a>: Future> + 'a - where - Self: 'a; - fn wait_power_ready(&mut self) -> Self::UsbPowerReadyFuture<'_>; +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, } -pub struct Driver<'d, T: Instance, P: UsbSupply> { - _p: PeripheralRef<'d, T>, - alloc_in: Allocator, - alloc_out: Allocator, - usb_supply: P, -} - -/// Uses the POWER peripheral to detect when power is available -/// for USB. Unsuitable for usage with the nRF softdevice. -#[cfg(not(feature = "_nrf5340-app"))] -pub struct PowerUsb { - _private: (), -} - -/// Can be used to signal that power is available. Particularly suited for -/// use with the nRF softdevice. -pub struct SignalledSupply { - usb_detected: AtomicBool, - power_ready: AtomicBool, -} - -static POWER_WAKER: AtomicWaker = NEW_AW; - -#[cfg(not(feature = "_nrf5340-app"))] -impl PowerUsb { - pub fn new(power_irq: impl Interrupt) -> Self { - let regs = unsafe { &*pac::POWER::ptr() }; - - power_irq.set_handler(Self::on_interrupt); - power_irq.unpend(); - power_irq.enable(); - - regs.intenset - .write(|w| w.usbdetected().set().usbremoved().set().usbpwrrdy().set()); - - Self { _private: () } - } - - #[cfg(not(feature = "_nrf5340-app"))] - fn on_interrupt(_: *mut ()) { - let regs = unsafe { &*pac::POWER::ptr() }; - - if regs.events_usbdetected.read().bits() != 0 { - regs.events_usbdetected.reset(); - BUS_WAKER.wake(); - } - - if regs.events_usbremoved.read().bits() != 0 { - regs.events_usbremoved.reset(); - BUS_WAKER.wake(); - POWER_WAKER.wake(); - } - - if regs.events_usbpwrrdy.read().bits() != 0 { - regs.events_usbpwrrdy.reset(); - POWER_WAKER.wake(); - } - } -} - -#[cfg(not(feature = "_nrf5340-app"))] -impl UsbSupply for PowerUsb { - fn is_usb_detected(&self) -> bool { - let regs = unsafe { &*pac::POWER::ptr() }; - regs.usbregstatus.read().vbusdetect().is_vbus_present() - } - - type UsbPowerReadyFuture<'a> = impl Future> + 'a where Self: 'a; - fn wait_power_ready(&mut self) -> Self::UsbPowerReadyFuture<'_> { - poll_fn(move |cx| { - POWER_WAKER.register(cx.waker()); - let regs = unsafe { &*pac::POWER::ptr() }; - - if regs.usbregstatus.read().outputrdy().is_ready() { - Poll::Ready(Ok(())) - } else if !self.is_usb_detected() { - Poll::Ready(Err(())) - } else { - Poll::Pending - } - }) - } -} - -impl SignalledSupply { - pub fn new(usb_detected: bool, power_ready: bool) -> Self { - BUS_WAKER.wake(); - - Self { - usb_detected: AtomicBool::new(usb_detected), - power_ready: AtomicBool::new(power_ready), - } - } - - pub fn detected(&self, detected: bool) { - self.usb_detected.store(detected, Ordering::Relaxed); - self.power_ready.store(false, Ordering::Relaxed); - BUS_WAKER.wake(); - POWER_WAKER.wake(); - } - - pub fn ready(&self) { - self.power_ready.store(true, Ordering::Relaxed); - POWER_WAKER.wake(); - } -} - -impl UsbSupply for &SignalledSupply { - fn is_usb_detected(&self) -> bool { - self.usb_detected.load(Ordering::Relaxed) - } - - type UsbPowerReadyFuture<'a> = impl Future> + 'a where Self: 'a; - fn wait_power_ready(&mut self) -> Self::UsbPowerReadyFuture<'_> { - poll_fn(move |cx| { - POWER_WAKER.register(cx.waker()); - - if self.power_ready.load(Ordering::Relaxed) { - Poll::Ready(Ok(())) - } else if !self.usb_detected.load(Ordering::Relaxed) { - Poll::Ready(Err(())) - } else { - Poll::Pending - } - }) - } -} - -impl<'d, T: Instance, P: UsbSupply> Driver<'d, T, P> { - pub fn new(usb: impl Peripheral

+ 'd, irq: impl Peripheral

+ 'd, usb_supply: P) -> Self { - into_ref!(usb, irq); - irq.set_handler(Self::on_interrupt); - irq.unpend(); - irq.enable(); - - Self { - _p: usb, - alloc_in: Allocator::new(), - alloc_out: Allocator::new(), - usb_supply, - } - } - - fn on_interrupt(_: *mut ()) { +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { let regs = T::regs(); if regs.events_usbreset.read().bits() != 0 { @@ -231,25 +86,54 @@ impl<'d, T: Instance, P: UsbSupply> Driver<'d, T, P> { } } -impl<'d, T: Instance, P: UsbSupply + 'd> driver::Driver<'d> for Driver<'d, T, P> { +/// USB driver. +pub struct Driver<'d, T: Instance, V: VbusDetect> { + _p: PeripheralRef<'d, T>, + alloc_in: Allocator, + alloc_out: Allocator, + vbus_detect: V, +} + +impl<'d, T: Instance, V: VbusDetect> Driver<'d, T, V> { + /// Create a new USB driver. + pub fn new( + usb: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + vbus_detect: V, + ) -> Self { + into_ref!(usb); + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + Self { + _p: usb, + alloc_in: Allocator::new(), + alloc_out: Allocator::new(), + vbus_detect, + } + } +} + +impl<'d, T: Instance, V: VbusDetect + 'd> driver::Driver<'d> for Driver<'d, T, V> { type EndpointOut = Endpoint<'d, T, Out>; type EndpointIn = Endpoint<'d, T, In>; type ControlPipe = ControlPipe<'d, T>; - type Bus = Bus<'d, T, P>; + type Bus = Bus<'d, T, V>; fn alloc_endpoint_in( &mut self, ep_type: EndpointType, packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result { let index = self.alloc_in.allocate(ep_type)?; - let ep_addr = EndpointAddress::from_parts(index, UsbDirection::In); + let ep_addr = EndpointAddress::from_parts(index, Direction::In); Ok(Endpoint::new(EndpointInfo { addr: ep_addr, ep_type, max_packet_size: packet_size, - interval, + interval_ms, })) } @@ -257,24 +141,24 @@ impl<'d, T: Instance, P: UsbSupply + 'd> driver::Driver<'d> for Driver<'d, T, P> &mut self, ep_type: EndpointType, packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result { let index = self.alloc_out.allocate(ep_type)?; - let ep_addr = EndpointAddress::from_parts(index, UsbDirection::Out); + let ep_addr = EndpointAddress::from_parts(index, Direction::Out); Ok(Endpoint::new(EndpointInfo { addr: ep_addr, ep_type, max_packet_size: packet_size, - interval, + interval_ms, })) } - fn start(mut self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) { + fn start(self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) { ( Bus { _p: unsafe { self._p.clone_unchecked() }, power_available: false, - usb_supply: self.usb_supply, + vbus_detect: self.vbus_detect, }, ControlPipe { _p: self._p, @@ -284,68 +168,60 @@ impl<'d, T: Instance, P: UsbSupply + 'd> driver::Driver<'d> for Driver<'d, T, P> } } -pub struct Bus<'d, T: Instance, P: UsbSupply> { +/// USB bus. +pub struct Bus<'d, T: Instance, V: VbusDetect> { _p: PeripheralRef<'d, T>, power_available: bool, - usb_supply: P, + vbus_detect: V, } -impl<'d, T: Instance, P: UsbSupply> driver::Bus for Bus<'d, T, P> { - type EnableFuture<'a> = impl Future + 'a where Self: 'a; - type DisableFuture<'a> = impl Future + 'a where Self: 'a; - type PollFuture<'a> = impl Future + 'a where Self: 'a; - type RemoteWakeupFuture<'a> = impl Future> + 'a where Self: 'a; +impl<'d, T: Instance, V: VbusDetect> driver::Bus for Bus<'d, T, V> { + async fn enable(&mut self) { + let regs = T::regs(); - fn enable(&mut self) -> Self::EnableFuture<'_> { - async move { - let regs = T::regs(); + errata::pre_enable(); - errata::pre_enable(); + regs.enable.write(|w| w.enable().enabled()); - regs.enable.write(|w| w.enable().enabled()); - - // Wait until the peripheral is ready. - regs.intenset.write(|w| w.usbevent().set_bit()); - poll_fn(|cx| { - BUS_WAKER.register(cx.waker()); - if regs.eventcause.read().ready().is_ready() { - Poll::Ready(()) - } else { - Poll::Pending - } - }) - .await; - regs.eventcause.write(|w| w.ready().set_bit()); // Write 1 to clear. - - errata::post_enable(); - - unsafe { NVIC::unmask(pac::Interrupt::USBD) }; - - regs.intenset.write(|w| { - w.usbreset().set_bit(); - w.usbevent().set_bit(); - w.epdata().set_bit(); - w - }); - - if self.usb_supply.wait_power_ready().await.is_ok() { - // Enable the USB pullup, allowing enumeration. - regs.usbpullup.write(|w| w.connect().enabled()); - trace!("enabled"); + // Wait until the peripheral is ready. + regs.intenset.write(|w| w.usbevent().set_bit()); + poll_fn(|cx| { + BUS_WAKER.register(cx.waker()); + if regs.eventcause.read().ready().is_ready() { + Poll::Ready(()) } else { - trace!("usb power not ready due to usb removal"); + Poll::Pending } + }) + .await; + regs.eventcause.write(|w| w.ready().clear_bit_by_one()); + + errata::post_enable(); + + unsafe { NVIC::unmask(pac::Interrupt::USBD) }; + + regs.intenset.write(|w| { + w.usbreset().set_bit(); + w.usbevent().set_bit(); + w.epdata().set_bit(); + w + }); + + if self.vbus_detect.wait_power_ready().await.is_ok() { + // Enable the USB pullup, allowing enumeration. + regs.usbpullup.write(|w| w.connect().enabled()); + trace!("enabled"); + } else { + trace!("usb power not ready due to usb removal"); } } - fn disable(&mut self) -> Self::DisableFuture<'_> { - async move { - let regs = T::regs(); - regs.enable.write(|x| x.enable().disabled()); - } + async fn disable(&mut self) { + let regs = T::regs(); + regs.enable.write(|x| x.enable().disabled()); } - fn poll<'a>(&'a mut self) -> Self::PollFuture<'a> { + async fn poll(&mut self) -> Event { poll_fn(move |cx| { BUS_WAKER.register(cx.waker()); let regs = T::regs(); @@ -369,28 +245,28 @@ impl<'d, T: Instance, P: UsbSupply> driver::Bus for Bus<'d, T, P> { let r = regs.eventcause.read(); if r.isooutcrc().bit() { - regs.eventcause.write(|w| w.isooutcrc().set_bit()); + regs.eventcause.write(|w| w.isooutcrc().detected()); trace!("USB event: isooutcrc"); } if r.usbwuallowed().bit() { - regs.eventcause.write(|w| w.usbwuallowed().set_bit()); + regs.eventcause.write(|w| w.usbwuallowed().allowed()); trace!("USB event: usbwuallowed"); } if r.suspend().bit() { - regs.eventcause.write(|w| w.suspend().set_bit()); + regs.eventcause.write(|w| w.suspend().detected()); regs.lowpower.write(|w| w.lowpower().low_power()); return Poll::Ready(Event::Suspend); } if r.resume().bit() { - regs.eventcause.write(|w| w.resume().set_bit()); + regs.eventcause.write(|w| w.resume().detected()); return Poll::Ready(Event::Resume); } if r.ready().bit() { - regs.eventcause.write(|w| w.ready().set_bit()); + regs.eventcause.write(|w| w.ready().ready()); trace!("USB event: ready"); } - if self.usb_supply.is_usb_detected() != self.power_available { + if self.vbus_detect.is_usb_detected() != self.power_available { self.power_available = !self.power_available; if self.power_available { trace!("Power event: available"); @@ -403,11 +279,7 @@ impl<'d, T: Instance, P: UsbSupply> driver::Bus for Bus<'d, T, P> { Poll::Pending }) - } - - #[inline] - fn set_address(&mut self, _addr: u8) { - // Nothing to do, the peripheral handles this. + .await } fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool) { @@ -429,8 +301,8 @@ impl<'d, T: Instance, P: UsbSupply> driver::Bus for Bus<'d, T, P> { let regs = T::regs(); let i = ep_addr.index(); match ep_addr.direction() { - UsbDirection::Out => regs.halted.epout[i].read().getstatus().is_halted(), - UsbDirection::In => regs.halted.epin[i].read().getstatus().is_halted(), + Direction::Out => regs.halted.epout[i].read().getstatus().is_halted(), + Direction::In => regs.halted.epin[i].read().getstatus().is_halted(), } } @@ -443,7 +315,7 @@ impl<'d, T: Instance, P: UsbSupply> driver::Bus for Bus<'d, T, P> { debug!("endpoint_set_enabled {:?} {}", ep_addr, enabled); match ep_addr.direction() { - UsbDirection::In => { + Direction::In => { let mut was_enabled = false; regs.epinen.modify(|r, w| { let mut bits = r.bits(); @@ -467,7 +339,7 @@ impl<'d, T: Instance, P: UsbSupply> driver::Bus for Bus<'d, T, P> { In::waker(i).wake(); } - UsbDirection::Out => { + Direction::Out => { regs.epouten.modify(|r, w| { let mut bits = r.bits(); if enabled { @@ -495,46 +367,47 @@ impl<'d, T: Instance, P: UsbSupply> driver::Bus for Bus<'d, T, P> { } #[inline] - fn remote_wakeup(&mut self) -> Self::RemoteWakeupFuture<'_> { - async move { - let regs = T::regs(); + async fn remote_wakeup(&mut self) -> Result<(), Unsupported> { + let regs = T::regs(); - if regs.lowpower.read().lowpower().is_low_power() { - errata::pre_wakeup(); + if regs.lowpower.read().lowpower().is_low_power() { + errata::pre_wakeup(); - regs.lowpower.write(|w| w.lowpower().force_normal()); + regs.lowpower.write(|w| w.lowpower().force_normal()); - poll_fn(|cx| { - BUS_WAKER.register(cx.waker()); - let regs = T::regs(); - let r = regs.eventcause.read(); + poll_fn(|cx| { + BUS_WAKER.register(cx.waker()); + let regs = T::regs(); + let r = regs.eventcause.read(); - if regs.events_usbreset.read().bits() != 0 { - Poll::Ready(()) - } else if r.resume().bit() { - Poll::Ready(()) - } else if r.usbwuallowed().bit() { - regs.eventcause.write(|w| w.usbwuallowed().set_bit()); + if regs.events_usbreset.read().bits() != 0 { + Poll::Ready(()) + } else if r.resume().bit() { + Poll::Ready(()) + } else if r.usbwuallowed().bit() { + regs.eventcause.write(|w| w.usbwuallowed().allowed()); - regs.dpdmvalue.write(|w| w.state().resume()); - regs.tasks_dpdmdrive.write(|w| w.tasks_dpdmdrive().set_bit()); + regs.dpdmvalue.write(|w| w.state().resume()); + regs.tasks_dpdmdrive.write(|w| w.tasks_dpdmdrive().set_bit()); - Poll::Ready(()) - } else { - Poll::Pending - } - }) - .await; + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; - errata::post_wakeup(); - } - - Ok(()) + errata::post_wakeup(); } + + Ok(()) } } +/// Type-level marker for OUT endpoints. pub enum Out {} + +/// Type-level marker for IN endpoints. pub enum In {} trait EndpointDir { @@ -577,6 +450,7 @@ impl EndpointDir for Out { } } +/// USB endpoint. pub struct Endpoint<'d, T: Instance, Dir> { _phantom: PhantomData<(&'d mut T, Dir)>, info: EndpointInfo, @@ -596,9 +470,7 @@ impl<'d, T: Instance, Dir: EndpointDir> driver::Endpoint for Endpoint<'d, T, Dir &self.info } - type WaitEnabledFuture<'a> = impl Future + 'a where Self: 'a; - - fn wait_enabled(&mut self) -> Self::WaitEnabledFuture<'_> { + async fn wait_enabled(&mut self) { let i = self.info.addr.index(); assert!(i != 0); @@ -610,6 +482,7 @@ impl<'d, T: Instance, Dir: EndpointDir> driver::Endpoint for Endpoint<'d, T, Dir Poll::Pending } }) + .await } } @@ -714,173 +587,155 @@ unsafe fn write_dma(i: usize, buf: &[u8]) { } impl<'d, T: Instance> driver::EndpointOut for Endpoint<'d, T, Out> { - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; + async fn read(&mut self, buf: &mut [u8]) -> Result { + let i = self.info.addr.index(); + assert!(i != 0); - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - async move { - let i = self.info.addr.index(); - assert!(i != 0); + self.wait_data_ready().await.map_err(|_| EndpointError::Disabled)?; - self.wait_data_ready().await.map_err(|_| EndpointError::Disabled)?; - - unsafe { read_dma::(i, buf) } - } + unsafe { read_dma::(i, buf) } } } impl<'d, T: Instance> driver::EndpointIn for Endpoint<'d, T, In> { - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; + async fn write(&mut self, buf: &[u8]) -> Result<(), EndpointError> { + let i = self.info.addr.index(); + assert!(i != 0); - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { - async move { - let i = self.info.addr.index(); - assert!(i != 0); + self.wait_data_ready().await.map_err(|_| EndpointError::Disabled)?; - self.wait_data_ready().await.map_err(|_| EndpointError::Disabled)?; + unsafe { write_dma::(i, buf) } - unsafe { write_dma::(i, buf) } - - Ok(()) - } + Ok(()) } } +/// USB control pipe. pub struct ControlPipe<'d, T: Instance> { _p: PeripheralRef<'d, T>, max_packet_size: u16, } impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { - type SetupFuture<'a> = impl Future + 'a where Self: 'a; - type DataOutFuture<'a> = impl Future> + 'a where Self: 'a; - type DataInFuture<'a> = impl Future> + 'a where Self: 'a; - type AcceptFuture<'a> = impl Future + 'a where Self: 'a; - type RejectFuture<'a> = impl Future + 'a where Self: 'a; - fn max_packet_size(&self) -> usize { usize::from(self.max_packet_size) } - fn setup<'a>(&'a mut self) -> Self::SetupFuture<'a> { - async move { + async fn setup(&mut self) -> [u8; 8] { + let regs = T::regs(); + + // Reset shorts + regs.shorts.write(|w| w); + + // Wait for SETUP packet + regs.intenset.write(|w| w.ep0setup().set()); + poll_fn(|cx| { + EP0_WAKER.register(cx.waker()); let regs = T::regs(); + if regs.events_ep0setup.read().bits() != 0 { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; - // Reset shorts - regs.shorts.write(|w| w); + regs.events_ep0setup.reset(); - // Wait for SETUP packet - regs.intenset.write(|w| w.ep0setup().set()); - poll_fn(|cx| { - EP0_WAKER.register(cx.waker()); - let regs = T::regs(); - if regs.events_ep0setup.read().bits() != 0 { - Poll::Ready(()) - } else { - Poll::Pending - } - }) - .await; + let mut buf = [0; 8]; + buf[0] = regs.bmrequesttype.read().bits() as u8; + buf[1] = regs.brequest.read().brequest().bits(); + buf[2] = regs.wvaluel.read().wvaluel().bits(); + buf[3] = regs.wvalueh.read().wvalueh().bits(); + buf[4] = regs.windexl.read().windexl().bits(); + buf[5] = regs.windexh.read().windexh().bits(); + buf[6] = regs.wlengthl.read().wlengthl().bits(); + buf[7] = regs.wlengthh.read().wlengthh().bits(); - regs.events_ep0setup.reset(); - - let mut buf = [0; 8]; - buf[0] = regs.bmrequesttype.read().bits() as u8; - buf[1] = regs.brequest.read().brequest().bits(); - buf[2] = regs.wvaluel.read().wvaluel().bits(); - buf[3] = regs.wvalueh.read().wvalueh().bits(); - buf[4] = regs.windexl.read().windexl().bits(); - buf[5] = regs.windexh.read().windexh().bits(); - buf[6] = regs.wlengthl.read().wlengthl().bits(); - buf[7] = regs.wlengthh.read().wlengthh().bits(); - - buf - } + buf } - fn data_out<'a>(&'a mut self, buf: &'a mut [u8], _first: bool, _last: bool) -> Self::DataOutFuture<'a> { - async move { + async fn data_out(&mut self, buf: &mut [u8], _first: bool, _last: bool) -> Result { + let regs = T::regs(); + + regs.events_ep0datadone.reset(); + + // This starts a RX on EP0. events_ep0datadone notifies when done. + regs.tasks_ep0rcvout.write(|w| w.tasks_ep0rcvout().set_bit()); + + // Wait until ready + regs.intenset.write(|w| { + w.usbreset().set(); + w.ep0setup().set(); + w.ep0datadone().set() + }); + poll_fn(|cx| { + EP0_WAKER.register(cx.waker()); let regs = T::regs(); + if regs.events_ep0datadone.read().bits() != 0 { + Poll::Ready(Ok(())) + } else if regs.events_usbreset.read().bits() != 0 { + trace!("aborted control data_out: usb reset"); + Poll::Ready(Err(EndpointError::Disabled)) + } else if regs.events_ep0setup.read().bits() != 0 { + trace!("aborted control data_out: received another SETUP"); + Poll::Ready(Err(EndpointError::Disabled)) + } else { + Poll::Pending + } + }) + .await?; - regs.events_ep0datadone.reset(); - - // This starts a RX on EP0. events_ep0datadone notifies when done. - regs.tasks_ep0rcvout.write(|w| w.tasks_ep0rcvout().set_bit()); - - // Wait until ready - regs.intenset.write(|w| { - w.usbreset().set(); - w.ep0setup().set(); - w.ep0datadone().set() - }); - poll_fn(|cx| { - EP0_WAKER.register(cx.waker()); - let regs = T::regs(); - if regs.events_ep0datadone.read().bits() != 0 { - Poll::Ready(Ok(())) - } else if regs.events_usbreset.read().bits() != 0 { - trace!("aborted control data_out: usb reset"); - Poll::Ready(Err(EndpointError::Disabled)) - } else if regs.events_ep0setup.read().bits() != 0 { - trace!("aborted control data_out: received another SETUP"); - Poll::Ready(Err(EndpointError::Disabled)) - } else { - Poll::Pending - } - }) - .await?; - - unsafe { read_dma::(0, buf) } - } + unsafe { read_dma::(0, buf) } } - fn data_in<'a>(&'a mut self, buf: &'a [u8], _first: bool, last: bool) -> Self::DataInFuture<'a> { - async move { + async fn data_in(&mut self, buf: &[u8], _first: bool, last: bool) -> Result<(), EndpointError> { + let regs = T::regs(); + regs.events_ep0datadone.reset(); + + regs.shorts.write(|w| w.ep0datadone_ep0status().bit(last)); + + // This starts a TX on EP0. events_ep0datadone notifies when done. + unsafe { write_dma::(0, buf) } + + regs.intenset.write(|w| { + w.usbreset().set(); + w.ep0setup().set(); + w.ep0datadone().set() + }); + + poll_fn(|cx| { + cx.waker().wake_by_ref(); + EP0_WAKER.register(cx.waker()); let regs = T::regs(); - regs.events_ep0datadone.reset(); - - regs.shorts.write(|w| w.ep0datadone_ep0status().bit(last)); - - // This starts a TX on EP0. events_ep0datadone notifies when done. - unsafe { write_dma::(0, buf) } - - regs.intenset.write(|w| { - w.usbreset().set(); - w.ep0setup().set(); - w.ep0datadone().set() - }); - - poll_fn(|cx| { - cx.waker().wake_by_ref(); - EP0_WAKER.register(cx.waker()); - let regs = T::regs(); - if regs.events_ep0datadone.read().bits() != 0 { - Poll::Ready(Ok(())) - } else if regs.events_usbreset.read().bits() != 0 { - trace!("aborted control data_in: usb reset"); - Poll::Ready(Err(EndpointError::Disabled)) - } else if regs.events_ep0setup.read().bits() != 0 { - trace!("aborted control data_in: received another SETUP"); - Poll::Ready(Err(EndpointError::Disabled)) - } else { - Poll::Pending - } - }) - .await - } + if regs.events_ep0datadone.read().bits() != 0 { + Poll::Ready(Ok(())) + } else if regs.events_usbreset.read().bits() != 0 { + trace!("aborted control data_in: usb reset"); + Poll::Ready(Err(EndpointError::Disabled)) + } else if regs.events_ep0setup.read().bits() != 0 { + trace!("aborted control data_in: received another SETUP"); + Poll::Ready(Err(EndpointError::Disabled)) + } else { + Poll::Pending + } + }) + .await } - fn accept<'a>(&'a mut self) -> Self::AcceptFuture<'a> { - async move { - let regs = T::regs(); - regs.tasks_ep0status.write(|w| w.tasks_ep0status().bit(true)); - } + async fn accept(&mut self) { + let regs = T::regs(); + regs.tasks_ep0status.write(|w| w.tasks_ep0status().bit(true)); } - fn reject<'a>(&'a mut self) -> Self::RejectFuture<'a> { - async move { - let regs = T::regs(); - regs.tasks_ep0stall.write(|w| w.tasks_ep0stall().bit(true)); - } + async fn reject(&mut self) { + let regs = T::regs(); + regs.tasks_ep0stall.write(|w| w.tasks_ep0stall().bit(true)); + } + + async fn accept_set_address(&mut self, _addr: u8) { + self.accept().await; + // Nothing to do, the peripheral handles this. } } @@ -946,8 +801,10 @@ pub(crate) mod sealed { } } +/// USB peripheral instance. pub trait Instance: Peripheral

+ sealed::Instance + 'static + Send { - type Interrupt: Interrupt; + /// Interrupt for this peripheral. + type Interrupt: interrupt::typelevel::Interrupt; } macro_rules! impl_usb { @@ -958,7 +815,7 @@ macro_rules! impl_usb { } } impl crate::usb::Instance for peripherals::$type { - type Interrupt = crate::interrupt::$irq; + type Interrupt = crate::interrupt::typelevel::$irq; } }; } diff --git a/embassy-nrf/src/usb/vbus_detect.rs b/embassy-nrf/src/usb/vbus_detect.rs new file mode 100644 index 000000000..a05e5aa52 --- /dev/null +++ b/embassy-nrf/src/usb/vbus_detect.rs @@ -0,0 +1,177 @@ +//! Trait and implementations for performing VBUS detection. + +use core::future::poll_fn; +use core::sync::atomic::{AtomicBool, Ordering}; +use core::task::Poll; + +use embassy_sync::waitqueue::AtomicWaker; + +use super::BUS_WAKER; +use crate::interrupt::typelevel::Interrupt; +use crate::{interrupt, pac}; + +/// Trait for detecting USB VBUS power. +/// +/// There are multiple ways to detect USB power. The behavior +/// here provides a hook into determining whether it is. +pub trait VbusDetect { + /// Report whether power is detected. + /// + /// This is indicated by the `USBREGSTATUS.VBUSDETECT` register, or the + /// `USBDETECTED`, `USBREMOVED` events from the `POWER` peripheral. + fn is_usb_detected(&self) -> bool; + + /// Wait until USB power is ready. + /// + /// USB power ready is indicated by the `USBREGSTATUS.OUTPUTRDY` register, or the + /// `USBPWRRDY` event from the `POWER` peripheral. + async fn wait_power_ready(&mut self) -> Result<(), ()>; +} + +#[cfg(not(feature = "_nrf5340"))] +type UsbRegIrq = interrupt::typelevel::POWER_CLOCK; +#[cfg(feature = "_nrf5340")] +type UsbRegIrq = interrupt::typelevel::USBREGULATOR; + +#[cfg(not(feature = "_nrf5340"))] +type UsbRegPeri = pac::POWER; +#[cfg(feature = "_nrf5340")] +type UsbRegPeri = pac::USBREGULATOR; + +/// Interrupt handler. +pub struct InterruptHandler { + _private: (), +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let regs = unsafe { &*UsbRegPeri::ptr() }; + + if regs.events_usbdetected.read().bits() != 0 { + regs.events_usbdetected.reset(); + BUS_WAKER.wake(); + } + + if regs.events_usbremoved.read().bits() != 0 { + regs.events_usbremoved.reset(); + BUS_WAKER.wake(); + POWER_WAKER.wake(); + } + + if regs.events_usbpwrrdy.read().bits() != 0 { + regs.events_usbpwrrdy.reset(); + POWER_WAKER.wake(); + } + } +} + +/// [`VbusDetect`] implementation using the native hardware POWER peripheral. +/// +/// Unsuitable for usage with the nRF softdevice, since it reserves exclusive acces +/// to POWER. In that case, use [`VbusDetectSignal`]. +pub struct HardwareVbusDetect { + _private: (), +} + +static POWER_WAKER: AtomicWaker = AtomicWaker::new(); + +impl HardwareVbusDetect { + /// Create a new `VbusDetectNative`. + pub fn new(_irq: impl interrupt::typelevel::Binding + 'static) -> Self { + let regs = unsafe { &*UsbRegPeri::ptr() }; + + UsbRegIrq::unpend(); + unsafe { UsbRegIrq::enable() }; + + regs.intenset + .write(|w| w.usbdetected().set().usbremoved().set().usbpwrrdy().set()); + + Self { _private: () } + } +} + +impl VbusDetect for HardwareVbusDetect { + fn is_usb_detected(&self) -> bool { + let regs = unsafe { &*UsbRegPeri::ptr() }; + regs.usbregstatus.read().vbusdetect().is_vbus_present() + } + + async fn wait_power_ready(&mut self) -> Result<(), ()> { + poll_fn(move |cx| { + POWER_WAKER.register(cx.waker()); + let regs = unsafe { &*UsbRegPeri::ptr() }; + + if regs.usbregstatus.read().outputrdy().is_ready() { + Poll::Ready(Ok(())) + } else if !self.is_usb_detected() { + Poll::Ready(Err(())) + } else { + Poll::Pending + } + }) + .await + } +} + +/// Software-backed [`VbusDetect`] implementation. +/// +/// This implementation does not interact with the hardware, it allows user code +/// to notify the power events by calling functions instead. +/// +/// This is suitable for use with the nRF softdevice, by calling the functions +/// when the softdevice reports power-related events. +pub struct SoftwareVbusDetect { + usb_detected: AtomicBool, + power_ready: AtomicBool, +} + +impl SoftwareVbusDetect { + /// Create a new `SoftwareVbusDetect`. + pub fn new(usb_detected: bool, power_ready: bool) -> Self { + BUS_WAKER.wake(); + + Self { + usb_detected: AtomicBool::new(usb_detected), + power_ready: AtomicBool::new(power_ready), + } + } + + /// Report whether power was detected. + /// + /// Equivalent to the `USBDETECTED`, `USBREMOVED` events from the `POWER` peripheral. + pub fn detected(&self, detected: bool) { + self.usb_detected.store(detected, Ordering::Relaxed); + self.power_ready.store(false, Ordering::Relaxed); + BUS_WAKER.wake(); + POWER_WAKER.wake(); + } + + /// Report when USB power is ready. + /// + /// Equivalent to the `USBPWRRDY` event from the `POWER` peripheral. + pub fn ready(&self) { + self.power_ready.store(true, Ordering::Relaxed); + POWER_WAKER.wake(); + } +} + +impl VbusDetect for &SoftwareVbusDetect { + fn is_usb_detected(&self) -> bool { + self.usb_detected.load(Ordering::Relaxed) + } + + async fn wait_power_ready(&mut self) -> Result<(), ()> { + poll_fn(move |cx| { + POWER_WAKER.register(cx.waker()); + + if self.power_ready.load(Ordering::Relaxed) { + Poll::Ready(Ok(())) + } else if !self.usb_detected.load(Ordering::Relaxed) { + Poll::Ready(Err(())) + } else { + Poll::Pending + } + }) + .await + } +} diff --git a/embassy-nrf/src/wdt.rs b/embassy-nrf/src/wdt.rs index 8760aa301..40a674424 100644 --- a/embassy-nrf/src/wdt.rs +++ b/embassy-nrf/src/wdt.rs @@ -1,4 +1,4 @@ -//! HAL interface to the WDT peripheral. +//! Watchdog Timer (WDT) driver. //! //! This HAL implements a basic watchdog timer with 1..=8 handles. //! Once the watchdog has been started, it cannot be stopped. @@ -8,6 +8,7 @@ use crate::peripherals; const MIN_TICKS: u32 = 15; +/// WDT configuration. #[non_exhaustive] pub struct Config { /// Number of 32768 Hz ticks in each watchdog period. @@ -23,6 +24,30 @@ pub struct Config { pub run_during_debug_halt: bool, } +impl Config { + /// Create a config structure from the current configuration of the WDT + /// peripheral. + pub fn try_new(_wdt: &peripherals::WDT) -> Option { + let r = unsafe { &*WDT::ptr() }; + + #[cfg(not(feature = "_nrf9160"))] + let runstatus = r.runstatus.read().runstatus().bit(); + #[cfg(feature = "_nrf9160")] + let runstatus = r.runstatus.read().runstatuswdt().bit(); + + if runstatus { + let config = r.config.read(); + Some(Self { + timeout_ticks: r.crv.read().bits(), + run_during_sleep: config.sleep().bit(), + run_during_debug_halt: config.halt().bit(), + }) + } else { + None + } + } +} + impl Default for Config { fn default() -> Self { Self { @@ -33,13 +58,13 @@ impl Default for Config { } } -/// An interface to the Watchdog. +/// Watchdog driver. pub struct Watchdog { _private: (), } impl Watchdog { - /// Try to create a new watchdog instance from the peripheral. + /// Try to create a new watchdog driver. /// /// This function will return an error if the watchdog is already active /// with a `config` different to the requested one, or a different number of @@ -131,6 +156,7 @@ impl Watchdog { } } +/// Watchdog handle. pub struct WatchdogHandle { index: u8, } diff --git a/embassy-rp/Cargo.toml b/embassy-rp/Cargo.toml index cfd95b7b4..8f3ed885d 100644 --- a/embassy-rp/Cargo.toml +++ b/embassy-rp/Cargo.toml @@ -2,16 +2,24 @@ name = "embassy-rp" version = "0.1.0" edition = "2021" +license = "MIT OR Apache-2.0" [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-rp-v$VERSION/embassy-rp/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-rp/src/" -features = ["nightly", "defmt", "unstable-pac", "unstable-traits"] +features = ["nightly", "defmt", "unstable-pac", "unstable-traits", "time-driver"] flavors = [ { name = "rp2040", target = "thumbv6m-none-eabi" }, ] [features] +default = [ "rt" ] +rt = [ "rp-pac/rt" ] + +defmt = ["dep:defmt", "embassy-usb-driver?/defmt", "embassy-hal-common/defmt"] + +# critical section that is safe for multicore use +critical-section-impl = ["critical-section/restore-state-u8"] # Reexport the PAC for the currently enabled chip at `embassy_rp::pac`. # This is unstable because semver-minor (non-breaking) releases of embassy-rp may major-bump (breaking) the PAC version. @@ -19,20 +27,40 @@ flavors = [ # There are no plans to make this stable. unstable-pac = [] +time-driver = [] + +rom-func-cache = [] +intrinsics = [] +rom-v2-intrinsics = [] + +# boot2 flash chip support. if none of these is enabled we'll default to w25q080 (used on the pico) +boot2-at25sf128a = [] +boot2-gd25q64cs = [] +boot2-generic-03h = [] +boot2-is25lp080 = [] +boot2-ram-memcpy = [] +boot2-w25q080 = [] +boot2-w25x10cl = [] + +# Indicate code is running from RAM. +# Set this if all code is in RAM, and the cores never access memory-mapped flash memory through XIP. +# This allows the flash driver to not force pausing execution on both cores when doing flash operations. +run-from-ram = [] + # Enable nightly-only features -nightly = ["embassy-executor/nightly", "embedded-hal-1", "embedded-hal-async", "embassy-embedded-hal/nightly"] +nightly = ["embedded-hal-1", "embedded-hal-async", "embassy-embedded-hal/nightly", "dep:embassy-usb-driver", "dep:embedded-io"] # Implement embedded-hal 1.0 alpha traits. # Implement embedded-hal-async traits if `nightly` is set as well. -unstable-traits = ["embedded-hal-1"] +unstable-traits = ["embedded-hal-1", "embedded-hal-nb"] [dependencies] -embassy-sync = { version = "0.1.0", path = "../embassy-sync" } -embassy-executor = { version = "0.1.0", path = "../embassy-executor" } -embassy-time = { version = "0.1.0", path = "../embassy-time", features = [ "tick-1mhz" ] } -embassy-cortex-m = { version = "0.1.0", path = "../embassy-cortex-m", features = ["prio-bits-2"]} -embassy-hal-common = {version = "0.1.0", path = "../embassy-hal-common" } +embassy-sync = { version = "0.2.0", path = "../embassy-sync" } +embassy-time = { version = "0.1.2", path = "../embassy-time", features = [ "tick-hz-1_000_000" ] } +embassy-futures = { version = "0.1.0", path = "../embassy-futures" } +embassy-hal-common = {version = "0.1.0", path = "../embassy-hal-common", features = ["cortex-m", "prio-bits-2"] } embassy-embedded-hal = {version = "0.1.0", path = "../embassy-embedded-hal" } +embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver", optional = true } atomic-polyfill = "1.0.1" defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } @@ -42,10 +70,24 @@ cortex-m-rt = ">=0.6.15,<0.8" cortex-m = "0.7.6" critical-section = "1.1" futures = { version = "0.3.17", default-features = false, features = ["async-await"] } +chrono = { version = "0.4", default-features = false, optional = true } +embedded-io = { version = "0.4.0", features = ["async"], optional = true } +embedded-storage = { version = "0.3" } +rand_core = "0.6.4" +fixed = "1.23.1" -rp2040-pac2 = { git = "https://github.com/embassy-rs/rp2040-pac2", rev="9ad7223a48a065e612bc7dc7be5bf5bd0b41cfc4", features = ["rt"] } -#rp2040-pac2 = { path = "../../rp/rp2040-pac2", features = ["rt"] } +rp-pac = { version = "6" } embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } -embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.8", optional = true} -embedded-hal-async = { version = "0.1.0-alpha.1", optional = true} +embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11", optional = true} +embedded-hal-async = { version = "=0.2.0-alpha.2", optional = true} +embedded-hal-nb = { version = "=1.0.0-alpha.3", optional = true} + +paste = "1.0" +pio-proc = {version= "0.2" } +pio = {version= "0.2.1" } +rp2040-boot2 = "0.3" + +[dev-dependencies] +embassy-executor = { version = "0.2.0", path = "../embassy-executor", features = ["nightly", "arch-std", "executor-thread"] } +static_cell = "1.1" diff --git a/embassy-rp/src/adc.rs b/embassy-rp/src/adc.rs new file mode 100644 index 000000000..dfa1b877a --- /dev/null +++ b/embassy-rp/src/adc.rs @@ -0,0 +1,238 @@ +use core::future::poll_fn; +use core::marker::PhantomData; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; + +use embassy_hal_common::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; + +use crate::gpio::sealed::Pin as GpioPin; +use crate::gpio::{self, AnyPin, Pull}; +use crate::interrupt::typelevel::Binding; +use crate::interrupt::InterruptExt; +use crate::peripherals::ADC; +use crate::{interrupt, pac, peripherals, Peripheral}; + +static WAKER: AtomicWaker = AtomicWaker::new(); + +#[non_exhaustive] +pub struct Config {} + +impl Default for Config { + fn default() -> Self { + Self {} + } +} + +pub struct Pin<'p> { + pin: PeripheralRef<'p, AnyPin>, +} + +impl<'p> Pin<'p> { + pub fn new(pin: impl Peripheral

+ 'p, pull: Pull) -> Self { + into_ref!(pin); + pin.pad_ctrl().modify(|w| { + // manual says: + // + // > When using an ADC input shared with a GPIO pin, the pin’s + // > digital functions must be disabled by setting IE low and OD + // > high in the pin’s pad control register + w.set_ie(false); + w.set_od(true); + w.set_pue(pull == Pull::Up); + w.set_pde(pull == Pull::Down); + }); + Self { pin: pin.map_into() } + } + + fn channel(&self) -> u8 { + // this requires adc pins to be sequential and matching the adc channels, + // which is the case for rp2040 + self.pin._pin() - 26 + } +} + +impl<'d> Drop for Pin<'d> { + fn drop(&mut self) { + self.pin.pad_ctrl().modify(|w| { + w.set_ie(true); + w.set_od(false); + w.set_pue(false); + w.set_pde(true); + }); + } +} + +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + ConversionFailed, +} + +pub trait Mode {} + +pub struct Async; +impl Mode for Async {} + +pub struct Blocking; +impl Mode for Blocking {} + +pub struct Adc<'d, M: Mode> { + phantom: PhantomData<(&'d ADC, M)>, +} + +impl<'d, M: Mode> Adc<'d, M> { + #[inline] + fn regs() -> pac::adc::Adc { + pac::ADC + } + + #[inline] + fn reset() -> pac::resets::regs::Peripherals { + let mut ret = pac::resets::regs::Peripherals::default(); + ret.set_adc(true); + ret + } + + fn setup() { + let reset = Self::reset(); + crate::reset::reset(reset); + crate::reset::unreset_wait(reset); + let r = Self::regs(); + // Enable ADC + r.cs().write(|w| w.set_en(true)); + // Wait for ADC ready + while !r.cs().read().ready() {} + } + + fn sample_blocking(channel: u8) -> Result { + let r = Self::regs(); + r.cs().modify(|w| { + w.set_ainsel(channel); + w.set_start_once(true); + w.set_err(true); + }); + while !r.cs().read().ready() {} + match r.cs().read().err() { + true => Err(Error::ConversionFailed), + false => Ok(r.result().read().result().into()), + } + } + + pub fn blocking_read(&mut self, pin: &mut Pin) -> Result { + Self::sample_blocking(pin.channel()) + } + + pub fn blocking_read_temperature(&mut self) -> Result { + let r = Self::regs(); + r.cs().modify(|w| w.set_ts_en(true)); + while !r.cs().read().ready() {} + let result = Self::sample_blocking(4); + r.cs().modify(|w| w.set_ts_en(false)); + result + } +} + +impl<'d> Adc<'d, Async> { + pub fn new( + _inner: impl Peripheral

+ 'd, + _irq: impl Binding, + _config: Config, + ) -> Self { + Self::setup(); + + // Setup IRQ + interrupt::ADC_IRQ_FIFO.unpend(); + unsafe { interrupt::ADC_IRQ_FIFO.enable() }; + + Self { phantom: PhantomData } + } + + async fn wait_for_ready() { + let r = Self::regs(); + r.inte().write(|w| w.set_fifo(true)); + compiler_fence(Ordering::SeqCst); + poll_fn(|cx| { + WAKER.register(cx.waker()); + if r.cs().read().ready() { + return Poll::Ready(()); + } + Poll::Pending + }) + .await; + } + + async fn sample_async(channel: u8) -> Result { + let r = Self::regs(); + r.cs().modify(|w| { + w.set_ainsel(channel); + w.set_start_once(true); + w.set_err(true); + }); + Self::wait_for_ready().await; + match r.cs().read().err() { + true => Err(Error::ConversionFailed), + false => Ok(r.result().read().result().into()), + } + } + + pub async fn read(&mut self, pin: &mut Pin<'_>) -> Result { + Self::sample_async(pin.channel()).await + } + + pub async fn read_temperature(&mut self) -> Result { + let r = Self::regs(); + r.cs().modify(|w| w.set_ts_en(true)); + if !r.cs().read().ready() { + Self::wait_for_ready().await; + } + let result = Self::sample_async(4).await; + r.cs().modify(|w| w.set_ts_en(false)); + result + } +} + +impl<'d> Adc<'d, Blocking> { + pub fn new_blocking(_inner: impl Peripheral

+ 'd, _config: Config) -> Self { + Self::setup(); + + Self { phantom: PhantomData } + } +} + +pub struct InterruptHandler { + _empty: (), +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let r = Adc::::regs(); + r.inte().write(|w| w.set_fifo(false)); + WAKER.wake(); + } +} + +mod sealed { + pub trait AdcPin: crate::gpio::sealed::Pin { + fn channel(&mut self) -> u8; + } +} + +pub trait AdcPin: sealed::AdcPin + gpio::Pin {} + +macro_rules! impl_pin { + ($pin:ident, $channel:expr) => { + impl sealed::AdcPin for peripherals::$pin { + fn channel(&mut self) -> u8 { + $channel + } + } + + impl AdcPin for peripherals::$pin {} + }; +} + +impl_pin!(PIN_26, 0); +impl_pin!(PIN_27, 1); +impl_pin!(PIN_28, 2); +impl_pin!(PIN_29, 3); diff --git a/embassy-rp/src/boot2.bin b/embassy-rp/src/boot2.bin deleted file mode 100644 index fdc1fc756..000000000 Binary files a/embassy-rp/src/boot2.bin and /dev/null differ diff --git a/embassy-rp/src/clocks.rs b/embassy-rp/src/clocks.rs index 3ad1e5d82..acb21dce5 100644 --- a/embassy-rp/src/clocks.rs +++ b/embassy-rp/src/clocks.rs @@ -1,138 +1,633 @@ +use core::marker::PhantomData; +use core::sync::atomic::{AtomicU16, AtomicU32, Ordering}; + +use embassy_hal_common::{into_ref, PeripheralRef}; use pac::clocks::vals::*; -use crate::{pac, reset}; +use crate::gpio::sealed::Pin; +use crate::gpio::AnyPin; +use crate::{pac, reset, Peripheral}; -const XOSC_MHZ: u32 = 12; +// NOTE: all gpin handling is commented out for future reference. +// gpin is not usually safe to use during the boot init() call, so it won't +// be very useful until we have runtime clock reconfiguration. once this +// happens we can resurrect the commented-out gpin bits. +struct Clocks { + xosc: AtomicU32, + sys: AtomicU32, + reference: AtomicU32, + pll_sys: AtomicU32, + pll_usb: AtomicU32, + usb: AtomicU32, + adc: AtomicU32, + // gpin0: AtomicU32, + // gpin1: AtomicU32, + rosc: AtomicU32, + peri: AtomicU32, + rtc: AtomicU16, +} + +static CLOCKS: Clocks = Clocks { + xosc: AtomicU32::new(0), + sys: AtomicU32::new(0), + reference: AtomicU32::new(0), + pll_sys: AtomicU32::new(0), + pll_usb: AtomicU32::new(0), + usb: AtomicU32::new(0), + adc: AtomicU32::new(0), + // gpin0: AtomicU32::new(0), + // gpin1: AtomicU32::new(0), + rosc: AtomicU32::new(0), + peri: AtomicU32::new(0), + rtc: AtomicU16::new(0), +}; + +#[repr(u8)] +#[non_exhaustive] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum PeriClkSrc { + Sys = ClkPeriCtrlAuxsrc::CLK_SYS as _, + PllSys = ClkPeriCtrlAuxsrc::CLKSRC_PLL_SYS as _, + PllUsb = ClkPeriCtrlAuxsrc::CLKSRC_PLL_USB as _, + Rosc = ClkPeriCtrlAuxsrc::ROSC_CLKSRC_PH as _, + Xosc = ClkPeriCtrlAuxsrc::XOSC_CLKSRC as _, + // Gpin0 = ClkPeriCtrlAuxsrc::CLKSRC_GPIN0 as _ , + // Gpin1 = ClkPeriCtrlAuxsrc::CLKSRC_GPIN1 as _ , +} + +#[non_exhaustive] +pub struct ClockConfig { + pub rosc: Option, + pub xosc: Option, + pub ref_clk: RefClkConfig, + pub sys_clk: SysClkConfig, + pub peri_clk_src: Option, + pub usb_clk: Option, + pub adc_clk: Option, + pub rtc_clk: Option, + // gpin0: Option<(u32, Gpin<'static, AnyPin>)>, + // gpin1: Option<(u32, Gpin<'static, AnyPin>)>, +} + +impl ClockConfig { + pub fn crystal(crystal_hz: u32) -> Self { + Self { + rosc: Some(RoscConfig { + hz: 6_500_000, + range: RoscRange::Medium, + drive_strength: [0; 8], + div: 16, + }), + xosc: Some(XoscConfig { + hz: crystal_hz, + sys_pll: Some(PllConfig { + refdiv: 1, + fbdiv: 125, + post_div1: 6, + post_div2: 2, + }), + usb_pll: Some(PllConfig { + refdiv: 1, + fbdiv: 120, + post_div1: 6, + post_div2: 5, + }), + }), + ref_clk: RefClkConfig { + src: RefClkSrc::Xosc, + div: 1, + }, + sys_clk: SysClkConfig { + src: SysClkSrc::PllSys, + div_int: 1, + div_frac: 0, + }, + peri_clk_src: Some(PeriClkSrc::Sys), + // CLK USB = PLL USB (48MHz) / 1 = 48MHz + usb_clk: Some(UsbClkConfig { + src: UsbClkSrc::PllUsb, + div: 1, + phase: 0, + }), + // CLK ADC = PLL USB (48MHZ) / 1 = 48MHz + adc_clk: Some(AdcClkConfig { + src: AdcClkSrc::PllUsb, + div: 1, + phase: 0, + }), + // CLK RTC = PLL USB (48MHz) / 1024 = 46875Hz + rtc_clk: Some(RtcClkConfig { + src: RtcClkSrc::PllUsb, + div_int: 1024, + div_frac: 0, + phase: 0, + }), + // gpin0: None, + // gpin1: None, + } + } + + pub fn rosc() -> Self { + Self { + rosc: Some(RoscConfig { + hz: 140_000_000, + range: RoscRange::High, + drive_strength: [0; 8], + div: 1, + }), + xosc: None, + ref_clk: RefClkConfig { + src: RefClkSrc::Rosc, + div: 1, + }, + sys_clk: SysClkConfig { + src: SysClkSrc::Rosc, + div_int: 1, + div_frac: 0, + }, + peri_clk_src: Some(PeriClkSrc::Rosc), + usb_clk: None, + // CLK ADC = ROSC (140MHz) / 3 ≅ 48MHz + adc_clk: Some(AdcClkConfig { + src: AdcClkSrc::Rosc, + div: 3, + phase: 0, + }), + // CLK RTC = ROSC (140MHz) / 2986.667969 ≅ 46875Hz + rtc_clk: Some(RtcClkConfig { + src: RtcClkSrc::Rosc, + div_int: 2986, + div_frac: 171, + phase: 0, + }), + // gpin0: None, + // gpin1: None, + } + } + + // pub fn bind_gpin(&mut self, gpin: Gpin<'static, P>, hz: u32) { + // match P::NR { + // 0 => self.gpin0 = Some((hz, gpin.map_into())), + // 1 => self.gpin1 = Some((hz, gpin.map_into())), + // _ => unreachable!(), + // } + // // pin is now provisionally bound. if the config is applied it must be forgotten, + // // or Gpin::drop will deconfigure the clock input. + // } +} + +#[repr(u16)] +#[non_exhaustive] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum RoscRange { + Low = pac::rosc::vals::FreqRange::LOW.0, + Medium = pac::rosc::vals::FreqRange::MEDIUM.0, + High = pac::rosc::vals::FreqRange::HIGH.0, + TooHigh = pac::rosc::vals::FreqRange::TOOHIGH.0, +} + +pub struct RoscConfig { + /// Final frequency of the oscillator, after the divider has been applied. + /// The oscillator has a nominal frequency of 6.5MHz at medium range with + /// divider 16 and all drive strengths set to 0, other values should be + /// measured in situ. + pub hz: u32, + pub range: RoscRange, + pub drive_strength: [u8; 8], + pub div: u16, +} + +pub struct XoscConfig { + pub hz: u32, + pub sys_pll: Option, + pub usb_pll: Option, +} + +pub struct PllConfig { + pub refdiv: u8, + pub fbdiv: u16, + pub post_div1: u8, + pub post_div2: u8, +} + +pub struct RefClkConfig { + pub src: RefClkSrc, + pub div: u8, +} + +#[non_exhaustive] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum RefClkSrc { + // main sources + Xosc, + Rosc, + // aux sources + PllUsb, + // Gpin0, + // Gpin1, +} + +#[non_exhaustive] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum SysClkSrc { + // main sources + Ref, + // aux sources + PllSys, + PllUsb, + Rosc, + Xosc, + // Gpin0, + // Gpin1, +} + +pub struct SysClkConfig { + pub src: SysClkSrc, + pub div_int: u32, + pub div_frac: u8, +} + +#[repr(u8)] +#[non_exhaustive] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum UsbClkSrc { + PllUsb = ClkUsbCtrlAuxsrc::CLKSRC_PLL_USB as _, + PllSys = ClkUsbCtrlAuxsrc::CLKSRC_PLL_SYS as _, + Rosc = ClkUsbCtrlAuxsrc::ROSC_CLKSRC_PH as _, + Xosc = ClkUsbCtrlAuxsrc::XOSC_CLKSRC as _, + // Gpin0 = ClkUsbCtrlAuxsrc::CLKSRC_GPIN0 as _ , + // Gpin1 = ClkUsbCtrlAuxsrc::CLKSRC_GPIN1 as _ , +} + +pub struct UsbClkConfig { + pub src: UsbClkSrc, + pub div: u8, + pub phase: u8, +} + +#[repr(u8)] +#[non_exhaustive] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum AdcClkSrc { + PllUsb = ClkAdcCtrlAuxsrc::CLKSRC_PLL_USB as _, + PllSys = ClkAdcCtrlAuxsrc::CLKSRC_PLL_SYS as _, + Rosc = ClkAdcCtrlAuxsrc::ROSC_CLKSRC_PH as _, + Xosc = ClkAdcCtrlAuxsrc::XOSC_CLKSRC as _, + // Gpin0 = ClkAdcCtrlAuxsrc::CLKSRC_GPIN0 as _ , + // Gpin1 = ClkAdcCtrlAuxsrc::CLKSRC_GPIN1 as _ , +} + +pub struct AdcClkConfig { + pub src: AdcClkSrc, + pub div: u8, + pub phase: u8, +} + +#[repr(u8)] +#[non_exhaustive] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum RtcClkSrc { + PllUsb = ClkRtcCtrlAuxsrc::CLKSRC_PLL_USB as _, + PllSys = ClkRtcCtrlAuxsrc::CLKSRC_PLL_SYS as _, + Rosc = ClkRtcCtrlAuxsrc::ROSC_CLKSRC_PH as _, + Xosc = ClkRtcCtrlAuxsrc::XOSC_CLKSRC as _, + // Gpin0 = ClkRtcCtrlAuxsrc::CLKSRC_GPIN0 as _ , + // Gpin1 = ClkRtcCtrlAuxsrc::CLKSRC_GPIN1 as _ , +} + +pub struct RtcClkConfig { + pub src: RtcClkSrc, + pub div_int: u32, + pub div_frac: u8, + pub phase: u8, +} /// safety: must be called exactly once at bootup -pub unsafe fn init() { +pub(crate) unsafe fn init(config: ClockConfig) { // Reset everything except: // - QSPI (we're using it to run this code!) // - PLLs (it may be suicide if that's what's clocking us) // - USB, SYSCFG (breaks usb-to-swd on core1) + // - RTC (else there would be no more time...) let mut peris = reset::ALL_PERIPHERALS; peris.set_io_qspi(false); + // peris.set_io_bank0(false); // might be suicide if we're clocked from gpin peris.set_pads_qspi(false); peris.set_pll_sys(false); peris.set_pll_usb(false); + // TODO investigate if usb should be unreset here peris.set_usbctrl(false); peris.set_syscfg(false); - reset::reset(peris); - - // Remove reset from peripherals which are clocked only by clk_sys and - // clk_ref. Other peripherals stay in reset until we've configured clocks. - let mut peris = reset::ALL_PERIPHERALS; - peris.set_adc(false); peris.set_rtc(false); - peris.set_spi0(false); - peris.set_spi1(false); - peris.set_uart0(false); - peris.set_uart1(false); - peris.set_usbctrl(false); - reset::unreset_wait(peris); - - // Start tick in watchdog - // xosc 12 mhz - pac::WATCHDOG.tick().write(|w| { - w.set_cycles(XOSC_MHZ as u16); - w.set_enable(true); - }); + reset::reset(peris); // Disable resus that may be enabled from previous software let c = pac::CLOCKS; c.clk_sys_resus_ctrl() .write_value(pac::clocks::regs::ClkSysResusCtrl(0)); - // start XOSC - start_xosc(); - // Before we touch PLLs, switch sys and ref cleanly away from their aux sources. c.clk_sys_ctrl().modify(|w| w.set_src(ClkSysCtrlSrc::CLK_REF)); while c.clk_sys_selected().read() != 1 {} c.clk_ref_ctrl().modify(|w| w.set_src(ClkRefCtrlSrc::ROSC_CLKSRC_PH)); while c.clk_ref_selected().read() != 1 {} - // Configure PLLs - // REF FBDIV VCO POSTDIV - // PLL SYS: 12 / 1 = 12MHz * 125 = 1500MHZ / 6 / 2 = 125MHz - // PLL USB: 12 / 1 = 12MHz * 40 = 480 MHz / 5 / 2 = 48MHz - configure_pll(pac::PLL_SYS, 1, 1500_000_000, 6, 2); - configure_pll(pac::PLL_USB, 1, 480_000_000, 5, 2); + // Reset the PLLs + let mut peris = reset::Peripherals(0); + peris.set_pll_sys(true); + peris.set_pll_usb(true); + reset::reset(peris); + reset::unreset_wait(peris); - // CLK_REF = XOSC (12MHz) / 1 = 12MHz2Mhz + // let gpin0_freq = config.gpin0.map_or(0, |p| { + // core::mem::forget(p.1); + // p.0 + // }); + // CLOCKS.gpin0.store(gpin0_freq, Ordering::Relaxed); + // let gpin1_freq = config.gpin1.map_or(0, |p| { + // core::mem::forget(p.1); + // p.0 + // }); + // CLOCKS.gpin1.store(gpin1_freq, Ordering::Relaxed); + + let rosc_freq = match config.rosc { + Some(config) => configure_rosc(config), + None => 0, + }; + CLOCKS.rosc.store(rosc_freq, Ordering::Relaxed); + + let (xosc_freq, pll_sys_freq, pll_usb_freq) = match config.xosc { + Some(config) => { + // start XOSC + // datasheet mentions support for clock inputs into XIN, but doesn't go into + // how this is achieved. pico-sdk doesn't support this at all. + start_xosc(config.hz); + + let pll_sys_freq = match config.sys_pll { + Some(sys_pll_config) => configure_pll(pac::PLL_SYS, config.hz, sys_pll_config), + None => 0, + }; + let pll_usb_freq = match config.usb_pll { + Some(usb_pll_config) => configure_pll(pac::PLL_USB, config.hz, usb_pll_config), + None => 0, + }; + + (config.hz, pll_sys_freq, pll_usb_freq) + } + None => (0, 0, 0), + }; + CLOCKS.xosc.store(xosc_freq, Ordering::Relaxed); + CLOCKS.pll_sys.store(pll_sys_freq, Ordering::Relaxed); + CLOCKS.pll_usb.store(pll_usb_freq, Ordering::Relaxed); + + let (ref_src, ref_aux, clk_ref_freq) = { + use {ClkRefCtrlAuxsrc as Aux, ClkRefCtrlSrc as Src}; + let div = config.ref_clk.div as u32; + assert!(div >= 1 && div <= 4); + match config.ref_clk.src { + RefClkSrc::Xosc => (Src::XOSC_CLKSRC, Aux::CLKSRC_PLL_USB, xosc_freq / div), + RefClkSrc::Rosc => (Src::ROSC_CLKSRC_PH, Aux::CLKSRC_PLL_USB, rosc_freq / div), + RefClkSrc::PllUsb => (Src::CLKSRC_CLK_REF_AUX, Aux::CLKSRC_PLL_USB, pll_usb_freq / div), + // RefClkSrc::Gpin0 => (Src::CLKSRC_CLK_REF_AUX, Aux::CLKSRC_GPIN0, gpin0_freq / div), + // RefClkSrc::Gpin1 => (Src::CLKSRC_CLK_REF_AUX, Aux::CLKSRC_GPIN1, gpin1_freq / div), + } + }; + assert!(clk_ref_freq != 0); + CLOCKS.reference.store(clk_ref_freq, Ordering::Relaxed); c.clk_ref_ctrl().write(|w| { - w.set_src(ClkRefCtrlSrc::XOSC_CLKSRC); + w.set_src(ref_src); + w.set_auxsrc(ref_aux); + }); + while c.clk_ref_selected().read() != 1 << ref_src as u32 {} + c.clk_ref_div().write(|w| { + w.set_int(config.ref_clk.div); }); - while c.clk_ref_selected().read() != 1 << ClkRefCtrlSrc::XOSC_CLKSRC.0 {} - c.clk_ref_div().write(|w| w.set_int(1)); - // CLK SYS = PLL SYS (125MHz) / 1 = 125MHz + pac::WATCHDOG.tick().write(|w| { + w.set_cycles((clk_ref_freq / 1_000_000) as u16); + w.set_enable(true); + }); + + let (sys_src, sys_aux, clk_sys_freq) = { + use {ClkSysCtrlAuxsrc as Aux, ClkSysCtrlSrc as Src}; + let (src, aux, freq) = match config.sys_clk.src { + SysClkSrc::Ref => (Src::CLK_REF, Aux::CLKSRC_PLL_SYS, clk_ref_freq), + SysClkSrc::PllSys => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_PLL_SYS, pll_sys_freq), + SysClkSrc::PllUsb => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_PLL_USB, pll_usb_freq), + SysClkSrc::Rosc => (Src::CLKSRC_CLK_SYS_AUX, Aux::ROSC_CLKSRC, rosc_freq), + SysClkSrc::Xosc => (Src::CLKSRC_CLK_SYS_AUX, Aux::XOSC_CLKSRC, xosc_freq), + // SysClkSrc::Gpin0 => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_GPIN0, gpin0_freq), + // SysClkSrc::Gpin1 => (Src::CLKSRC_CLK_SYS_AUX, Aux::CLKSRC_GPIN1, gpin1_freq), + }; + assert!(config.sys_clk.div_int <= 0x1000000); + let div = config.sys_clk.div_int as u64 * 256 + config.sys_clk.div_frac as u64; + (src, aux, ((freq as u64 * 256) / div) as u32) + }; + assert!(clk_sys_freq != 0); + CLOCKS.sys.store(clk_sys_freq, Ordering::Relaxed); + if sys_src != ClkSysCtrlSrc::CLK_REF { + c.clk_sys_ctrl().write(|w| w.set_src(ClkSysCtrlSrc::CLK_REF)); + while c.clk_sys_selected().read() != 1 << ClkSysCtrlSrc::CLK_REF as u32 {} + } c.clk_sys_ctrl().write(|w| { - w.set_src(ClkSysCtrlSrc::CLK_REF); + w.set_auxsrc(sys_aux); + w.set_src(sys_src); }); - while c.clk_sys_selected().read() != 1 << ClkSysCtrlSrc::CLK_REF.0 {} - c.clk_sys_div().write(|w| w.set_int(1)); - c.clk_sys_ctrl().write(|w| { - w.set_auxsrc(ClkSysCtrlAuxsrc::CLKSRC_PLL_SYS); - w.set_src(ClkSysCtrlSrc::CLKSRC_CLK_SYS_AUX); - }); - while c.clk_sys_selected().read() != 1 << ClkSysCtrlSrc::CLKSRC_CLK_SYS_AUX.0 {} - - // CLK USB = PLL USB (48MHz) / 1 = 48MHz - c.clk_usb_div().write(|w| w.set_int(1)); - c.clk_usb_ctrl().write(|w| { - w.set_enable(true); - w.set_auxsrc(ClkUsbCtrlAuxsrc::CLKSRC_PLL_USB); + while c.clk_sys_selected().read() != 1 << sys_src as u32 {} + c.clk_sys_div().write(|w| { + w.set_int(config.sys_clk.div_int); + w.set_frac(config.sys_clk.div_frac); }); - // CLK ADC = PLL USB (48MHZ) / 1 = 48MHz - c.clk_adc_div().write(|w| w.set_int(1)); - c.clk_adc_ctrl().write(|w| { - w.set_enable(true); - w.set_auxsrc(ClkAdcCtrlAuxsrc::CLKSRC_PLL_USB); - }); + let mut peris = reset::ALL_PERIPHERALS; - // CLK RTC = PLL USB (48MHz) / 1024 = 46875Hz - c.clk_rtc_ctrl().modify(|w| { - w.set_enable(false); - }); - c.clk_rtc_div().write(|w| w.set_int(1024)); - c.clk_rtc_ctrl().write(|w| { - w.set_enable(true); - w.set_auxsrc(ClkRtcCtrlAuxsrc::CLKSRC_PLL_USB); - }); + if let Some(src) = config.peri_clk_src { + c.clk_peri_ctrl().write(|w| { + w.set_enable(true); + w.set_auxsrc(ClkPeriCtrlAuxsrc::from_bits(src as _)); + }); + let peri_freq = match src { + PeriClkSrc::Sys => clk_sys_freq, + PeriClkSrc::PllSys => pll_sys_freq, + PeriClkSrc::PllUsb => pll_usb_freq, + PeriClkSrc::Rosc => rosc_freq, + PeriClkSrc::Xosc => xosc_freq, + // PeriClkSrc::Gpin0 => gpin0_freq, + // PeriClkSrc::Gpin1 => gpin1_freq, + }; + assert!(peri_freq != 0); + CLOCKS.peri.store(peri_freq, Ordering::Relaxed); + } else { + peris.set_spi0(false); + peris.set_spi1(false); + peris.set_uart0(false); + peris.set_uart1(false); + CLOCKS.peri.store(0, Ordering::Relaxed); + } - // CLK PERI = clk_sys. Used as reference clock for Peripherals. No dividers so just select and enable - // Normally choose clk_sys or clk_usb - c.clk_peri_ctrl().write(|w| { - w.set_enable(true); - w.set_auxsrc(ClkPeriCtrlAuxsrc::CLK_SYS); - }); + if let Some(conf) = config.usb_clk { + c.clk_usb_div().write(|w| w.set_int(conf.div)); + c.clk_usb_ctrl().write(|w| { + w.set_phase(conf.phase); + w.set_enable(true); + w.set_auxsrc(ClkUsbCtrlAuxsrc::from_bits(conf.src as _)); + }); + let usb_freq = match conf.src { + UsbClkSrc::PllUsb => pll_usb_freq, + UsbClkSrc::PllSys => pll_sys_freq, + UsbClkSrc::Rosc => rosc_freq, + UsbClkSrc::Xosc => xosc_freq, + // UsbClkSrc::Gpin0 => gpin0_freq, + // UsbClkSrc::Gpin1 => gpin1_freq, + }; + assert!(usb_freq != 0); + assert!(conf.div >= 1 && conf.div <= 4); + CLOCKS.usb.store(usb_freq / conf.div as u32, Ordering::Relaxed); + } else { + peris.set_usbctrl(false); + CLOCKS.usb.store(0, Ordering::Relaxed); + } + + if let Some(conf) = config.adc_clk { + c.clk_adc_div().write(|w| w.set_int(conf.div)); + c.clk_adc_ctrl().write(|w| { + w.set_phase(conf.phase); + w.set_enable(true); + w.set_auxsrc(ClkAdcCtrlAuxsrc::from_bits(conf.src as _)); + }); + let adc_in_freq = match conf.src { + AdcClkSrc::PllUsb => pll_usb_freq, + AdcClkSrc::PllSys => pll_sys_freq, + AdcClkSrc::Rosc => rosc_freq, + AdcClkSrc::Xosc => xosc_freq, + // AdcClkSrc::Gpin0 => gpin0_freq, + // AdcClkSrc::Gpin1 => gpin1_freq, + }; + assert!(adc_in_freq != 0); + assert!(conf.div >= 1 && conf.div <= 4); + CLOCKS.adc.store(adc_in_freq / conf.div as u32, Ordering::Relaxed); + } else { + peris.set_adc(false); + CLOCKS.adc.store(0, Ordering::Relaxed); + } + + if let Some(conf) = config.rtc_clk { + c.clk_rtc_div().write(|w| { + w.set_int(conf.div_int); + w.set_frac(conf.div_frac); + }); + c.clk_rtc_ctrl().write(|w| { + w.set_phase(conf.phase); + w.set_enable(true); + w.set_auxsrc(ClkRtcCtrlAuxsrc::from_bits(conf.src as _)); + }); + let rtc_in_freq = match conf.src { + RtcClkSrc::PllUsb => pll_usb_freq, + RtcClkSrc::PllSys => pll_sys_freq, + RtcClkSrc::Rosc => rosc_freq, + RtcClkSrc::Xosc => xosc_freq, + // RtcClkSrc::Gpin0 => gpin0_freq, + // RtcClkSrc::Gpin1 => gpin1_freq, + }; + assert!(rtc_in_freq != 0); + assert!(config.sys_clk.div_int <= 0x1000000); + CLOCKS.rtc.store( + ((rtc_in_freq as u64 * 256) / (conf.div_int as u64 * 256 + conf.div_frac as u64)) as u16, + Ordering::Relaxed, + ); + } else { + peris.set_rtc(false); + CLOCKS.rtc.store(0, Ordering::Relaxed); + } // Peripheral clocks should now all be running - let peris = reset::ALL_PERIPHERALS; reset::unreset_wait(peris); } -pub(crate) fn _clk_sys_freq() -> u32 { - 125_000_000 +fn configure_rosc(config: RoscConfig) -> u32 { + let p = pac::ROSC; + + p.freqa().write(|w| { + w.set_passwd(pac::rosc::vals::Passwd::PASS); + w.set_ds0(config.drive_strength[0]); + w.set_ds1(config.drive_strength[1]); + w.set_ds2(config.drive_strength[2]); + w.set_ds3(config.drive_strength[3]); + }); + + p.freqb().write(|w| { + w.set_passwd(pac::rosc::vals::Passwd::PASS); + w.set_ds4(config.drive_strength[4]); + w.set_ds5(config.drive_strength[5]); + w.set_ds6(config.drive_strength[6]); + w.set_ds7(config.drive_strength[7]); + }); + + p.div().write(|w| { + w.set_div(pac::rosc::vals::Div(config.div + pac::rosc::vals::Div::PASS.0)); + }); + + p.ctrl().write(|w| { + w.set_enable(pac::rosc::vals::Enable::ENABLE); + w.set_freq_range(pac::rosc::vals::FreqRange(config.range as u16)); + }); + + config.hz } -pub(crate) fn clk_peri_freq() -> u32 { - 125_000_000 +pub fn rosc_freq() -> u32 { + CLOCKS.rosc.load(Ordering::Relaxed) } -pub(crate) fn _clk_rtc_freq() -> u32 { - 46875 +pub fn xosc_freq() -> u32 { + CLOCKS.xosc.load(Ordering::Relaxed) } -unsafe fn start_xosc() { - const XOSC_MHZ: u32 = 12; +// pub fn gpin0_freq() -> u32 { +// CLOCKS.gpin0.load(Ordering::Relaxed) +// } +// pub fn gpin1_freq() -> u32 { +// CLOCKS.gpin1.load(Ordering::Relaxed) +// } + +pub fn pll_sys_freq() -> u32 { + CLOCKS.pll_sys.load(Ordering::Relaxed) +} + +pub fn pll_usb_freq() -> u32 { + CLOCKS.pll_usb.load(Ordering::Relaxed) +} + +pub fn clk_sys_freq() -> u32 { + CLOCKS.sys.load(Ordering::Relaxed) +} + +pub fn clk_ref_freq() -> u32 { + CLOCKS.reference.load(Ordering::Relaxed) +} + +pub fn clk_peri_freq() -> u32 { + CLOCKS.peri.load(Ordering::Relaxed) +} + +pub fn clk_usb_freq() -> u32 { + CLOCKS.usb.load(Ordering::Relaxed) +} + +pub fn clk_adc_freq() -> u32 { + CLOCKS.adc.load(Ordering::Relaxed) +} + +pub fn clk_rtc_freq() -> u16 { + CLOCKS.rtc.load(Ordering::Relaxed) +} + +fn start_xosc(crystal_hz: u32) { pac::XOSC .ctrl() .write(|w| w.set_freq_range(pac::xosc::vals::CtrlFreqRange::_1_15MHZ)); - let startup_delay = (((XOSC_MHZ * 1_000_000) / 1000) + 128) / 256; + let startup_delay = ((crystal_hz / 1000) + 128) / 256; pac::XOSC.startup().write(|w| w.set_delay(startup_delay as u16)); pac::XOSC.ctrl().write(|w| { w.set_freq_range(pac::xosc::vals::CtrlFreqRange::_1_15MHZ); @@ -141,58 +636,240 @@ unsafe fn start_xosc() { while !pac::XOSC.status().read().stable() {} } -unsafe fn configure_pll(p: pac::pll::Pll, refdiv: u32, vco_freq: u32, post_div1: u8, post_div2: u8) { - let ref_freq = XOSC_MHZ * 1_000_000 / refdiv; - - let fbdiv = vco_freq / ref_freq; - assert!(fbdiv >= 16 && fbdiv <= 320); - assert!(post_div1 >= 1 && post_div1 <= 7); - assert!(post_div2 >= 1 && post_div2 <= 7); - assert!(post_div2 <= post_div1); - assert!(ref_freq <= (vco_freq / 16)); - - // do not disrupt PLL that is already correctly configured and operating - let cs = p.cs().read(); - let prim = p.prim().read(); - if cs.lock() - && cs.refdiv() == refdiv as _ - && p.fbdiv_int().read().fbdiv_int() == fbdiv as _ - && prim.postdiv1() == post_div1 - && prim.postdiv2() == post_div2 - { - return; - } - - // Reset it - let mut peris = reset::Peripherals(0); - match p { - pac::PLL_SYS => peris.set_pll_sys(true), - pac::PLL_USB => peris.set_pll_usb(true), - _ => unreachable!(), - } - reset::reset(peris); - reset::unreset_wait(peris); +#[inline(always)] +fn configure_pll(p: pac::pll::Pll, input_freq: u32, config: PllConfig) -> u32 { + let ref_freq = input_freq / config.refdiv as u32; + assert!(config.fbdiv >= 16 && config.fbdiv <= 320); + assert!(config.post_div1 >= 1 && config.post_div1 <= 7); + assert!(config.post_div2 >= 1 && config.post_div2 <= 7); + assert!(config.refdiv >= 1 && config.refdiv <= 63); + assert!(ref_freq >= 5_000_000 && ref_freq <= 800_000_000); + let vco_freq = ref_freq.saturating_mul(config.fbdiv as u32); + assert!(vco_freq >= 750_000_000 && vco_freq <= 1800_000_000); // Load VCO-related dividers before starting VCO - p.cs().write(|w| w.set_refdiv(refdiv as _)); - p.fbdiv_int().write(|w| w.set_fbdiv_int(fbdiv as _)); + p.cs().write(|w| w.set_refdiv(config.refdiv as _)); + p.fbdiv_int().write(|w| w.set_fbdiv_int(config.fbdiv)); // Turn on PLL - p.pwr().modify(|w| { + let pwr = p.pwr().write(|w| { + w.set_dsmpd(true); // "nothing is achieved by setting this low" w.set_pd(false); w.set_vcopd(false); w.set_postdivpd(true); + *w }); // Wait for PLL to lock while !p.cs().read().lock() {} - // Wait for PLL to lock + // Set post-dividers p.prim().write(|w| { - w.set_postdiv1(post_div1); - w.set_postdiv2(post_div2); + w.set_postdiv1(config.post_div1); + w.set_postdiv2(config.post_div2); }); // Turn on post divider - p.pwr().modify(|w| w.set_postdivpd(false)); + p.pwr().write(|w| { + *w = pwr; + w.set_postdivpd(false); + }); + + vco_freq / ((config.post_div1 * config.post_div2) as u32) +} + +pub trait GpinPin: crate::gpio::Pin { + const NR: usize; +} + +macro_rules! impl_gpinpin { + ($name:ident, $pin_num:expr, $gpin_num:expr) => { + impl GpinPin for crate::peripherals::$name { + const NR: usize = $gpin_num; + } + }; +} + +impl_gpinpin!(PIN_20, 20, 0); +impl_gpinpin!(PIN_22, 22, 1); + +pub struct Gpin<'d, T: Pin> { + gpin: PeripheralRef<'d, AnyPin>, + _phantom: PhantomData, +} + +impl<'d, T: Pin> Gpin<'d, T> { + pub fn new(gpin: impl Peripheral

+ 'd) -> Gpin<'d, P> { + into_ref!(gpin); + + gpin.io().ctrl().write(|w| w.set_funcsel(0x08)); + + Gpin { + gpin: gpin.map_into(), + _phantom: PhantomData, + } + } + + // fn map_into(self) -> Gpin<'d, AnyPin> { + // unsafe { core::mem::transmute(self) } + // } +} + +impl<'d, T: Pin> Drop for Gpin<'d, T> { + fn drop(&mut self) { + self.gpin + .io() + .ctrl() + .write(|w| w.set_funcsel(pac::io::vals::Gpio0ctrlFuncsel::NULL as _)); + } +} + +pub trait GpoutPin: crate::gpio::Pin { + fn number(&self) -> usize; +} + +macro_rules! impl_gpoutpin { + ($name:ident, $gpout_num:expr) => { + impl GpoutPin for crate::peripherals::$name { + fn number(&self) -> usize { + $gpout_num + } + } + }; +} + +impl_gpoutpin!(PIN_21, 0); +impl_gpoutpin!(PIN_23, 1); +impl_gpoutpin!(PIN_24, 2); +impl_gpoutpin!(PIN_25, 3); + +#[repr(u8)] +pub enum GpoutSrc { + PllSys = ClkGpoutCtrlAuxsrc::CLKSRC_PLL_SYS as _, + // Gpin0 = ClkGpoutCtrlAuxsrc::CLKSRC_GPIN0 as _ , + // Gpin1 = ClkGpoutCtrlAuxsrc::CLKSRC_GPIN1 as _ , + PllUsb = ClkGpoutCtrlAuxsrc::CLKSRC_PLL_USB as _, + Rosc = ClkGpoutCtrlAuxsrc::ROSC_CLKSRC as _, + Xosc = ClkGpoutCtrlAuxsrc::XOSC_CLKSRC as _, + Sys = ClkGpoutCtrlAuxsrc::CLK_SYS as _, + Usb = ClkGpoutCtrlAuxsrc::CLK_USB as _, + Adc = ClkGpoutCtrlAuxsrc::CLK_ADC as _, + Rtc = ClkGpoutCtrlAuxsrc::CLK_RTC as _, + Ref = ClkGpoutCtrlAuxsrc::CLK_REF as _, +} + +pub struct Gpout<'d, T: GpoutPin> { + gpout: PeripheralRef<'d, T>, +} + +impl<'d, T: GpoutPin> Gpout<'d, T> { + pub fn new(gpout: impl Peripheral

+ 'd) -> Self { + into_ref!(gpout); + + gpout.io().ctrl().write(|w| w.set_funcsel(0x08)); + + Self { gpout } + } + + pub fn set_div(&self, int: u32, frac: u8) { + let c = pac::CLOCKS; + c.clk_gpout_div(self.gpout.number()).write(|w| { + w.set_int(int); + w.set_frac(frac); + }); + } + + pub fn set_src(&self, src: GpoutSrc) { + let c = pac::CLOCKS; + c.clk_gpout_ctrl(self.gpout.number()).modify(|w| { + w.set_auxsrc(ClkGpoutCtrlAuxsrc::from_bits(src as _)); + }); + } + + pub fn enable(&self) { + let c = pac::CLOCKS; + c.clk_gpout_ctrl(self.gpout.number()).modify(|w| { + w.set_enable(true); + }); + } + + pub fn disable(&self) { + let c = pac::CLOCKS; + c.clk_gpout_ctrl(self.gpout.number()).modify(|w| { + w.set_enable(false); + }); + } + + pub fn get_freq(&self) -> u32 { + let c = pac::CLOCKS; + let src = c.clk_gpout_ctrl(self.gpout.number()).read().auxsrc(); + + let base = match src { + ClkGpoutCtrlAuxsrc::CLKSRC_PLL_SYS => pll_sys_freq(), + // ClkGpoutCtrlAuxsrc::CLKSRC_GPIN0 => gpin0_freq(), + // ClkGpoutCtrlAuxsrc::CLKSRC_GPIN1 => gpin1_freq(), + ClkGpoutCtrlAuxsrc::CLKSRC_PLL_USB => pll_usb_freq(), + ClkGpoutCtrlAuxsrc::ROSC_CLKSRC => rosc_freq(), + ClkGpoutCtrlAuxsrc::XOSC_CLKSRC => xosc_freq(), + ClkGpoutCtrlAuxsrc::CLK_SYS => clk_sys_freq(), + ClkGpoutCtrlAuxsrc::CLK_USB => clk_usb_freq(), + ClkGpoutCtrlAuxsrc::CLK_ADC => clk_adc_freq(), + ClkGpoutCtrlAuxsrc::CLK_RTC => clk_rtc_freq() as _, + ClkGpoutCtrlAuxsrc::CLK_REF => clk_ref_freq(), + _ => unreachable!(), + }; + + let div = c.clk_gpout_div(self.gpout.number()).read(); + let int = if div.int() == 0 { 65536 } else { div.int() } as u64; + let frac = div.frac() as u64; + + ((base as u64 * 256) / (int * 256 + frac)) as u32 + } +} + +impl<'d, T: GpoutPin> Drop for Gpout<'d, T> { + fn drop(&mut self) { + self.disable(); + self.gpout + .io() + .ctrl() + .write(|w| w.set_funcsel(pac::io::vals::Gpio0ctrlFuncsel::NULL as _)); + } +} + +/// Random number generator based on the ROSC RANDOMBIT register. +/// +/// This will not produce random values if the ROSC is stopped or run at some +/// harmonic of the bus frequency. With default clock settings these are not +/// issues. +pub struct RoscRng; + +impl RoscRng { + fn next_u8() -> u8 { + let random_reg = pac::ROSC.randombit(); + let mut acc = 0; + for _ in 0..u8::BITS { + acc <<= 1; + acc |= random_reg.read().randombit() as u8; + } + acc + } +} + +impl rand_core::RngCore for RoscRng { + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { + Ok(self.fill_bytes(dest)) + } + + fn next_u32(&mut self) -> u32 { + rand_core::impls::next_u32_via_fill(self) + } + + fn next_u64(&mut self) -> u64 { + rand_core::impls::next_u64_via_fill(self) + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + dest.fill_with(Self::next_u8) + } } diff --git a/embassy-rp/src/critical_section_impl.rs b/embassy-rp/src/critical_section_impl.rs new file mode 100644 index 000000000..d233e6fab --- /dev/null +++ b/embassy-rp/src/critical_section_impl.rs @@ -0,0 +1,137 @@ +use core::sync::atomic::{AtomicU8, Ordering}; + +use crate::pac; + +struct RpSpinlockCs; +critical_section::set_impl!(RpSpinlockCs); + +/// Marker value to indicate no-one has the lock. +/// +/// Initialising `LOCK_OWNER` to 0 means cheaper static initialisation so it's the best choice +const LOCK_UNOWNED: u8 = 0; + +/// Indicates which core owns the lock so that we can call critical_section recursively. +/// +/// 0 = no one has the lock, 1 = core0 has the lock, 2 = core1 has the lock +static LOCK_OWNER: AtomicU8 = AtomicU8::new(LOCK_UNOWNED); + +/// Marker value to indicate that we already owned the lock when we started the `critical_section`. +/// +/// Since we can't take the spinlock when we already have it, we need some other way to keep track of `critical_section` ownership. +/// `critical_section` provides a token for communicating between `acquire` and `release` so we use that. +/// If we're the outermost call to `critical_section` we use the values 0 and 1 to indicate we should release the spinlock and set the interrupts back to disabled and enabled, respectively. +/// The value 2 indicates that we aren't the outermost call, and should not release the spinlock or re-enable interrupts in `release` +const LOCK_ALREADY_OWNED: u8 = 2; + +unsafe impl critical_section::Impl for RpSpinlockCs { + unsafe fn acquire() -> u8 { + RpSpinlockCs::acquire() + } + + unsafe fn release(token: u8) { + RpSpinlockCs::release(token); + } +} + +impl RpSpinlockCs { + unsafe fn acquire() -> u8 { + // Store the initial interrupt state and current core id in stack variables + let interrupts_active = cortex_m::register::primask::read().is_active(); + // We reserved 0 as our `LOCK_UNOWNED` value, so add 1 to core_id so we get 1 for core0, 2 for core1. + let core = pac::SIO.cpuid().read() as u8 + 1; + // Do we already own the spinlock? + if LOCK_OWNER.load(Ordering::Acquire) == core { + // We already own the lock, so we must have called acquire within a critical_section. + // Return the magic inner-loop value so that we know not to re-enable interrupts in release() + LOCK_ALREADY_OWNED + } else { + // Spin until we get the lock + loop { + // Need to disable interrupts to ensure that we will not deadlock + // if an interrupt enters critical_section::Impl after we acquire the lock + cortex_m::interrupt::disable(); + // Ensure the compiler doesn't re-order accesses and violate safety here + core::sync::atomic::compiler_fence(Ordering::SeqCst); + // Read the spinlock reserved for `critical_section` + if let Some(lock) = Spinlock31::try_claim() { + // We just acquired the lock. + // 1. Forget it, so we don't immediately unlock + core::mem::forget(lock); + // 2. Store which core we are so we can tell if we're called recursively + LOCK_OWNER.store(core, Ordering::Relaxed); + break; + } + // We didn't get the lock, enable interrupts if they were enabled before we started + if interrupts_active { + cortex_m::interrupt::enable(); + } + } + // If we broke out of the loop we have just acquired the lock + // As the outermost loop, we want to return the interrupt status to restore later + interrupts_active as _ + } + } + + unsafe fn release(token: u8) { + // Did we already own the lock at the start of the `critical_section`? + if token != LOCK_ALREADY_OWNED { + // No, it wasn't owned at the start of this `critical_section`, so this core no longer owns it. + // Set `LOCK_OWNER` back to `LOCK_UNOWNED` to ensure the next critical section tries to obtain the spinlock instead + LOCK_OWNER.store(LOCK_UNOWNED, Ordering::Relaxed); + // Ensure the compiler doesn't re-order accesses and violate safety here + core::sync::atomic::compiler_fence(Ordering::SeqCst); + // Release the spinlock to allow others to enter critical_section again + Spinlock31::release(); + // Re-enable interrupts if they were enabled when we first called acquire() + // We only do this on the outermost `critical_section` to ensure interrupts stay disabled + // for the whole time that we have the lock + if token != 0 { + cortex_m::interrupt::enable(); + } + } + } +} + +pub struct Spinlock(core::marker::PhantomData<()>) +where + Spinlock: SpinlockValid; + +impl Spinlock +where + Spinlock: SpinlockValid, +{ + /// Try to claim the spinlock. Will return `Some(Self)` if the lock is obtained, and `None` if the lock is + /// already in use somewhere else. + pub fn try_claim() -> Option { + let lock = pac::SIO.spinlock(N).read(); + if lock > 0 { + Some(Self(core::marker::PhantomData)) + } else { + None + } + } + + /// Clear a locked spin-lock. + /// + /// # Safety + /// + /// Only call this function if you hold the spin-lock. + pub unsafe fn release() { + // Write (any value): release the lock + pac::SIO.spinlock(N).write_value(1); + } +} + +impl Drop for Spinlock +where + Spinlock: SpinlockValid, +{ + fn drop(&mut self) { + // This is safe because we own the object, and hence hold the lock. + unsafe { Self::release() } + } +} + +pub(crate) type Spinlock31 = Spinlock<31>; +pub trait SpinlockValid {} +impl SpinlockValid for Spinlock<31> {} diff --git a/embassy-rp/src/dma.rs b/embassy-rp/src/dma.rs index 42c4fd13e..1a458778c 100644 --- a/embassy-rp/src/dma.rs +++ b/embassy-rp/src/dma.rs @@ -1,82 +1,283 @@ +//! Direct Memory Access (DMA) +use core::future::Future; +use core::pin::Pin; use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::{Context, Poll}; -use embassy_hal_common::impl_peripheral; +use embassy_hal_common::{impl_peripheral, into_ref, Peripheral, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; +use pac::dma::vals::DataSize; +use crate::interrupt::InterruptExt; use crate::pac::dma::vals; -use crate::{pac, peripherals}; +use crate::{interrupt, pac, peripherals}; -pub struct Dma { - _inner: T, +#[cfg(feature = "rt")] +#[interrupt] +fn DMA_IRQ_0() { + let ints0 = pac::DMA.ints0().read().ints0(); + for channel in 0..CHANNEL_COUNT { + let ctrl_trig = pac::DMA.ch(channel).ctrl_trig().read(); + if ctrl_trig.ahb_error() { + panic!("DMA: error on DMA_0 channel {}", channel); + } + + if ints0 & (1 << channel) == (1 << channel) { + CHANNEL_WAKERS[channel].wake(); + } + } + pac::DMA.ints0().write(|w| w.set_ints0(ints0)); } -impl Dma { - pub fn copy(inner: T, from: &[u32], to: &mut [u32]) { - assert!(from.len() == to.len()); +pub(crate) unsafe fn init() { + interrupt::DMA_IRQ_0.disable(); + interrupt::DMA_IRQ_0.set_priority(interrupt::Priority::P3); - unsafe { - let p = inner.regs(); + pac::DMA.inte0().write(|w| w.set_inte0(0xFFFF)); - p.read_addr().write_value(from.as_ptr() as u32); - p.write_addr().write_value(to.as_mut_ptr() as u32); - p.trans_count().write_value(from.len() as u32); + interrupt::DMA_IRQ_0.enable(); +} - compiler_fence(Ordering::SeqCst); +pub unsafe fn read<'a, C: Channel, W: Word>( + ch: impl Peripheral

+ 'a, + from: *const W, + to: *mut [W], + dreq: u8, +) -> Transfer<'a, C> { + let (to_ptr, len) = crate::dma::slice_ptr_parts(to); + copy_inner( + ch, + from as *const u32, + to_ptr as *mut u32, + len, + W::size(), + false, + true, + dreq, + ) +} - p.ctrl_trig().write(|w| { - w.set_data_size(vals::DataSize::SIZE_WORD); - w.set_incr_read(true); - w.set_incr_write(true); - w.set_chain_to(inner.number()); - w.set_en(true); - }); +pub unsafe fn write<'a, C: Channel, W: Word>( + ch: impl Peripheral

+ 'a, + from: *const [W], + to: *mut W, + dreq: u8, +) -> Transfer<'a, C> { + let (from_ptr, len) = crate::dma::slice_ptr_parts(from); + copy_inner( + ch, + from_ptr as *const u32, + to as *mut u32, + len, + W::size(), + true, + false, + dreq, + ) +} - while p.ctrl_trig().read().busy() {} +static DUMMY: u32 = 0; - compiler_fence(Ordering::SeqCst); +pub unsafe fn write_repeated<'a, C: Channel, W: Word>( + ch: impl Peripheral

+ 'a, + to: *mut W, + len: usize, + dreq: u8, +) -> Transfer<'a, C> { + copy_inner( + ch, + &DUMMY as *const u32, + to as *mut u32, + len, + W::size(), + false, + false, + dreq, + ) +} + +pub unsafe fn copy<'a, C: Channel, W: Word>( + ch: impl Peripheral

+ 'a, + from: &[W], + to: &mut [W], +) -> Transfer<'a, C> { + let (from_ptr, from_len) = crate::dma::slice_ptr_parts(from); + let (to_ptr, to_len) = crate::dma::slice_ptr_parts_mut(to); + assert_eq!(from_len, to_len); + copy_inner( + ch, + from_ptr as *const u32, + to_ptr as *mut u32, + from_len, + W::size(), + true, + true, + vals::TreqSel::PERMANENT.0, + ) +} + +fn copy_inner<'a, C: Channel>( + ch: impl Peripheral

+ 'a, + from: *const u32, + to: *mut u32, + len: usize, + data_size: DataSize, + incr_read: bool, + incr_write: bool, + dreq: u8, +) -> Transfer<'a, C> { + into_ref!(ch); + + let p = ch.regs(); + + p.read_addr().write_value(from as u32); + p.write_addr().write_value(to as u32); + p.trans_count().write_value(len as u32); + + compiler_fence(Ordering::SeqCst); + + p.ctrl_trig().write(|w| { + // TODO: Add all DREQ options to pac vals::TreqSel, and use + // `set_treq:sel` + w.0 = ((dreq as u32) & 0x3f) << 15usize; + w.set_data_size(data_size); + w.set_incr_read(incr_read); + w.set_incr_write(incr_write); + w.set_chain_to(ch.number()); + w.set_en(true); + }); + + compiler_fence(Ordering::SeqCst); + Transfer::new(ch) +} + +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Transfer<'a, C: Channel> { + channel: PeripheralRef<'a, C>, +} + +impl<'a, C: Channel> Transfer<'a, C> { + pub(crate) fn new(channel: impl Peripheral

+ 'a) -> Self { + into_ref!(channel); + + Self { channel } + } +} + +impl<'a, C: Channel> Drop for Transfer<'a, C> { + fn drop(&mut self) { + let p = self.channel.regs(); + pac::DMA + .chan_abort() + .modify(|m| m.set_chan_abort(1 << self.channel.number())); + while p.ctrl_trig().read().busy() {} + } +} + +impl<'a, C: Channel> Unpin for Transfer<'a, C> {} +impl<'a, C: Channel> Future for Transfer<'a, C> { + type Output = (); + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // We need to register/re-register the waker for each poll because any + // calls to wake will deregister the waker. + CHANNEL_WAKERS[self.channel.number() as usize].register(cx.waker()); + + if self.channel.regs().ctrl_trig().read().busy() { + Poll::Pending + } else { + Poll::Ready(()) } } } -pub struct NoDma; - -impl_peripheral!(NoDma); +pub(crate) const CHANNEL_COUNT: usize = 12; +const NEW_AW: AtomicWaker = AtomicWaker::new(); +static CHANNEL_WAKERS: [AtomicWaker; CHANNEL_COUNT] = [NEW_AW; CHANNEL_COUNT]; mod sealed { - use super::*; + pub trait Channel {} - pub trait Channel { - fn number(&self) -> u8; + pub trait Word {} +} - fn regs(&self) -> pac::dma::Channel { - pac::DMA.ch(self.number() as _) - } +pub trait Channel: Peripheral

+ sealed::Channel + Into + Sized + 'static { + fn number(&self) -> u8; + + fn regs(&self) -> pac::dma::Channel { + pac::DMA.ch(self.number() as _) + } + + fn degrade(self) -> AnyChannel { + AnyChannel { number: self.number() } } } -pub trait Channel: sealed::Channel {} +pub trait Word: sealed::Word { + fn size() -> vals::DataSize; +} + +impl sealed::Word for u8 {} +impl Word for u8 { + fn size() -> vals::DataSize { + vals::DataSize::SIZE_BYTE + } +} + +impl sealed::Word for u16 {} +impl Word for u16 { + fn size() -> vals::DataSize { + vals::DataSize::SIZE_HALFWORD + } +} + +impl sealed::Word for u32 {} +impl Word for u32 { + fn size() -> vals::DataSize { + vals::DataSize::SIZE_WORD + } +} pub struct AnyChannel { number: u8, } -impl Channel for AnyChannel {} -impl sealed::Channel for AnyChannel { +impl_peripheral!(AnyChannel); + +impl sealed::Channel for AnyChannel {} +impl Channel for AnyChannel { fn number(&self) -> u8 { self.number } } macro_rules! channel { - ($type:ident, $num:expr) => { - impl Channel for peripherals::$type {} - impl sealed::Channel for peripherals::$type { + ($name:ident, $num:expr) => { + impl sealed::Channel for peripherals::$name {} + impl Channel for peripherals::$name { fn number(&self) -> u8 { $num } } + + impl From for crate::dma::AnyChannel { + fn from(val: peripherals::$name) -> Self { + crate::dma::Channel::degrade(val) + } + } }; } +// TODO: replace transmutes with core::ptr::metadata once it's stable +#[allow(unused)] +pub(crate) fn slice_ptr_parts(slice: *const [T]) -> (usize, usize) { + unsafe { core::mem::transmute(slice) } +} + +#[allow(unused)] +pub(crate) fn slice_ptr_parts_mut(slice: *mut [T]) -> (usize, usize) { + unsafe { core::mem::transmute(slice) } +} + channel!(DMA_CH0, 0); channel!(DMA_CH1, 1); channel!(DMA_CH2, 2); diff --git a/embassy-rp/src/flash.rs b/embassy-rp/src/flash.rs new file mode 100644 index 000000000..96d2d4541 --- /dev/null +++ b/embassy-rp/src/flash.rs @@ -0,0 +1,707 @@ +use core::marker::PhantomData; + +use embassy_hal_common::Peripheral; +use embedded_storage::nor_flash::{ + check_erase, check_read, check_write, ErrorType, MultiwriteNorFlash, NorFlash, NorFlashError, NorFlashErrorKind, + ReadNorFlash, +}; + +use crate::pac; +use crate::peripherals::FLASH; + +pub const FLASH_BASE: *const u32 = 0x10000000 as _; + +// If running from RAM, we might have no boot2. Use bootrom `flash_enter_cmd_xip` instead. +// TODO: when run-from-ram is set, completely skip the "pause cores and jumpp to RAM" dance. +pub const USE_BOOT2: bool = !cfg!(feature = "run-from-ram"); + +// **NOTE**: +// +// These limitations are currently enforced because of using the +// RP2040 boot-rom flash functions, that are optimized for flash compatibility +// rather than performance. +pub const PAGE_SIZE: usize = 256; +pub const WRITE_SIZE: usize = 1; +pub const READ_SIZE: usize = 1; +pub const ERASE_SIZE: usize = 4096; + +/// Error type for NVMC operations. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// Operation using a location not in flash. + OutOfBounds, + /// Unaligned operation or using unaligned buffers. + Unaligned, + InvalidCore, + Other, +} + +impl From for Error { + fn from(e: NorFlashErrorKind) -> Self { + match e { + NorFlashErrorKind::NotAligned => Self::Unaligned, + NorFlashErrorKind::OutOfBounds => Self::OutOfBounds, + _ => Self::Other, + } + } +} + +impl NorFlashError for Error { + fn kind(&self) -> NorFlashErrorKind { + match self { + Self::OutOfBounds => NorFlashErrorKind::OutOfBounds, + Self::Unaligned => NorFlashErrorKind::NotAligned, + _ => NorFlashErrorKind::Other, + } + } +} + +pub struct Flash<'d, T: Instance, const FLASH_SIZE: usize>(PhantomData<&'d mut T>); + +impl<'d, T: Instance, const FLASH_SIZE: usize> Flash<'d, T, FLASH_SIZE> { + pub fn new(_flash: impl Peripheral

+ 'd) -> Self { + Self(PhantomData) + } + + pub fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Error> { + trace!( + "Reading from 0x{:x} to 0x{:x}", + FLASH_BASE as u32 + offset, + FLASH_BASE as u32 + offset + bytes.len() as u32 + ); + check_read(self, offset, bytes.len())?; + + let flash_data = unsafe { core::slice::from_raw_parts((FLASH_BASE as u32 + offset) as *const u8, bytes.len()) }; + + bytes.copy_from_slice(flash_data); + Ok(()) + } + + pub fn capacity(&self) -> usize { + FLASH_SIZE + } + + pub fn erase(&mut self, from: u32, to: u32) -> Result<(), Error> { + check_erase(self, from, to)?; + + trace!( + "Erasing from 0x{:x} to 0x{:x}", + FLASH_BASE as u32 + from, + FLASH_BASE as u32 + to + ); + + let len = to - from; + + unsafe { self.in_ram(|| ram_helpers::flash_range_erase(from, len))? }; + + Ok(()) + } + + pub fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Error> { + check_write(self, offset, bytes.len())?; + + trace!("Writing {:?} bytes to 0x{:x}", bytes.len(), FLASH_BASE as u32 + offset); + + let end_offset = offset as usize + bytes.len(); + + let padded_offset = (offset as *const u8).align_offset(PAGE_SIZE); + let start_padding = core::cmp::min(padded_offset, bytes.len()); + + // Pad in the beginning + if start_padding > 0 { + let start = PAGE_SIZE - padded_offset; + let end = start + start_padding; + + let mut pad_buf = [0xFF_u8; PAGE_SIZE]; + pad_buf[start..end].copy_from_slice(&bytes[..start_padding]); + + let unaligned_offset = offset as usize - start; + + unsafe { self.in_ram(|| ram_helpers::flash_range_program(unaligned_offset as u32, &pad_buf))? } + } + + let remaining_len = bytes.len() - start_padding; + let end_padding = start_padding + PAGE_SIZE * (remaining_len / PAGE_SIZE); + + // Write aligned slice of length in multiples of 256 bytes + // If the remaining bytes to be written is more than a full page. + if remaining_len >= PAGE_SIZE { + let mut aligned_offset = if start_padding > 0 { + offset as usize + padded_offset + } else { + offset as usize + }; + + if bytes.as_ptr() as usize >= 0x2000_0000 { + let aligned_data = &bytes[start_padding..end_padding]; + + unsafe { self.in_ram(|| ram_helpers::flash_range_program(aligned_offset as u32, aligned_data))? } + } else { + for chunk in bytes[start_padding..end_padding].chunks_exact(PAGE_SIZE) { + let mut ram_buf = [0xFF_u8; PAGE_SIZE]; + ram_buf.copy_from_slice(chunk); + unsafe { self.in_ram(|| ram_helpers::flash_range_program(aligned_offset as u32, &ram_buf))? } + aligned_offset += PAGE_SIZE; + } + } + } + + // Pad in the end + let rem_offset = (end_offset as *const u8).align_offset(PAGE_SIZE); + let rem_padding = remaining_len % PAGE_SIZE; + if rem_padding > 0 { + let mut pad_buf = [0xFF_u8; PAGE_SIZE]; + pad_buf[..rem_padding].copy_from_slice(&bytes[end_padding..]); + + let unaligned_offset = end_offset - (PAGE_SIZE - rem_offset); + + unsafe { self.in_ram(|| ram_helpers::flash_range_program(unaligned_offset as u32, &pad_buf))? } + } + + Ok(()) + } + + /// Make sure to uphold the contract points with rp2040-flash. + /// - interrupts must be disabled + /// - DMA must not access flash memory + unsafe fn in_ram(&mut self, operation: impl FnOnce()) -> Result<(), Error> { + // Make sure we're running on CORE0 + let core_id: u32 = pac::SIO.cpuid().read(); + if core_id != 0 { + return Err(Error::InvalidCore); + } + + // Make sure CORE1 is paused during the entire duration of the RAM function + crate::multicore::pause_core1(); + + critical_section::with(|_| { + // Wait for all DMA channels in flash to finish before ram operation + const SRAM_LOWER: u32 = 0x2000_0000; + for n in 0..crate::dma::CHANNEL_COUNT { + let ch = crate::pac::DMA.ch(n); + while ch.read_addr().read() < SRAM_LOWER && ch.ctrl_trig().read().busy() {} + } + + // Run our flash operation in RAM + operation(); + }); + + // Resume CORE1 execution + crate::multicore::resume_core1(); + Ok(()) + } + + /// Read SPI flash unique ID + pub fn unique_id(&mut self, uid: &mut [u8]) -> Result<(), Error> { + unsafe { self.in_ram(|| ram_helpers::flash_unique_id(uid))? }; + Ok(()) + } + + /// Read SPI flash JEDEC ID + pub fn jedec_id(&mut self) -> Result { + let mut jedec = None; + unsafe { + self.in_ram(|| { + jedec.replace(ram_helpers::flash_jedec_id()); + })?; + }; + Ok(jedec.unwrap()) + } +} + +impl<'d, T: Instance, const FLASH_SIZE: usize> ErrorType for Flash<'d, T, FLASH_SIZE> { + type Error = Error; +} + +impl<'d, T: Instance, const FLASH_SIZE: usize> ReadNorFlash for Flash<'d, T, FLASH_SIZE> { + const READ_SIZE: usize = READ_SIZE; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.read(offset, bytes) + } + + fn capacity(&self) -> usize { + self.capacity() + } +} + +impl<'d, T: Instance, const FLASH_SIZE: usize> MultiwriteNorFlash for Flash<'d, T, FLASH_SIZE> {} + +impl<'d, T: Instance, const FLASH_SIZE: usize> NorFlash for Flash<'d, T, FLASH_SIZE> { + const WRITE_SIZE: usize = WRITE_SIZE; + + const ERASE_SIZE: usize = ERASE_SIZE; + + fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.erase(from, to) + } + + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.write(offset, bytes) + } +} + +#[allow(dead_code)] +mod ram_helpers { + use core::marker::PhantomData; + + use super::*; + use crate::rom_data; + + #[repr(C)] + struct FlashFunctionPointers<'a> { + connect_internal_flash: unsafe extern "C" fn() -> (), + flash_exit_xip: unsafe extern "C" fn() -> (), + flash_range_erase: Option ()>, + flash_range_program: Option ()>, + flash_flush_cache: unsafe extern "C" fn() -> (), + flash_enter_cmd_xip: unsafe extern "C" fn() -> (), + phantom: PhantomData<&'a ()>, + } + + #[allow(unused)] + fn flash_function_pointers(erase: bool, write: bool) -> FlashFunctionPointers<'static> { + FlashFunctionPointers { + connect_internal_flash: rom_data::connect_internal_flash::ptr(), + flash_exit_xip: rom_data::flash_exit_xip::ptr(), + flash_range_erase: if erase { + Some(rom_data::flash_range_erase::ptr()) + } else { + None + }, + flash_range_program: if write { + Some(rom_data::flash_range_program::ptr()) + } else { + None + }, + flash_flush_cache: rom_data::flash_flush_cache::ptr(), + flash_enter_cmd_xip: rom_data::flash_enter_cmd_xip::ptr(), + phantom: PhantomData, + } + } + + #[allow(unused)] + /// # Safety + /// + /// `boot2` must contain a valid 2nd stage boot loader which can be called to re-initialize XIP mode + unsafe fn flash_function_pointers_with_boot2(erase: bool, write: bool, boot2: &[u32; 64]) -> FlashFunctionPointers { + let boot2_fn_ptr = (boot2 as *const u32 as *const u8).offset(1); + let boot2_fn: unsafe extern "C" fn() -> () = core::mem::transmute(boot2_fn_ptr); + FlashFunctionPointers { + connect_internal_flash: rom_data::connect_internal_flash::ptr(), + flash_exit_xip: rom_data::flash_exit_xip::ptr(), + flash_range_erase: if erase { + Some(rom_data::flash_range_erase::ptr()) + } else { + None + }, + flash_range_program: if write { + Some(rom_data::flash_range_program::ptr()) + } else { + None + }, + flash_flush_cache: rom_data::flash_flush_cache::ptr(), + flash_enter_cmd_xip: boot2_fn, + phantom: PhantomData, + } + } + + /// Erase a flash range starting at `addr` with length `len`. + /// + /// `addr` and `len` must be multiples of 4096 + /// + /// If `USE_BOOT2` is `true`, a copy of the 2nd stage boot loader + /// is used to re-initialize the XIP engine after flashing. + /// + /// # Safety + /// + /// Nothing must access flash while this is running. + /// Usually this means: + /// - interrupts must be disabled + /// - 2nd core must be running code from RAM or ROM with interrupts disabled + /// - DMA must not access flash memory + /// + /// `addr` and `len` parameters must be valid and are not checked. + pub unsafe fn flash_range_erase(addr: u32, len: u32) { + let mut boot2 = [0u32; 256 / 4]; + let ptrs = if USE_BOOT2 { + rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); + flash_function_pointers_with_boot2(true, false, &boot2) + } else { + flash_function_pointers(true, false) + }; + + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); + + write_flash_inner(addr, len, None, &ptrs as *const FlashFunctionPointers); + } + + /// Erase and rewrite a flash range starting at `addr` with data `data`. + /// + /// `addr` and `data.len()` must be multiples of 4096 + /// + /// If `USE_BOOT2` is `true`, a copy of the 2nd stage boot loader + /// is used to re-initialize the XIP engine after flashing. + /// + /// # Safety + /// + /// Nothing must access flash while this is running. + /// Usually this means: + /// - interrupts must be disabled + /// - 2nd core must be running code from RAM or ROM with interrupts disabled + /// - DMA must not access flash memory + /// + /// `addr` and `len` parameters must be valid and are not checked. + pub unsafe fn flash_range_erase_and_program(addr: u32, data: &[u8]) { + let mut boot2 = [0u32; 256 / 4]; + let ptrs = if USE_BOOT2 { + rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); + flash_function_pointers_with_boot2(true, true, &boot2) + } else { + flash_function_pointers(true, true) + }; + + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); + + write_flash_inner( + addr, + data.len() as u32, + Some(data), + &ptrs as *const FlashFunctionPointers, + ); + } + + /// Write a flash range starting at `addr` with data `data`. + /// + /// `addr` and `data.len()` must be multiples of 256 + /// + /// If `USE_BOOT2` is `true`, a copy of the 2nd stage boot loader + /// is used to re-initialize the XIP engine after flashing. + /// + /// # Safety + /// + /// Nothing must access flash while this is running. + /// Usually this means: + /// - interrupts must be disabled + /// - 2nd core must be running code from RAM or ROM with interrupts disabled + /// - DMA must not access flash memory + /// + /// `addr` and `len` parameters must be valid and are not checked. + pub unsafe fn flash_range_program(addr: u32, data: &[u8]) { + let mut boot2 = [0u32; 256 / 4]; + let ptrs = if USE_BOOT2 { + rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); + flash_function_pointers_with_boot2(false, true, &boot2) + } else { + flash_function_pointers(false, true) + }; + + core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); + + write_flash_inner( + addr, + data.len() as u32, + Some(data), + &ptrs as *const FlashFunctionPointers, + ); + } + + /// # Safety + /// + /// Nothing must access flash while this is running. + /// Usually this means: + /// - interrupts must be disabled + /// - 2nd core must be running code from RAM or ROM with interrupts disabled + /// - DMA must not access flash memory + /// Length of data must be a multiple of 4096 + /// addr must be aligned to 4096 + #[inline(never)] + #[link_section = ".data.ram_func"] + unsafe fn write_flash_inner(addr: u32, len: u32, data: Option<&[u8]>, ptrs: *const FlashFunctionPointers) { + /* + Should be equivalent to: + rom_data::connect_internal_flash(); + rom_data::flash_exit_xip(); + rom_data::flash_range_erase(addr, len, 1 << 31, 0); // if selected + rom_data::flash_range_program(addr, data as *const _, len); // if selected + rom_data::flash_flush_cache(); + rom_data::flash_enter_cmd_xip(); + */ + #[cfg(target_arch = "arm")] + core::arch::asm!( + "mov r8, r0", + "mov r9, r2", + "mov r10, r1", + "ldr r4, [{ptrs}, #0]", + "blx r4", // connect_internal_flash() + + "ldr r4, [{ptrs}, #4]", + "blx r4", // flash_exit_xip() + + "mov r0, r8", // r0 = addr + "mov r1, r10", // r1 = len + "movs r2, #1", + "lsls r2, r2, #31", // r2 = 1 << 31 + "movs r3, #0", // r3 = 0 + "ldr r4, [{ptrs}, #8]", + "cmp r4, #0", + "beq 1f", + "blx r4", // flash_range_erase(addr, len, 1 << 31, 0) + "1:", + + "mov r0, r8", // r0 = addr + "mov r1, r9", // r0 = data + "mov r2, r10", // r2 = len + "ldr r4, [{ptrs}, #12]", + "cmp r4, #0", + "beq 1f", + "blx r4", // flash_range_program(addr, data, len); + "1:", + + "ldr r4, [{ptrs}, #16]", + "blx r4", // flash_flush_cache(); + + "ldr r4, [{ptrs}, #20]", + "blx r4", // flash_enter_cmd_xip(); + ptrs = in(reg) ptrs, + // Registers r8-r15 are not allocated automatically, + // so assign them manually. We need to use them as + // otherwise there are not enough registers available. + in("r0") addr, + in("r2") data.map(|d| d.as_ptr()).unwrap_or(core::ptr::null()), + in("r1") len, + out("r3") _, + out("r4") _, + lateout("r8") _, + lateout("r9") _, + lateout("r10") _, + clobber_abi("C"), + ); + } + + #[repr(C)] + struct FlashCommand { + cmd_addr: *const u8, + cmd_addr_len: u32, + dummy_len: u32, + data: *mut u8, + data_len: u32, + } + + /// Return SPI flash unique ID + /// + /// Not all SPI flashes implement this command, so check the JEDEC + /// ID before relying on it. The Winbond parts commonly seen on + /// RP2040 devboards (JEDEC=0xEF7015) support an 8-byte unique ID; + /// https://forums.raspberrypi.com/viewtopic.php?t=331949 suggests + /// that LCSC (Zetta) parts have a 16-byte unique ID (which is + /// *not* unique in just its first 8 bytes), + /// JEDEC=0xBA6015. Macronix and Spansion parts do not have a + /// unique ID. + /// + /// The returned bytes are relatively predictable and should be + /// salted and hashed before use if that is an issue (e.g. for MAC + /// addresses). + /// + /// # Safety + /// + /// Nothing must access flash while this is running. + /// Usually this means: + /// - interrupts must be disabled + /// - 2nd core must be running code from RAM or ROM with interrupts disabled + /// - DMA must not access flash memory + /// + /// Credit: taken from `rp2040-flash` (also licensed Apache+MIT) + pub unsafe fn flash_unique_id(out: &mut [u8]) { + let mut boot2 = [0u32; 256 / 4]; + let ptrs = if USE_BOOT2 { + rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); + flash_function_pointers_with_boot2(false, false, &boot2) + } else { + flash_function_pointers(false, false) + }; + // 4B - read unique ID + let cmd = [0x4B]; + read_flash(&cmd[..], 4, out, &ptrs as *const FlashFunctionPointers); + } + + /// Return SPI flash JEDEC ID + /// + /// This is the three-byte manufacturer-and-model identifier + /// commonly used to check before using manufacturer-specific SPI + /// flash features, e.g. 0xEF7015 for Winbond W25Q16JV. + /// + /// # Safety + /// + /// Nothing must access flash while this is running. + /// Usually this means: + /// - interrupts must be disabled + /// - 2nd core must be running code from RAM or ROM with interrupts disabled + /// - DMA must not access flash memory + /// + /// Credit: taken from `rp2040-flash` (also licensed Apache+MIT) + pub unsafe fn flash_jedec_id() -> u32 { + let mut boot2 = [0u32; 256 / 4]; + let ptrs = if USE_BOOT2 { + rom_data::memcpy44(&mut boot2 as *mut _, FLASH_BASE, 256); + flash_function_pointers_with_boot2(false, false, &boot2) + } else { + flash_function_pointers(false, false) + }; + let mut id = [0u8; 4]; + // 9F - read JEDEC ID + let cmd = [0x9F]; + read_flash(&cmd[..], 0, &mut id[1..4], &ptrs as *const FlashFunctionPointers); + u32::from_be_bytes(id) + } + + unsafe fn read_flash(cmd_addr: &[u8], dummy_len: u32, out: &mut [u8], ptrs: *const FlashFunctionPointers) { + read_flash_inner( + FlashCommand { + cmd_addr: cmd_addr.as_ptr(), + cmd_addr_len: cmd_addr.len() as u32, + dummy_len, + data: out.as_mut_ptr(), + data_len: out.len() as u32, + }, + ptrs, + ); + } + + /// Issue a generic SPI flash read command + /// + /// # Arguments + /// + /// * `cmd` - `FlashCommand` structure + /// * `ptrs` - Flash function pointers as per `write_flash_inner` + /// + /// Credit: taken from `rp2040-flash` (also licensed Apache+MIT) + #[inline(never)] + #[link_section = ".data.ram_func"] + unsafe fn read_flash_inner(cmd: FlashCommand, ptrs: *const FlashFunctionPointers) { + #[cfg(target_arch = "arm")] + core::arch::asm!( + "mov r10, r0", // cmd + "mov r5, r1", // ptrs + + "ldr r4, [r5, #0]", + "blx r4", // connect_internal_flash() + + "ldr r4, [r5, #4]", + "blx r4", // flash_exit_xip() + + + "movs r4, #0x18", + "lsls r4, r4, #24", // 0x18000000, SSI, RP2040 datasheet 4.10.13 + + // Disable, write 0 to SSIENR + "movs r0, #0", + "str r0, [r4, #8]", // SSIENR + + // Write ctrlr0 + "movs r0, #0x3", + "lsls r0, r0, #8", // TMOD=0x300 + "ldr r1, [r4, #0]", // CTRLR0 + "orrs r1, r0", + "str r1, [r4, #0]", + + // Write ctrlr1 with len-1 + "mov r3, r10", // cmd + "ldr r0, [r3, #8]", // dummy_len + "ldr r1, [r3, #16]", // data_len + "add r0, r1", + "subs r0, #1", + "str r0, [r4, #0x04]", // CTRLR1 + + // Enable, write 1 to ssienr + "movs r0, #1", + "str r0, [r4, #8]", // SSIENR + + // Write cmd/addr phase to DR + "mov r2, r4", + "adds r2, 0x60", // &DR + "ldr r0, [r3, #0]", // cmd_addr + "ldr r1, [r3, #4]", // cmd_addr_len + "10:", + "ldrb r3, [r0]", + "strb r3, [r2]", // DR + "adds r0, #1", + "subs r1, #1", + "bne 10b", + + // Skip any dummy cycles + "mov r3, r10", // cmd + "ldr r1, [r3, #8]", // dummy_len + "cmp r1, #0", + "beq 9f", + "4:", + "ldr r3, [r4, #0x28]", // SR + "movs r2, #0x8", + "tst r3, r2", // SR.RFNE + "beq 4b", + + "mov r2, r4", + "adds r2, 0x60", // &DR + "ldrb r3, [r2]", // DR + "subs r1, #1", + "bne 4b", + + // Read RX fifo + "9:", + "mov r2, r10", // cmd + "ldr r0, [r2, #12]", // data + "ldr r1, [r2, #16]", // data_len + + "2:", + "ldr r3, [r4, #0x28]", // SR + "movs r2, #0x8", + "tst r3, r2", // SR.RFNE + "beq 2b", + + "mov r2, r4", + "adds r2, 0x60", // &DR + "ldrb r3, [r2]", // DR + "strb r3, [r0]", + "adds r0, #1", + "subs r1, #1", + "bne 2b", + + // Disable, write 0 to ssienr + "movs r0, #0", + "str r0, [r4, #8]", // SSIENR + + // Write 0 to CTRLR1 (returning to its default value) + // + // flash_enter_cmd_xip does NOT do this, and everything goes + // wrong unless we do it here + "str r0, [r4, #4]", // CTRLR1 + + "ldr r4, [r5, #20]", + "blx r4", // flash_enter_cmd_xip(); + + in("r0") &cmd as *const FlashCommand, + in("r1") ptrs, + out("r2") _, + out("r3") _, + out("r4") _, + out("r5") _, + // Registers r8-r10 are used to store values + // from r0-r2 in registers not clobbered by + // function calls. + // The values can't be passed in using r8-r10 directly + // due to https://github.com/rust-lang/rust/issues/99071 + out("r10") _, + clobber_abi("C"), + ); + } +} + +mod sealed { + pub trait Instance {} +} + +pub trait Instance: sealed::Instance {} + +impl sealed::Instance for FLASH {} +impl Instance for FLASH {} diff --git a/embassy-rp/src/float/add_sub.rs b/embassy-rp/src/float/add_sub.rs new file mode 100644 index 000000000..673544cfe --- /dev/null +++ b/embassy-rp/src/float/add_sub.rs @@ -0,0 +1,92 @@ +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/float/add_sub.rs + +use super::{Float, Int}; +use crate::rom_data; + +trait ROMAdd { + fn rom_add(self, b: Self) -> Self; +} + +impl ROMAdd for f32 { + fn rom_add(self, b: Self) -> Self { + rom_data::float_funcs::fadd(self, b) + } +} + +impl ROMAdd for f64 { + fn rom_add(self, b: Self) -> Self { + rom_data::double_funcs::dadd(self, b) + } +} + +fn add(a: F, b: F) -> F { + if a.is_not_finite() { + if b.is_not_finite() { + let class_a = a.repr() & (F::SIGNIFICAND_MASK | F::SIGN_MASK); + let class_b = b.repr() & (F::SIGNIFICAND_MASK | F::SIGN_MASK); + + if class_a == F::Int::ZERO && class_b == F::Int::ZERO { + // inf + inf = inf + return a; + } + if class_a == F::SIGN_MASK && class_b == F::SIGN_MASK { + // -inf + (-inf) = -inf + return a; + } + + // Sign mismatch, or either is NaN already + return F::NAN; + } + + // [-]inf/NaN + X = [-]inf/NaN + return a; + } + + if b.is_not_finite() { + // X + [-]inf/NaN = [-]inf/NaN + return b; + } + + a.rom_add(b) +} + +intrinsics! { + #[alias = __addsf3vfp] + #[aeabi = __aeabi_fadd] + extern "C" fn __addsf3(a: f32, b: f32) -> f32 { + add(a, b) + } + + #[bootrom_v2] + #[alias = __adddf3vfp] + #[aeabi = __aeabi_dadd] + extern "C" fn __adddf3(a: f64, b: f64) -> f64 { + add(a, b) + } + + // The ROM just implements subtraction the same way, so just do it here + // and save the work of implementing more complicated NaN/inf handling. + + #[alias = __subsf3vfp] + #[aeabi = __aeabi_fsub] + extern "C" fn __subsf3(a: f32, b: f32) -> f32 { + add(a, -b) + } + + #[bootrom_v2] + #[alias = __subdf3vfp] + #[aeabi = __aeabi_dsub] + extern "C" fn __subdf3(a: f64, b: f64) -> f64 { + add(a, -b) + } + + extern "aapcs" fn __aeabi_frsub(a: f32, b: f32) -> f32 { + add(b, -a) + } + + #[bootrom_v2] + extern "aapcs" fn __aeabi_drsub(a: f64, b: f64) -> f64 { + add(b, -a) + } +} diff --git a/embassy-rp/src/float/cmp.rs b/embassy-rp/src/float/cmp.rs new file mode 100644 index 000000000..e540e3918 --- /dev/null +++ b/embassy-rp/src/float/cmp.rs @@ -0,0 +1,201 @@ +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/float/cmp.rs + +use super::Float; +use crate::rom_data; + +trait ROMCmp { + fn rom_cmp(self, b: Self) -> i32; +} + +impl ROMCmp for f32 { + fn rom_cmp(self, b: Self) -> i32 { + rom_data::float_funcs::fcmp(self, b) + } +} + +impl ROMCmp for f64 { + fn rom_cmp(self, b: Self) -> i32 { + rom_data::double_funcs::dcmp(self, b) + } +} + +fn le_abi(a: F, b: F) -> i32 { + if a.is_nan() || b.is_nan() { + 1 + } else { + a.rom_cmp(b) + } +} + +fn ge_abi(a: F, b: F) -> i32 { + if a.is_nan() || b.is_nan() { + -1 + } else { + a.rom_cmp(b) + } +} + +intrinsics! { + #[slower_than_default] + #[bootrom_v2] + #[alias = __eqsf2, __ltsf2, __nesf2] + extern "C" fn __lesf2(a: f32, b: f32) -> i32 { + le_abi(a, b) + } + + #[slower_than_default] + #[bootrom_v2] + #[alias = __eqdf2, __ltdf2, __nedf2] + extern "C" fn __ledf2(a: f64, b: f64) -> i32 { + le_abi(a, b) + } + + #[slower_than_default] + #[bootrom_v2] + #[alias = __gtsf2] + extern "C" fn __gesf2(a: f32, b: f32) -> i32 { + ge_abi(a, b) + } + + #[slower_than_default] + #[bootrom_v2] + #[alias = __gtdf2] + extern "C" fn __gedf2(a: f64, b: f64) -> i32 { + ge_abi(a, b) + } + + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_fcmple(a: f32, b: f32) -> i32 { + (le_abi(a, b) <= 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_fcmpge(a: f32, b: f32) -> i32 { + (ge_abi(a, b) >= 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_fcmpeq(a: f32, b: f32) -> i32 { + (le_abi(a, b) == 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_fcmplt(a: f32, b: f32) -> i32 { + (le_abi(a, b) < 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_fcmpgt(a: f32, b: f32) -> i32 { + (ge_abi(a, b) > 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_dcmple(a: f64, b: f64) -> i32 { + (le_abi(a, b) <= 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_dcmpge(a: f64, b: f64) -> i32 { + (ge_abi(a, b) >= 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_dcmpeq(a: f64, b: f64) -> i32 { + (le_abi(a, b) == 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_dcmplt(a: f64, b: f64) -> i32 { + (le_abi(a, b) < 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "aapcs" fn __aeabi_dcmpgt(a: f64, b: f64) -> i32 { + (ge_abi(a, b) > 0) as i32 + } + + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __gesf2vfp(a: f32, b: f32) -> i32 { + (ge_abi(a, b) >= 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __gedf2vfp(a: f64, b: f64) -> i32 { + (ge_abi(a, b) >= 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __gtsf2vfp(a: f32, b: f32) -> i32 { + (ge_abi(a, b) > 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __gtdf2vfp(a: f64, b: f64) -> i32 { + (ge_abi(a, b) > 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __ltsf2vfp(a: f32, b: f32) -> i32 { + (le_abi(a, b) < 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __ltdf2vfp(a: f64, b: f64) -> i32 { + (le_abi(a, b) < 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __lesf2vfp(a: f32, b: f32) -> i32 { + (le_abi(a, b) <= 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __ledf2vfp(a: f64, b: f64) -> i32 { + (le_abi(a, b) <= 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __nesf2vfp(a: f32, b: f32) -> i32 { + (le_abi(a, b) != 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __nedf2vfp(a: f64, b: f64) -> i32 { + (le_abi(a, b) != 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __eqsf2vfp(a: f32, b: f32) -> i32 { + (le_abi(a, b) == 0) as i32 + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn __eqdf2vfp(a: f64, b: f64) -> i32 { + (le_abi(a, b) == 0) as i32 + } +} diff --git a/embassy-rp/src/float/conv.rs b/embassy-rp/src/float/conv.rs new file mode 100644 index 000000000..021826e28 --- /dev/null +++ b/embassy-rp/src/float/conv.rs @@ -0,0 +1,157 @@ +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/float/conv.rs + +use super::Float; +use crate::rom_data; + +// Some of these are also not connected in the Pico SDK. This is probably +// because the ROM version actually does a fixed point conversion, just with +// the fractional width set to zero. + +intrinsics! { + // Not connected in the Pico SDK + #[slower_than_default] + #[aeabi = __aeabi_i2f] + extern "C" fn __floatsisf(i: i32) -> f32 { + rom_data::float_funcs::int_to_float(i) + } + + // Not connected in the Pico SDK + #[slower_than_default] + #[aeabi = __aeabi_i2d] + extern "C" fn __floatsidf(i: i32) -> f64 { + rom_data::double_funcs::int_to_double(i) + } + + // Questionable gain + #[aeabi = __aeabi_l2f] + extern "C" fn __floatdisf(i: i64) -> f32 { + rom_data::float_funcs::int64_to_float(i) + } + + #[bootrom_v2] + #[aeabi = __aeabi_l2d] + extern "C" fn __floatdidf(i: i64) -> f64 { + rom_data::double_funcs::int64_to_double(i) + } + + // Not connected in the Pico SDK + #[slower_than_default] + #[aeabi = __aeabi_ui2f] + extern "C" fn __floatunsisf(i: u32) -> f32 { + rom_data::float_funcs::uint_to_float(i) + } + + // Questionable gain + #[bootrom_v2] + #[aeabi = __aeabi_ui2d] + extern "C" fn __floatunsidf(i: u32) -> f64 { + rom_data::double_funcs::uint_to_double(i) + } + + // Questionable gain + #[bootrom_v2] + #[aeabi = __aeabi_ul2f] + extern "C" fn __floatundisf(i: u64) -> f32 { + rom_data::float_funcs::uint64_to_float(i) + } + + #[bootrom_v2] + #[aeabi = __aeabi_ul2d] + extern "C" fn __floatundidf(i: u64) -> f64 { + rom_data::double_funcs::uint64_to_double(i) + } + + + // The Pico SDK does some optimization here (e.x. fast paths for zero and + // one), but we can just directly connect it. + #[aeabi = __aeabi_f2iz] + extern "C" fn __fixsfsi(f: f32) -> i32 { + rom_data::float_funcs::float_to_int(f) + } + + #[bootrom_v2] + #[aeabi = __aeabi_f2lz] + extern "C" fn __fixsfdi(f: f32) -> i64 { + rom_data::float_funcs::float_to_int64(f) + } + + // Not connected in the Pico SDK + #[slower_than_default] + #[bootrom_v2] + #[aeabi = __aeabi_d2iz] + extern "C" fn __fixdfsi(f: f64) -> i32 { + rom_data::double_funcs::double_to_int(f) + } + + // Like with the 32 bit version, there's optimization that we just + // skip. + #[bootrom_v2] + #[aeabi = __aeabi_d2lz] + extern "C" fn __fixdfdi(f: f64) -> i64 { + rom_data::double_funcs::double_to_int64(f) + } + + #[slower_than_default] + #[aeabi = __aeabi_f2uiz] + extern "C" fn __fixunssfsi(f: f32) -> u32 { + rom_data::float_funcs::float_to_uint(f) + } + + #[slower_than_default] + #[bootrom_v2] + #[aeabi = __aeabi_f2ulz] + extern "C" fn __fixunssfdi(f: f32) -> u64 { + rom_data::float_funcs::float_to_uint64(f) + } + + #[slower_than_default] + #[bootrom_v2] + #[aeabi = __aeabi_d2uiz] + extern "C" fn __fixunsdfsi(f: f64) -> u32 { + rom_data::double_funcs::double_to_uint(f) + } + + #[slower_than_default] + #[bootrom_v2] + #[aeabi = __aeabi_d2ulz] + extern "C" fn __fixunsdfdi(f: f64) -> u64 { + rom_data::double_funcs::double_to_uint64(f) + } + + #[bootrom_v2] + #[alias = __extendsfdf2vfp] + #[aeabi = __aeabi_f2d] + extern "C" fn __extendsfdf2(f: f32) -> f64 { + if f.is_not_finite() { + return f64::from_repr( + // Not finite + f64::EXPONENT_MASK | + // Preserve NaN or inf + ((f.repr() & f32::SIGNIFICAND_MASK) as u64) | + // Preserve sign + ((f.repr() & f32::SIGN_MASK) as u64) << (f64::BITS-f32::BITS) + ); + } + rom_data::float_funcs::float_to_double(f) + } + + #[bootrom_v2] + #[alias = __truncdfsf2vfp] + #[aeabi = __aeabi_d2f] + extern "C" fn __truncdfsf2(f: f64) -> f32 { + if f.is_not_finite() { + let mut repr: u32 = + // Not finite + f32::EXPONENT_MASK | + // Preserve sign + ((f.repr() & f64::SIGN_MASK) >> (f64::BITS-f32::BITS)) as u32; + // Set NaN + if (f.repr() & f64::SIGNIFICAND_MASK) != 0 { + repr |= 1; + } + return f32::from_repr(repr); + } + rom_data::double_funcs::double_to_float(f) + } +} diff --git a/embassy-rp/src/float/div.rs b/embassy-rp/src/float/div.rs new file mode 100644 index 000000000..aff0dcb07 --- /dev/null +++ b/embassy-rp/src/float/div.rs @@ -0,0 +1,139 @@ +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/float/conv.rs + +use super::Float; +use crate::rom_data; + +// Make sure this stays as a separate call, because when it's inlined the +// compiler will move the save of the registers used to contain the divider +// state into the function prologue. That save and restore (push/pop) takes +// longer than the actual division, so doing it in the common case where +// they are not required wastes a lot of time. +#[inline(never)] +#[cold] +fn save_divider_and_call(f: F) -> R +where + F: FnOnce() -> R, +{ + let sio = rp_pac::SIO; + + // Since we can't save the signed-ness of the calculation, we have to make + // sure that there's at least an 8 cycle delay before we read the result. + // The Pico SDK ensures this by using a 6 cycle push and two 1 cycle reads. + // Since we can't be sure the Rust implementation will optimize to the same, + // just use an explicit wait. + while !sio.div().csr().read().ready() {} + + // Read the quotient last, since that's what clears the dirty flag + let dividend = sio.div().udividend().read(); + let divisor = sio.div().udivisor().read(); + let remainder = sio.div().remainder().read(); + let quotient = sio.div().quotient().read(); + + // If we get interrupted here (before a write sets the DIRTY flag) its fine, since + // we have the full state, so the interruptor doesn't have to restore it. Once the + // write happens and the DIRTY flag is set, the interruptor becomes responsible for + // restoring our state. + let result = f(); + + // If we are interrupted here, then the interruptor will start an incorrect calculation + // using a wrong divisor, but we'll restore the divisor and result ourselves correctly. + // This sets DIRTY, so any interruptor will save the state. + sio.div().udividend().write_value(dividend); + // If we are interrupted here, the the interruptor may start the calculation using + // incorrectly signed inputs, but we'll restore the result ourselves. + // This sets DIRTY, so any interruptor will save the state. + sio.div().udivisor().write_value(divisor); + // If we are interrupted here, the interruptor will have restored everything but the + // quotient may be wrongly signed. If the calculation started by the above writes is + // still ongoing it is stopped, so it won't replace the result we're restoring. + // DIRTY and READY set, but only DIRTY matters to make the interruptor save the state. + sio.div().remainder().write_value(remainder); + // State fully restored after the quotient write. This sets both DIRTY and READY, so + // whatever we may have interrupted can read the result. + sio.div().quotient().write_value(quotient); + + result +} + +fn save_divider(f: F) -> R +where + F: FnOnce() -> R, +{ + let sio = rp_pac::SIO; + if !sio.div().csr().read().dirty() { + // Not dirty, so nothing is waiting for the calculation. So we can just + // issue it directly without a save/restore. + f() + } else { + save_divider_and_call(f) + } +} + +trait ROMDiv { + fn rom_div(self, b: Self) -> Self; +} + +impl ROMDiv for f32 { + fn rom_div(self, b: Self) -> Self { + // ROM implementation uses the hardware divider, so we have to save it + save_divider(|| rom_data::float_funcs::fdiv(self, b)) + } +} + +impl ROMDiv for f64 { + fn rom_div(self, b: Self) -> Self { + // ROM implementation uses the hardware divider, so we have to save it + save_divider(|| rom_data::double_funcs::ddiv(self, b)) + } +} + +fn div(a: F, b: F) -> F { + if a.is_not_finite() { + if b.is_not_finite() { + // inf/NaN / inf/NaN = NaN + return F::NAN; + } + + if b.is_zero() { + // inf/NaN / 0 = NaN + return F::NAN; + } + + return if b.is_sign_negative() { + // [+/-]inf/NaN / (-X) = [-/+]inf/NaN + a.negate() + } else { + // [-]inf/NaN / X = [-]inf/NaN + a + }; + } + + if b.is_nan() { + // X / NaN = NaN + return b; + } + + // ROM handles X / 0 = [-]inf and X / [-]inf = [-]0, so we only + // need to catch 0 / 0 + if b.is_zero() && a.is_zero() { + return F::NAN; + } + + a.rom_div(b) +} + +intrinsics! { + #[alias = __divsf3vfp] + #[aeabi = __aeabi_fdiv] + extern "C" fn __divsf3(a: f32, b: f32) -> f32 { + div(a, b) + } + + #[bootrom_v2] + #[alias = __divdf3vfp] + #[aeabi = __aeabi_ddiv] + extern "C" fn __divdf3(a: f64, b: f64) -> f64 { + div(a, b) + } +} diff --git a/embassy-rp/src/float/functions.rs b/embassy-rp/src/float/functions.rs new file mode 100644 index 000000000..de29ce336 --- /dev/null +++ b/embassy-rp/src/float/functions.rs @@ -0,0 +1,239 @@ +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/float/functions.rs + +use crate::float::{Float, Int}; +use crate::rom_data; + +trait ROMFunctions { + fn sqrt(self) -> Self; + fn ln(self) -> Self; + fn exp(self) -> Self; + fn sin(self) -> Self; + fn cos(self) -> Self; + fn tan(self) -> Self; + fn atan2(self, y: Self) -> Self; + + fn to_trig_range(self) -> Self; +} + +impl ROMFunctions for f32 { + fn sqrt(self) -> Self { + rom_data::float_funcs::fsqrt(self) + } + + fn ln(self) -> Self { + rom_data::float_funcs::fln(self) + } + + fn exp(self) -> Self { + rom_data::float_funcs::fexp(self) + } + + fn sin(self) -> Self { + rom_data::float_funcs::fsin(self) + } + + fn cos(self) -> Self { + rom_data::float_funcs::fcos(self) + } + + fn tan(self) -> Self { + rom_data::float_funcs::ftan(self) + } + + fn atan2(self, y: Self) -> Self { + rom_data::float_funcs::fatan2(self, y) + } + + fn to_trig_range(self) -> Self { + // -128 < X < 128, logic from the Pico SDK + let exponent = (self.repr() & Self::EXPONENT_MASK) >> Self::SIGNIFICAND_BITS; + if exponent < 134 { + self + } else { + self % (core::f32::consts::PI * 2.0) + } + } +} + +impl ROMFunctions for f64 { + fn sqrt(self) -> Self { + rom_data::double_funcs::dsqrt(self) + } + + fn ln(self) -> Self { + rom_data::double_funcs::dln(self) + } + + fn exp(self) -> Self { + rom_data::double_funcs::dexp(self) + } + + fn sin(self) -> Self { + rom_data::double_funcs::dsin(self) + } + + fn cos(self) -> Self { + rom_data::double_funcs::dcos(self) + } + fn tan(self) -> Self { + rom_data::double_funcs::dtan(self) + } + + fn atan2(self, y: Self) -> Self { + rom_data::double_funcs::datan2(self, y) + } + + fn to_trig_range(self) -> Self { + // -1024 < X < 1024, logic from the Pico SDK + let exponent = (self.repr() & Self::EXPONENT_MASK) >> Self::SIGNIFICAND_BITS; + if exponent < 1033 { + self + } else { + self % (core::f64::consts::PI * 2.0) + } + } +} + +fn is_negative_nonzero_or_nan(f: F) -> bool { + let repr = f.repr(); + if (repr & F::SIGN_MASK) != F::Int::ZERO { + // Negative, so anything other than exactly zero + return (repr & (!F::SIGN_MASK)) != F::Int::ZERO; + } + // NaN + (repr & (F::EXPONENT_MASK | F::SIGNIFICAND_MASK)) > F::EXPONENT_MASK +} + +fn sqrt(f: F) -> F { + if is_negative_nonzero_or_nan(f) { + F::NAN + } else { + f.sqrt() + } +} + +fn ln(f: F) -> F { + if is_negative_nonzero_or_nan(f) { + F::NAN + } else { + f.ln() + } +} + +fn exp(f: F) -> F { + if f.is_nan() { + F::NAN + } else { + f.exp() + } +} + +fn sin(f: F) -> F { + if f.is_not_finite() { + F::NAN + } else { + f.to_trig_range().sin() + } +} + +fn cos(f: F) -> F { + if f.is_not_finite() { + F::NAN + } else { + f.to_trig_range().cos() + } +} + +fn tan(f: F) -> F { + if f.is_not_finite() { + F::NAN + } else { + f.to_trig_range().tan() + } +} + +fn atan2(x: F, y: F) -> F { + if x.is_nan() || y.is_nan() { + F::NAN + } else { + x.to_trig_range().atan2(y) + } +} + +// Name collisions +mod intrinsics { + intrinsics! { + extern "C" fn sqrtf(f: f32) -> f32 { + super::sqrt(f) + } + + #[bootrom_v2] + extern "C" fn sqrt(f: f64) -> f64 { + super::sqrt(f) + } + + extern "C" fn logf(f: f32) -> f32 { + super::ln(f) + } + + #[bootrom_v2] + extern "C" fn log(f: f64) -> f64 { + super::ln(f) + } + + extern "C" fn expf(f: f32) -> f32 { + super::exp(f) + } + + #[bootrom_v2] + extern "C" fn exp(f: f64) -> f64 { + super::exp(f) + } + + #[slower_than_default] + extern "C" fn sinf(f: f32) -> f32 { + super::sin(f) + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn sin(f: f64) -> f64 { + super::sin(f) + } + + #[slower_than_default] + extern "C" fn cosf(f: f32) -> f32 { + super::cos(f) + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn cos(f: f64) -> f64 { + super::cos(f) + } + + #[slower_than_default] + extern "C" fn tanf(f: f32) -> f32 { + super::tan(f) + } + + #[slower_than_default] + #[bootrom_v2] + extern "C" fn tan(f: f64) -> f64 { + super::tan(f) + } + + // Questionable gain + #[bootrom_v2] + extern "C" fn atan2f(a: f32, b: f32) -> f32 { + super::atan2(a, b) + } + + // Questionable gain + #[bootrom_v2] + extern "C" fn atan2(a: f64, b: f64) -> f64 { + super::atan2(a, b) + } + } +} diff --git a/embassy-rp/src/float/mod.rs b/embassy-rp/src/float/mod.rs new file mode 100644 index 000000000..945afff90 --- /dev/null +++ b/embassy-rp/src/float/mod.rs @@ -0,0 +1,149 @@ +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/float/mod.rs + +use core::ops; + +// Borrowed and simplified from compiler-builtins so we can use bit ops +// on floating point without macro soup. +pub(crate) trait Int: + Copy + + core::fmt::Debug + + PartialEq + + PartialOrd + + ops::AddAssign + + ops::SubAssign + + ops::BitAndAssign + + ops::BitOrAssign + + ops::BitXorAssign + + ops::ShlAssign + + ops::ShrAssign + + ops::Add + + ops::Sub + + ops::Div + + ops::Shl + + ops::Shr + + ops::BitOr + + ops::BitXor + + ops::BitAnd + + ops::Not +{ + const ZERO: Self; +} + +macro_rules! int_impl { + ($ty:ty) => { + impl Int for $ty { + const ZERO: Self = 0; + } + }; +} + +int_impl!(u32); +int_impl!(u64); + +pub(crate) trait Float: + Copy + + core::fmt::Debug + + PartialEq + + PartialOrd + + ops::AddAssign + + ops::MulAssign + + ops::Add + + ops::Sub + + ops::Div + + ops::Rem +{ + /// A uint of the same with as the float + type Int: Int; + + /// NaN representation for the float + const NAN: Self; + + /// The bitwidth of the float type + const BITS: u32; + + /// The bitwidth of the significand + const SIGNIFICAND_BITS: u32; + + /// A mask for the sign bit + const SIGN_MASK: Self::Int; + + /// A mask for the significand + const SIGNIFICAND_MASK: Self::Int; + + /// A mask for the exponent + const EXPONENT_MASK: Self::Int; + + /// Returns `self` transmuted to `Self::Int` + fn repr(self) -> Self::Int; + + /// Returns a `Self::Int` transmuted back to `Self` + fn from_repr(a: Self::Int) -> Self; + + /// Return a sign swapped `self` + fn negate(self) -> Self; + + /// Returns true if `self` is either NaN or infinity + fn is_not_finite(self) -> bool { + (self.repr() & Self::EXPONENT_MASK) == Self::EXPONENT_MASK + } + + /// Returns true if `self` is infinity + fn is_infinity(self) -> bool { + (self.repr() & (Self::EXPONENT_MASK | Self::SIGNIFICAND_MASK)) == Self::EXPONENT_MASK + } + + /// Returns true if `self is NaN + fn is_nan(self) -> bool { + (self.repr() & (Self::EXPONENT_MASK | Self::SIGNIFICAND_MASK)) > Self::EXPONENT_MASK + } + + /// Returns true if `self` is negative + fn is_sign_negative(self) -> bool { + (self.repr() & Self::SIGN_MASK) != Self::Int::ZERO + } + + /// Returns true if `self` is zero (either sign) + fn is_zero(self) -> bool { + (self.repr() & (Self::SIGNIFICAND_MASK | Self::EXPONENT_MASK)) == Self::Int::ZERO + } +} + +macro_rules! float_impl { + ($ty:ident, $ity:ident, $bits:expr, $significand_bits:expr) => { + impl Float for $ty { + type Int = $ity; + + const NAN: Self = <$ty>::NAN; + + const BITS: u32 = $bits; + const SIGNIFICAND_BITS: u32 = $significand_bits; + + const SIGN_MASK: Self::Int = 1 << (Self::BITS - 1); + const SIGNIFICAND_MASK: Self::Int = (1 << Self::SIGNIFICAND_BITS) - 1; + const EXPONENT_MASK: Self::Int = !(Self::SIGN_MASK | Self::SIGNIFICAND_MASK); + + fn repr(self) -> Self::Int { + self.to_bits() + } + + fn from_repr(a: Self::Int) -> Self { + Self::from_bits(a) + } + + fn negate(self) -> Self { + -self + } + } + }; +} + +float_impl!(f32, u32, 32, 23); +float_impl!(f64, u64, 64, 52); + +mod add_sub; +mod cmp; +mod conv; +mod div; +mod functions; +mod mul; diff --git a/embassy-rp/src/float/mul.rs b/embassy-rp/src/float/mul.rs new file mode 100644 index 000000000..ceb0210e3 --- /dev/null +++ b/embassy-rp/src/float/mul.rs @@ -0,0 +1,70 @@ +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/float/mul.rs + +use super::Float; +use crate::rom_data; + +trait ROMMul { + fn rom_mul(self, b: Self) -> Self; +} + +impl ROMMul for f32 { + fn rom_mul(self, b: Self) -> Self { + rom_data::float_funcs::fmul(self, b) + } +} + +impl ROMMul for f64 { + fn rom_mul(self, b: Self) -> Self { + rom_data::double_funcs::dmul(self, b) + } +} + +fn mul(a: F, b: F) -> F { + if a.is_not_finite() { + if b.is_zero() { + // [-]inf/NaN * 0 = NaN + return F::NAN; + } + + return if b.is_sign_negative() { + // [+/-]inf/NaN * (-X) = [-/+]inf/NaN + a.negate() + } else { + // [-]inf/NaN * X = [-]inf/NaN + a + }; + } + + if b.is_not_finite() { + if a.is_zero() { + // 0 * [-]inf/NaN = NaN + return F::NAN; + } + + return if b.is_sign_negative() { + // (-X) * [+/-]inf/NaN = [-/+]inf/NaN + b.negate() + } else { + // X * [-]inf/NaN = [-]inf/NaN + b + }; + } + + a.rom_mul(b) +} + +intrinsics! { + #[alias = __mulsf3vfp] + #[aeabi = __aeabi_fmul] + extern "C" fn __mulsf3(a: f32, b: f32) -> f32 { + mul(a, b) + } + + #[bootrom_v2] + #[alias = __muldf3vfp] + #[aeabi = __aeabi_dmul] + extern "C" fn __muldf3(a: f64, b: f64) -> f64 { + mul(a, b) + } +} diff --git a/embassy-rp/src/gpio.rs b/embassy-rp/src/gpio.rs index 428855c7f..a3d330cdc 100644 --- a/embassy-rp/src/gpio.rs +++ b/embassy-rp/src/gpio.rs @@ -3,20 +3,20 @@ use core::future::Future; use core::pin::Pin as FuturePin; use core::task::{Context, Poll}; -use embassy_cortex_m::interrupt::{Interrupt, InterruptExt}; use embassy_hal_common::{impl_peripheral, into_ref, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; +use crate::interrupt::InterruptExt; use crate::pac::common::{Reg, RW}; use crate::pac::SIO; -use crate::{interrupt, pac, peripherals, Peripheral}; +use crate::{interrupt, pac, peripherals, Peripheral, RegExt}; const PIN_COUNT: usize = 30; const NEW_AW: AtomicWaker = AtomicWaker::new(); static INTERRUPT_WAKERS: [AtomicWaker; PIN_COUNT] = [NEW_AW; PIN_COUNT]; /// Represents a digital input or output level. -#[derive(Debug, Eq, PartialEq)] +#[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum Level { Low, High, @@ -31,9 +31,9 @@ impl From for Level { } } -impl Into for Level { - fn into(self) -> bool { - match self { +impl From for bool { + fn from(level: Level) -> bool { + match level { Level::Low => false, Level::High => true, } @@ -41,13 +41,28 @@ impl Into for Level { } /// Represents a pull setting for an input. -#[derive(Debug, Eq, PartialEq)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum Pull { None, Up, Down, } +/// Drive strength of an output +#[derive(Debug, Eq, PartialEq)] +pub enum Drive { + _2mA, + _4mA, + _8mA, + _12mA, +} +/// Slew rate of an output +#[derive(Debug, Eq, PartialEq)] +pub enum SlewRate { + Fast, + Slow, +} + /// A GPIO bank with up to 32 pins. #[derive(Debug, Eq, PartialEq)] pub enum Bank { @@ -121,20 +136,15 @@ pub enum InterruptTrigger { AnyEdge, } -impl InterruptTrigger { - fn from_u32(value: u32) -> Option { - match value { - 1 => Some(InterruptTrigger::LevelLow), - 2 => Some(InterruptTrigger::LevelHigh), - 3 => Some(InterruptTrigger::EdgeLow), - 4 => Some(InterruptTrigger::EdgeHigh), - _ => None, - } - } +pub(crate) unsafe fn init() { + interrupt::IO_IRQ_BANK0.disable(); + interrupt::IO_IRQ_BANK0.set_priority(interrupt::Priority::P3); + interrupt::IO_IRQ_BANK0.enable(); } +#[cfg(feature = "rt")] #[interrupt] -unsafe fn IO_IRQ_BANK0() { +fn IO_IRQ_BANK0() { let cpu = SIO.cpuid().read() as usize; // There are two sets of interrupt registers, one for cpu0 and one for cpu1 // and here we are selecting the set that belongs to the currently executing @@ -151,33 +161,22 @@ unsafe fn IO_IRQ_BANK0() { let pin_group = (pin % 8) as usize; let event = (intsx.read().0 >> pin_group * 4) & 0xf as u32; - if let Some(trigger) = InterruptTrigger::from_u32(event) { - critical_section::with(|_| { - proc_intx.inte(pin / 8).modify(|w| match trigger { - InterruptTrigger::AnyEdge => { - w.set_edge_high(pin_group, false); - w.set_edge_low(pin_group, false); - } - InterruptTrigger::LevelHigh => { - debug!("IO_IRQ_BANK0 pin {} LevelHigh triggered\n", pin); - w.set_level_high(pin_group, false); - } - InterruptTrigger::LevelLow => { - w.set_level_low(pin_group, false); - } - InterruptTrigger::EdgeHigh => { - w.set_edge_high(pin_group, false); - } - InterruptTrigger::EdgeLow => { - w.set_edge_low(pin_group, false); - } - }); + // no more than one event can be awaited per pin at any given time, so + // we can just clear all interrupt enables for that pin without having + // to check which event was signalled. + if event != 0 { + proc_intx.inte(pin / 8).write_clear(|w| { + w.set_edge_high(pin_group, true); + w.set_edge_low(pin_group, true); + w.set_level_high(pin_group, true); + w.set_level_low(pin_group, true); }); INTERRUPT_WAKERS[pin as usize].wake(); } } } +#[must_use = "futures do nothing unless you `.await` or poll them"] struct InputFuture<'a, T: Pin> { pin: PeripheralRef<'a, T>, level: InterruptTrigger, @@ -186,39 +185,45 @@ struct InputFuture<'a, T: Pin> { impl<'d, T: Pin> InputFuture<'d, T> { pub fn new(pin: impl Peripheral

+ 'd, level: InterruptTrigger) -> Self { into_ref!(pin); - unsafe { - let irq = interrupt::IO_IRQ_BANK0::steal(); - irq.disable(); - irq.set_priority(interrupt::Priority::P3); + let pin_group = (pin.pin() % 8) as usize; + // first, clear the INTR register bits. without this INTR will still + // contain reports of previous edges, causing the IRQ to fire early + // on stale state. clearing these means that we can only detect edges + // that occur *after* the clear happened, but since both this and the + // alternative are fundamentally racy it's probably fine. + // (the alternative being checking the current level and waiting for + // its inverse, but that requires reading the current level and thus + // missing anything that happened before the level was read.) + pac::IO_BANK0.intr(pin.pin() as usize / 8).write(|w| { + w.set_edge_high(pin_group, true); + w.set_edge_low(pin_group, true); + }); - // Each INTR register is divided into 8 groups, one group for each - // pin, and each group consists of LEVEL_LOW, LEVEL_HIGH, EDGE_LOW, - // and EGDE_HIGH. - let pin_group = (pin.pin() % 8) as usize; - critical_section::with(|_| { - pin.int_proc().inte((pin.pin() / 8) as usize).modify(|w| match level { - InterruptTrigger::LevelHigh => { - debug!("InputFuture::new enable LevelHigh for pin {} \n", pin.pin()); - w.set_level_high(pin_group, true); - } - InterruptTrigger::LevelLow => { - w.set_level_low(pin_group, true); - } - InterruptTrigger::EdgeHigh => { - w.set_edge_high(pin_group, true); - } - InterruptTrigger::EdgeLow => { - w.set_edge_low(pin_group, true); - } - InterruptTrigger::AnyEdge => { - // noop - } - }); + // Each INTR register is divided into 8 groups, one group for each + // pin, and each group consists of LEVEL_LOW, LEVEL_HIGH, EDGE_LOW, + // and EGDE_HIGH. + pin.int_proc() + .inte((pin.pin() / 8) as usize) + .write_set(|w| match level { + InterruptTrigger::LevelHigh => { + trace!("InputFuture::new enable LevelHigh for pin {}", pin.pin()); + w.set_level_high(pin_group, true); + } + InterruptTrigger::LevelLow => { + w.set_level_low(pin_group, true); + } + InterruptTrigger::EdgeHigh => { + w.set_edge_high(pin_group, true); + } + InterruptTrigger::EdgeLow => { + w.set_edge_low(pin_group, true); + } + InterruptTrigger::AnyEdge => { + w.set_edge_high(pin_group, true); + w.set_edge_low(pin_group, true); + } }); - irq.enable(); - } - Self { pin, level } } } @@ -235,55 +240,29 @@ impl<'d, T: Pin> Future for InputFuture<'d, T> { // then we want to access the interrupt enable register for our // pin (there are 4 of these PROC0_INTE0, PROC0_INTE1, PROC0_INTE2, and // PROC0_INTE3 per cpu). - let inte: pac::io::regs::Int = unsafe { self.pin.int_proc().inte((self.pin.pin() / 8) as usize).read() }; + let inte: pac::io::regs::Int = self.pin.int_proc().inte((self.pin.pin() / 8) as usize).read(); // The register is divided into groups of four, one group for // each pin. Each group consists of four trigger levels LEVEL_LOW, // LEVEL_HIGH, EDGE_LOW, and EDGE_HIGH for each pin. let pin_group = (self.pin.pin() % 8) as usize; - // This should check the the level of the interrupt trigger level of - // the pin and if it has been disabled that means it was done by the - // interrupt service routine, so we then know that the event/trigger - // happened and Poll::Ready will be returned. - debug!("{:?} for pin {}\n", self.level, self.pin.pin()); - match self.level { - InterruptTrigger::AnyEdge => { - if !inte.edge_high(pin_group) && !inte.edge_low(pin_group) { - #[rustfmt::skip] - debug!("{:?} for pin {} was cleared, return Poll::Ready\n", self.level, self.pin.pin()); - return Poll::Ready(()); - } - } - InterruptTrigger::LevelHigh => { - if !inte.level_high(pin_group) { - #[rustfmt::skip] - debug!("{:?} for pin {} was cleared, return Poll::Ready\n", self.level, self.pin.pin()); - return Poll::Ready(()); - } - } - InterruptTrigger::LevelLow => { - if !inte.level_low(pin_group) { - #[rustfmt::skip] - debug!("{:?} for pin {} was cleared, return Poll::Ready\n", self.level, self.pin.pin()); - return Poll::Ready(()); - } - } - InterruptTrigger::EdgeHigh => { - if !inte.edge_high(pin_group) { - #[rustfmt::skip] - debug!("{:?} for pin {} was cleared, return Poll::Ready\n", self.level, self.pin.pin()); - return Poll::Ready(()); - } - } - InterruptTrigger::EdgeLow => { - if !inte.edge_low(pin_group) { - #[rustfmt::skip] - debug!("{:?} for pin {} was cleared, return Poll::Ready\n", self.level, self.pin.pin()); - return Poll::Ready(()); - } - } + // since the interrupt handler clears all INTE flags we'll check that + // all have been cleared and unconditionally return Ready(()) if so. + // we don't need further handshaking since only a single event wait + // is possible for any given pin at any given time. + if !inte.edge_high(pin_group) + && !inte.edge_low(pin_group) + && !inte.level_high(pin_group) + && !inte.level_low(pin_group) + { + trace!( + "{:?} for pin {} was cleared, return Poll::Ready", + self.level, + self.pin.pin() + ); + return Poll::Ready(()); } - debug!("InputFuture::poll return Poll::Pending\n"); + trace!("InputFuture::poll return Poll::Pending"); Poll::Pending } } @@ -411,6 +390,47 @@ impl<'d, T: Pin> OutputOpenDrain<'d, T> { pub fn toggle(&mut self) { self.pin.toggle_set_as_output() } + + #[inline] + pub fn is_high(&self) -> bool { + self.pin.is_high() + } + + #[inline] + pub fn is_low(&self) -> bool { + self.pin.is_low() + } + + /// Returns current pin level + #[inline] + pub fn get_level(&self) -> Level { + self.is_high().into() + } + + #[inline] + pub async fn wait_for_high(&mut self) { + self.pin.wait_for_high().await; + } + + #[inline] + pub async fn wait_for_low(&mut self) { + self.pin.wait_for_low().await; + } + + #[inline] + pub async fn wait_for_rising_edge(&mut self) { + self.pin.wait_for_rising_edge().await; + } + + #[inline] + pub async fn wait_for_falling_edge(&mut self) { + self.pin.wait_for_falling_edge().await; + } + + #[inline] + pub async fn wait_for_any_edge(&mut self) { + self.pin.wait_for_any_edge().await; + } } /// GPIO flexible pin. @@ -427,15 +447,13 @@ impl<'d, T: Pin> Flex<'d, T> { pub fn new(pin: impl Peripheral

+ 'd) -> Self { into_ref!(pin); - unsafe { - pin.pad_ctrl().write(|w| { - w.set_ie(true); - }); + pin.pad_ctrl().write(|w| { + w.set_ie(true); + }); - pin.io().ctrl().write(|w| { - w.set_funcsel(pac::io::vals::Gpio0CtrlFuncsel::SIO_0.0); - }); - } + pin.io().ctrl().write(|w| { + w.set_funcsel(pac::io::vals::Gpio0ctrlFuncsel::SIO_0 as _); + }); Self { pin } } @@ -448,16 +466,37 @@ impl<'d, T: Pin> Flex<'d, T> { /// Set the pin's pull. #[inline] pub fn set_pull(&mut self, pull: Pull) { - unsafe { - self.pin.pad_ctrl().write(|w| { - w.set_ie(true); - match pull { - Pull::Up => w.set_pue(true), - Pull::Down => w.set_pde(true), - Pull::None => {} - } + self.pin.pad_ctrl().modify(|w| { + w.set_ie(true); + let (pu, pd) = match pull { + Pull::Up => (true, false), + Pull::Down => (false, true), + Pull::None => (false, false), + }; + w.set_pue(pu); + w.set_pde(pd); + }); + } + + /// Set the pin's drive strength. + #[inline] + pub fn set_drive_strength(&mut self, strength: Drive) { + self.pin.pad_ctrl().modify(|w| { + w.set_drive(match strength { + Drive::_2mA => pac::pads::vals::Drive::_2MA, + Drive::_4mA => pac::pads::vals::Drive::_4MA, + Drive::_8mA => pac::pads::vals::Drive::_8MA, + Drive::_12mA => pac::pads::vals::Drive::_12MA, }); - } + }); + } + + // Set the pin's slew rate. + #[inline] + pub fn set_slew_rate(&mut self, slew_rate: SlewRate) { + self.pin.pad_ctrl().modify(|w| { + w.set_slewfast(slew_rate == SlewRate::Fast); + }); } /// Put the pin into input mode. @@ -465,7 +504,7 @@ impl<'d, T: Pin> Flex<'d, T> { /// The pull setting is left unchanged. #[inline] pub fn set_as_input(&mut self) { - unsafe { self.pin.sio_oe().value_clr().write_value(self.bit()) } + self.pin.sio_oe().value_clr().write_value(self.bit()) } /// Put the pin into output mode. @@ -474,17 +513,17 @@ impl<'d, T: Pin> Flex<'d, T> { /// at a specific level, call `set_high`/`set_low` on the pin first. #[inline] pub fn set_as_output(&mut self) { - unsafe { self.pin.sio_oe().value_set().write_value(self.bit()) } + self.pin.sio_oe().value_set().write_value(self.bit()) } #[inline] fn is_set_as_output(&self) -> bool { - unsafe { (self.pin.sio_oe().value().read() & self.bit()) != 0 } + (self.pin.sio_oe().value().read() & self.bit()) != 0 } #[inline] pub fn toggle_set_as_output(&mut self) { - unsafe { self.pin.sio_oe().value_xor().write_value(self.bit()) } + self.pin.sio_oe().value_xor().write_value(self.bit()) } #[inline] @@ -494,7 +533,7 @@ impl<'d, T: Pin> Flex<'d, T> { #[inline] pub fn is_low(&self) -> bool { - unsafe { self.pin.sio_in().read() & self.bit() == 0 } + self.pin.sio_in().read() & self.bit() == 0 } /// Returns current pin level @@ -506,13 +545,13 @@ impl<'d, T: Pin> Flex<'d, T> { /// Set the output as high. #[inline] pub fn set_high(&mut self) { - unsafe { self.pin.sio_out().value_set().write_value(self.bit()) } + self.pin.sio_out().value_set().write_value(self.bit()) } /// Set the output as low. #[inline] pub fn set_low(&mut self) { - unsafe { self.pin.sio_out().value_clr().write_value(self.bit()) } + self.pin.sio_out().value_clr().write_value(self.bit()) } /// Set the output level. @@ -527,13 +566,13 @@ impl<'d, T: Pin> Flex<'d, T> { /// Is the output level high? #[inline] pub fn is_set_high(&self) -> bool { - unsafe { (self.pin.sio_out().value().read() & self.bit()) == 0 } + !self.is_set_low() } /// Is the output level low? #[inline] pub fn is_set_low(&self) -> bool { - !self.is_set_high() + (self.pin.sio_out().value().read() & self.bit()) == 0 } /// What level output is set to @@ -545,7 +584,7 @@ impl<'d, T: Pin> Flex<'d, T> { /// Toggle pin output #[inline] pub fn toggle(&mut self) { - unsafe { self.pin.sio_out().value_xor().write_value(self.bit()) } + self.pin.sio_out().value_xor().write_value(self.bit()) } #[inline] @@ -560,35 +599,27 @@ impl<'d, T: Pin> Flex<'d, T> { #[inline] pub async fn wait_for_rising_edge(&mut self) { - self.wait_for_low().await; - self.wait_for_high().await; + InputFuture::new(&mut self.pin, InterruptTrigger::EdgeHigh).await; } #[inline] pub async fn wait_for_falling_edge(&mut self) { - self.wait_for_high().await; - self.wait_for_low().await; + InputFuture::new(&mut self.pin, InterruptTrigger::EdgeLow).await; } #[inline] pub async fn wait_for_any_edge(&mut self) { - if self.is_high() { - self.wait_for_low().await; - } else { - self.wait_for_high().await; - } + InputFuture::new(&mut self.pin, InterruptTrigger::AnyEdge).await; } } impl<'d, T: Pin> Drop for Flex<'d, T> { #[inline] fn drop(&mut self) { - unsafe { - self.pin.pad_ctrl().write(|_| {}); - self.pin.io().ctrl().write(|w| { - w.set_funcsel(pac::io::vals::Gpio0CtrlFuncsel::NULL.0); - }); - } + self.pin.pad_ctrl().write(|_| {}); + self.pin.io().ctrl().write(|w| { + w.set_funcsel(pac::io::vals::Gpio0ctrlFuncsel::NULL as _); + }); } } @@ -599,12 +630,12 @@ pub(crate) mod sealed { fn pin_bank(&self) -> u8; #[inline] - fn pin(&self) -> u8 { + fn _pin(&self) -> u8 { self.pin_bank() & 0x1f } #[inline] - fn bank(&self) -> Bank { + fn _bank(&self) -> Bank { if self.pin_bank() & 0x20 == 0 { Bank::Bank0 } else { @@ -613,39 +644,39 @@ pub(crate) mod sealed { } fn io(&self) -> pac::io::Gpio { - let block = match self.bank() { + let block = match self._bank() { Bank::Bank0 => crate::pac::IO_BANK0, Bank::Qspi => crate::pac::IO_QSPI, }; - block.gpio(self.pin() as _) + block.gpio(self._pin() as _) } fn pad_ctrl(&self) -> Reg { - let block = match self.bank() { + let block = match self._bank() { Bank::Bank0 => crate::pac::PADS_BANK0, Bank::Qspi => crate::pac::PADS_QSPI, }; - block.gpio(self.pin() as _) + block.gpio(self._pin() as _) } fn sio_out(&self) -> pac::sio::Gpio { - SIO.gpio_out(self.bank() as _) + SIO.gpio_out(self._bank() as _) } fn sio_oe(&self) -> pac::sio::Gpio { - SIO.gpio_oe(self.bank() as _) + SIO.gpio_oe(self._bank() as _) } fn sio_in(&self) -> Reg { - SIO.gpio_in(self.bank() as _) + SIO.gpio_in(self._bank() as _) } fn int_proc(&self) -> pac::io::Int { - let io_block = match self.bank() { + let io_block = match self._bank() { Bank::Bank0 => crate::pac::IO_BANK0, Bank::Qspi => crate::pac::IO_QSPI, }; - let proc = unsafe { SIO.cpuid().read() }; + let proc = SIO.cpuid().read(); io_block.int_proc(proc as _) } } @@ -658,6 +689,18 @@ pub trait Pin: Peripheral

+ Into + sealed::Pin + Sized + 'stat pin_bank: self.pin_bank(), } } + + /// Returns the pin number within a bank + #[inline] + fn pin(&self) -> u8 { + self._pin() + } + + /// Returns the bank of this pin + #[inline] + fn bank(&self) -> Bank { + self._bank() + } } pub struct AnyPin { @@ -679,6 +722,7 @@ macro_rules! impl_pin { ($name:ident, $bank:expr, $pin_num:expr) => { impl Pin for peripherals::$name {} impl sealed::Pin for peripherals::$name { + #[inline] fn pin_bank(&self) -> u8 { ($bank as u8) * 32 + $pin_num } @@ -779,6 +823,18 @@ mod eh02 { } } + impl<'d, T: Pin> embedded_hal_02::digital::v2::InputPin for OutputOpenDrain<'d, T> { + type Error = Infallible; + + fn is_high(&self) -> Result { + Ok(self.is_high()) + } + + fn is_low(&self) -> Result { + Ok(self.is_low()) + } + } + impl<'d, T: Pin> embedded_hal_02::digital::v2::OutputPin for OutputOpenDrain<'d, T> { type Error = Infallible; @@ -858,16 +914,13 @@ mod eh02 { mod eh1 { use core::convert::Infallible; - #[cfg(feature = "nightly")] - use futures::FutureExt; - use super::*; impl<'d, T: Pin> embedded_hal_1::digital::ErrorType for Input<'d, T> { type Error = Infallible; } - impl<'d, T: Pin> embedded_hal_1::digital::blocking::InputPin for Input<'d, T> { + impl<'d, T: Pin> embedded_hal_1::digital::InputPin for Input<'d, T> { fn is_high(&self) -> Result { Ok(self.is_high()) } @@ -881,7 +934,7 @@ mod eh1 { type Error = Infallible; } - impl<'d, T: Pin> embedded_hal_1::digital::blocking::OutputPin for Output<'d, T> { + impl<'d, T: Pin> embedded_hal_1::digital::OutputPin for Output<'d, T> { fn set_high(&mut self) -> Result<(), Self::Error> { Ok(self.set_high()) } @@ -891,7 +944,7 @@ mod eh1 { } } - impl<'d, T: Pin> embedded_hal_1::digital::blocking::StatefulOutputPin for Output<'d, T> { + impl<'d, T: Pin> embedded_hal_1::digital::StatefulOutputPin for Output<'d, T> { fn is_set_high(&self) -> Result { Ok(self.is_set_high()) } @@ -901,7 +954,7 @@ mod eh1 { } } - impl<'d, T: Pin> embedded_hal_1::digital::blocking::ToggleableOutputPin for Output<'d, T> { + impl<'d, T: Pin> embedded_hal_1::digital::ToggleableOutputPin for Output<'d, T> { fn toggle(&mut self) -> Result<(), Self::Error> { Ok(self.toggle()) } @@ -911,7 +964,7 @@ mod eh1 { type Error = Infallible; } - impl<'d, T: Pin> embedded_hal_1::digital::blocking::OutputPin for OutputOpenDrain<'d, T> { + impl<'d, T: Pin> embedded_hal_1::digital::OutputPin for OutputOpenDrain<'d, T> { fn set_high(&mut self) -> Result<(), Self::Error> { Ok(self.set_high()) } @@ -921,7 +974,7 @@ mod eh1 { } } - impl<'d, T: Pin> embedded_hal_1::digital::blocking::StatefulOutputPin for OutputOpenDrain<'d, T> { + impl<'d, T: Pin> embedded_hal_1::digital::StatefulOutputPin for OutputOpenDrain<'d, T> { fn is_set_high(&self) -> Result { Ok(self.is_set_high()) } @@ -931,17 +984,13 @@ mod eh1 { } } - impl<'d, T: Pin> embedded_hal_1::digital::blocking::ToggleableOutputPin for OutputOpenDrain<'d, T> { + impl<'d, T: Pin> embedded_hal_1::digital::ToggleableOutputPin for OutputOpenDrain<'d, T> { fn toggle(&mut self) -> Result<(), Self::Error> { Ok(self.toggle()) } } - impl<'d, T: Pin> embedded_hal_1::digital::ErrorType for Flex<'d, T> { - type Error = Infallible; - } - - impl<'d, T: Pin> embedded_hal_1::digital::blocking::InputPin for Flex<'d, T> { + impl<'d, T: Pin> embedded_hal_1::digital::InputPin for OutputOpenDrain<'d, T> { fn is_high(&self) -> Result { Ok(self.is_high()) } @@ -951,7 +1000,21 @@ mod eh1 { } } - impl<'d, T: Pin> embedded_hal_1::digital::blocking::OutputPin for Flex<'d, T> { + impl<'d, T: Pin> embedded_hal_1::digital::ErrorType for Flex<'d, T> { + type Error = Infallible; + } + + impl<'d, T: Pin> embedded_hal_1::digital::InputPin for Flex<'d, T> { + fn is_high(&self) -> Result { + Ok(self.is_high()) + } + + fn is_low(&self) -> Result { + Ok(self.is_low()) + } + } + + impl<'d, T: Pin> embedded_hal_1::digital::OutputPin for Flex<'d, T> { fn set_high(&mut self) -> Result<(), Self::Error> { Ok(self.set_high()) } @@ -961,7 +1024,7 @@ mod eh1 { } } - impl<'d, T: Pin> embedded_hal_1::digital::blocking::StatefulOutputPin for Flex<'d, T> { + impl<'d, T: Pin> embedded_hal_1::digital::StatefulOutputPin for Flex<'d, T> { fn is_set_high(&self) -> Result { Ok(self.is_set_high()) } @@ -971,7 +1034,7 @@ mod eh1 { } } - impl<'d, T: Pin> embedded_hal_1::digital::blocking::ToggleableOutputPin for Flex<'d, T> { + impl<'d, T: Pin> embedded_hal_1::digital::ToggleableOutputPin for Flex<'d, T> { fn toggle(&mut self) -> Result<(), Self::Error> { Ok(self.toggle()) } @@ -979,57 +1042,85 @@ mod eh1 { #[cfg(feature = "nightly")] impl<'d, T: Pin> embedded_hal_async::digital::Wait for Flex<'d, T> { - type WaitForHighFuture<'a> = impl Future> + 'a where Self: 'a; - fn wait_for_high<'a>(&'a mut self) -> Self::WaitForHighFuture<'a> { - self.wait_for_high().map(Ok) + async fn wait_for_high(&mut self) -> Result<(), Self::Error> { + self.wait_for_high().await; + Ok(()) } - type WaitForLowFuture<'a> = impl Future> + 'a where Self: 'a; - fn wait_for_low<'a>(&'a mut self) -> Self::WaitForLowFuture<'a> { - self.wait_for_low().map(Ok) + async fn wait_for_low(&mut self) -> Result<(), Self::Error> { + self.wait_for_low().await; + Ok(()) } - type WaitForRisingEdgeFuture<'a> = impl Future> + 'a where Self: 'a; - fn wait_for_rising_edge<'a>(&'a mut self) -> Self::WaitForRisingEdgeFuture<'a> { - self.wait_for_rising_edge().map(Ok) + async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_rising_edge().await; + Ok(()) } - type WaitForFallingEdgeFuture<'a> = impl Future> + 'a where Self: 'a; - fn wait_for_falling_edge<'a>(&'a mut self) -> Self::WaitForFallingEdgeFuture<'a> { - self.wait_for_falling_edge().map(Ok) + async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_falling_edge().await; + Ok(()) } - type WaitForAnyEdgeFuture<'a> = impl Future> + 'a where Self: 'a; - fn wait_for_any_edge<'a>(&'a mut self) -> Self::WaitForAnyEdgeFuture<'a> { - self.wait_for_any_edge().map(Ok) + async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_any_edge().await; + Ok(()) } } #[cfg(feature = "nightly")] impl<'d, T: Pin> embedded_hal_async::digital::Wait for Input<'d, T> { - type WaitForHighFuture<'a> = impl Future> + 'a where Self: 'a; - fn wait_for_high<'a>(&'a mut self) -> Self::WaitForHighFuture<'a> { - self.wait_for_high().map(Ok) + async fn wait_for_high(&mut self) -> Result<(), Self::Error> { + self.wait_for_high().await; + Ok(()) } - type WaitForLowFuture<'a> = impl Future> + 'a where Self: 'a; - fn wait_for_low<'a>(&'a mut self) -> Self::WaitForLowFuture<'a> { - self.wait_for_low().map(Ok) + async fn wait_for_low(&mut self) -> Result<(), Self::Error> { + self.wait_for_low().await; + Ok(()) } - type WaitForRisingEdgeFuture<'a> = impl Future> + 'a where Self: 'a; - fn wait_for_rising_edge<'a>(&'a mut self) -> Self::WaitForRisingEdgeFuture<'a> { - self.wait_for_rising_edge().map(Ok) + async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_rising_edge().await; + Ok(()) } - type WaitForFallingEdgeFuture<'a> = impl Future> + 'a where Self: 'a; - fn wait_for_falling_edge<'a>(&'a mut self) -> Self::WaitForFallingEdgeFuture<'a> { - self.wait_for_falling_edge().map(Ok) + async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_falling_edge().await; + Ok(()) } - type WaitForAnyEdgeFuture<'a> = impl Future> + 'a where Self: 'a; - fn wait_for_any_edge<'a>(&'a mut self) -> Self::WaitForAnyEdgeFuture<'a> { - self.wait_for_any_edge().map(Ok) + async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_any_edge().await; + Ok(()) + } + } + + #[cfg(feature = "nightly")] + impl<'d, T: Pin> embedded_hal_async::digital::Wait for OutputOpenDrain<'d, T> { + async fn wait_for_high(&mut self) -> Result<(), Self::Error> { + self.wait_for_high().await; + Ok(()) + } + + async fn wait_for_low(&mut self) -> Result<(), Self::Error> { + self.wait_for_low().await; + Ok(()) + } + + async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_rising_edge().await; + Ok(()) + } + + async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_falling_edge().await; + Ok(()) + } + + async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_any_edge().await; + Ok(()) } } } diff --git a/embassy-rp/src/i2c.rs b/embassy-rp/src/i2c.rs new file mode 100644 index 000000000..791c64554 --- /dev/null +++ b/embassy-rp/src/i2c.rs @@ -0,0 +1,856 @@ +use core::future; +use core::marker::PhantomData; +use core::task::Poll; + +use embassy_hal_common::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; +use pac::i2c; + +use crate::gpio::sealed::Pin; +use crate::gpio::AnyPin; +use crate::interrupt::typelevel::{Binding, Interrupt}; +use crate::{interrupt, pac, peripherals, Peripheral}; + +/// I2C error abort reason +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum AbortReason { + /// A bus operation was not acknowledged, e.g. due to the addressed device + /// not being available on the bus or the device not being ready to process + /// requests at the moment + NoAcknowledge, + /// The arbitration was lost, e.g. electrical problems with the clock signal + ArbitrationLoss, + Other(u32), +} + +/// I2C error +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Error { + /// I2C abort with error + Abort(AbortReason), + /// User passed in a read buffer that was 0 length + InvalidReadBufferLength, + /// User passed in a write buffer that was 0 length + InvalidWriteBufferLength, + /// Target i2c address is out of range + AddressOutOfRange(u16), + /// Target i2c address is reserved + AddressReserved(u16), +} + +#[non_exhaustive] +#[derive(Copy, Clone)] +pub struct Config { + pub frequency: u32, +} + +impl Default for Config { + fn default() -> Self { + Self { frequency: 100_000 } + } +} + +const FIFO_SIZE: u8 = 16; + +pub struct I2c<'d, T: Instance, M: Mode> { + phantom: PhantomData<(&'d mut T, M)>, +} + +impl<'d, T: Instance> I2c<'d, T, Blocking> { + pub fn new_blocking( + peri: impl Peripheral

+ 'd, + scl: impl Peripheral

> + 'd, + sda: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + into_ref!(scl, sda); + Self::new_inner(peri, scl.map_into(), sda.map_into(), config) + } +} + +impl<'d, T: Instance> I2c<'d, T, Async> { + pub fn new_async( + peri: impl Peripheral

+ 'd, + scl: impl Peripheral

> + 'd, + sda: impl Peripheral

> + 'd, + _irq: impl Binding>, + config: Config, + ) -> Self { + into_ref!(scl, sda); + + let i2c = Self::new_inner(peri, scl.map_into(), sda.map_into(), config); + + let r = T::regs(); + + // mask everything initially + r.ic_intr_mask().write_value(i2c::regs::IcIntrMask(0)); + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + i2c + } + + /// Calls `f` to check if we are ready or not. + /// If not, `g` is called once the waker is set (to eg enable the required interrupts). + async fn wait_on(&mut self, mut f: F, mut g: G) -> U + where + F: FnMut(&mut Self) -> Poll, + G: FnMut(&mut Self), + { + future::poll_fn(|cx| { + let r = f(self); + + if r.is_pending() { + T::waker().register(cx.waker()); + g(self); + } + r + }) + .await + } + + async fn read_async_internal(&mut self, buffer: &mut [u8], restart: bool, send_stop: bool) -> Result<(), Error> { + if buffer.is_empty() { + return Err(Error::InvalidReadBufferLength); + } + + let p = T::regs(); + + let mut remaining = buffer.len(); + let mut remaining_queue = buffer.len(); + + let mut abort_reason = Ok(()); + + while remaining > 0 { + // Waggle SCK - basically the same as write + let tx_fifo_space = Self::tx_fifo_capacity(); + let mut batch = 0; + + debug_assert!(remaining_queue > 0); + + for _ in 0..remaining_queue.min(tx_fifo_space as usize) { + remaining_queue -= 1; + let last = remaining_queue == 0; + batch += 1; + + p.ic_data_cmd().write(|w| { + w.set_restart(restart && remaining_queue == buffer.len() - 1); + w.set_stop(last && send_stop); + w.set_cmd(true); + }); + } + + // We've either run out of txfifo or just plain finished setting up + // the clocks for the message - either way we need to wait for rx + // data. + + debug_assert!(batch > 0); + let res = self + .wait_on( + |me| { + let rxfifo = Self::rx_fifo_len(); + if let Err(abort_reason) = me.read_and_clear_abort_reason() { + Poll::Ready(Err(abort_reason)) + } else if rxfifo >= batch { + Poll::Ready(Ok(rxfifo)) + } else { + Poll::Pending + } + }, + |_me| { + // Set the read threshold to the number of bytes we're + // expecting so we don't get spurious interrupts. + p.ic_rx_tl().write(|w| w.set_rx_tl(batch - 1)); + + p.ic_intr_mask().modify(|w| { + w.set_m_rx_full(true); + w.set_m_tx_abrt(true); + }); + }, + ) + .await; + + match res { + Err(reason) => { + abort_reason = Err(reason); + break; + } + Ok(rxfifo) => { + // Fetch things from rx fifo. We're assuming we're the only + // rxfifo reader, so nothing else can take things from it. + let rxbytes = (rxfifo as usize).min(remaining); + let received = buffer.len() - remaining; + for b in &mut buffer[received..received + rxbytes] { + *b = p.ic_data_cmd().read().dat(); + } + remaining -= rxbytes; + } + }; + } + + self.wait_stop_det(abort_reason, send_stop).await + } + + async fn write_async_internal( + &mut self, + bytes: impl IntoIterator, + send_stop: bool, + ) -> Result<(), Error> { + let p = T::regs(); + + let mut bytes = bytes.into_iter().peekable(); + + let res = 'xmit: loop { + let tx_fifo_space = Self::tx_fifo_capacity(); + + for _ in 0..tx_fifo_space { + if let Some(byte) = bytes.next() { + let last = bytes.peek().is_none(); + + p.ic_data_cmd().write(|w| { + w.set_stop(last && send_stop); + w.set_cmd(false); + w.set_dat(byte); + }); + } else { + break 'xmit Ok(()); + } + } + + let res = self + .wait_on( + |me| { + if let abort_reason @ Err(_) = me.read_and_clear_abort_reason() { + Poll::Ready(abort_reason) + } else if !Self::tx_fifo_full() { + // resume if there's any space free in the tx fifo + Poll::Ready(Ok(())) + } else { + Poll::Pending + } + }, + |_me| { + // Set tx "free" threshold a little high so that we get + // woken before the fifo completely drains to minimize + // transfer stalls. + p.ic_tx_tl().write(|w| w.set_tx_tl(1)); + + p.ic_intr_mask().modify(|w| { + w.set_m_tx_empty(true); + w.set_m_tx_abrt(true); + }) + }, + ) + .await; + if res.is_err() { + break res; + } + }; + + self.wait_stop_det(res, send_stop).await + } + + /// Helper to wait for a stop bit, for both tx and rx. If we had an abort, + /// then we'll get a hardware-generated stop, otherwise wait for a stop if + /// we're expecting it. + /// + /// Also handles an abort which arises while processing the tx fifo. + async fn wait_stop_det(&mut self, had_abort: Result<(), Error>, do_stop: bool) -> Result<(), Error> { + if had_abort.is_err() || do_stop { + let p = T::regs(); + + let had_abort2 = self + .wait_on( + |me| { + // We could see an abort while processing fifo backlog, + // so handle it here. + let abort = me.read_and_clear_abort_reason(); + if had_abort.is_ok() && abort.is_err() { + Poll::Ready(abort) + } else if p.ic_raw_intr_stat().read().stop_det() { + Poll::Ready(Ok(())) + } else { + Poll::Pending + } + }, + |_me| { + p.ic_intr_mask().modify(|w| { + w.set_m_stop_det(true); + w.set_m_tx_abrt(true); + }); + }, + ) + .await; + p.ic_clr_stop_det().read(); + + had_abort.and(had_abort2) + } else { + had_abort + } + } + + pub async fn read_async(&mut self, addr: u16, buffer: &mut [u8]) -> Result<(), Error> { + Self::setup(addr)?; + self.read_async_internal(buffer, false, true).await + } + + pub async fn write_async(&mut self, addr: u16, bytes: impl IntoIterator) -> Result<(), Error> { + Self::setup(addr)?; + self.write_async_internal(bytes, true).await + } +} + +pub struct InterruptHandler { + _uart: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + // Mask interrupts and wake any task waiting for this interrupt + unsafe fn on_interrupt() { + let i2c = T::regs(); + i2c.ic_intr_mask().write_value(pac::i2c::regs::IcIntrMask::default()); + + T::waker().wake(); + } +} + +impl<'d, T: Instance + 'd, M: Mode> I2c<'d, T, M> { + fn new_inner( + _peri: impl Peripheral

+ 'd, + scl: PeripheralRef<'d, AnyPin>, + sda: PeripheralRef<'d, AnyPin>, + config: Config, + ) -> Self { + into_ref!(_peri); + + assert!(config.frequency <= 1_000_000); + assert!(config.frequency > 0); + + let p = T::regs(); + + let reset = T::reset(); + crate::reset::reset(reset); + crate::reset::unreset_wait(reset); + + p.ic_enable().write(|w| w.set_enable(false)); + + // Select controller mode & speed + p.ic_con().modify(|w| { + // Always use "fast" mode (<= 400 kHz, works fine for standard + // mode too) + w.set_speed(i2c::vals::Speed::FAST); + w.set_master_mode(true); + w.set_ic_slave_disable(true); + w.set_ic_restart_en(true); + w.set_tx_empty_ctrl(true); + }); + + // Set FIFO watermarks to 1 to make things simpler. This is encoded + // by a register value of 0. + p.ic_tx_tl().write(|w| w.set_tx_tl(0)); + p.ic_rx_tl().write(|w| w.set_rx_tl(0)); + + // Configure SCL & SDA pins + scl.io().ctrl().write(|w| w.set_funcsel(3)); + sda.io().ctrl().write(|w| w.set_funcsel(3)); + + scl.pad_ctrl().write(|w| { + w.set_schmitt(true); + w.set_ie(true); + w.set_od(false); + w.set_pue(true); + w.set_pde(false); + }); + sda.pad_ctrl().write(|w| { + w.set_schmitt(true); + w.set_ie(true); + w.set_od(false); + w.set_pue(true); + w.set_pde(false); + }); + + // Configure baudrate + + // There are some subtleties to I2C timing which we are completely + // ignoring here See: + // https://github.com/raspberrypi/pico-sdk/blob/bfcbefafc5d2a210551a4d9d80b4303d4ae0adf7/src/rp2_common/hardware_i2c/i2c.c#L69 + let clk_base = crate::clocks::clk_peri_freq(); + + let period = (clk_base + config.frequency / 2) / config.frequency; + let lcnt = period * 3 / 5; // spend 3/5 (60%) of the period low + let hcnt = period - lcnt; // and 2/5 (40%) of the period high + + // Check for out-of-range divisors: + assert!(hcnt <= 0xffff); + assert!(lcnt <= 0xffff); + assert!(hcnt >= 8); + assert!(lcnt >= 8); + + // Per I2C-bus specification a device in standard or fast mode must + // internally provide a hold time of at least 300ns for the SDA + // signal to bridge the undefined region of the falling edge of SCL. + // A smaller hold time of 120ns is used for fast mode plus. + let sda_tx_hold_count = if config.frequency < 1_000_000 { + // sda_tx_hold_count = clk_base [cycles/s] * 300ns * (1s / + // 1e9ns) Reduce 300/1e9 to 3/1e7 to avoid numbers that don't + // fit in uint. Add 1 to avoid division truncation. + ((clk_base * 3) / 10_000_000) + 1 + } else { + // fast mode plus requires a clk_base > 32MHz + assert!(clk_base >= 32_000_000); + + // sda_tx_hold_count = clk_base [cycles/s] * 120ns * (1s / + // 1e9ns) Reduce 120/1e9 to 3/25e6 to avoid numbers that don't + // fit in uint. Add 1 to avoid division truncation. + ((clk_base * 3) / 25_000_000) + 1 + }; + assert!(sda_tx_hold_count <= lcnt - 2); + + p.ic_fs_scl_hcnt().write(|w| w.set_ic_fs_scl_hcnt(hcnt as u16)); + p.ic_fs_scl_lcnt().write(|w| w.set_ic_fs_scl_lcnt(lcnt as u16)); + p.ic_fs_spklen() + .write(|w| w.set_ic_fs_spklen(if lcnt < 16 { 1 } else { (lcnt / 16) as u8 })); + p.ic_sda_hold() + .modify(|w| w.set_ic_sda_tx_hold(sda_tx_hold_count as u16)); + + // Enable I2C block + p.ic_enable().write(|w| w.set_enable(true)); + + Self { phantom: PhantomData } + } + + fn setup(addr: u16) -> Result<(), Error> { + if addr >= 0x80 { + return Err(Error::AddressOutOfRange(addr)); + } + + if i2c_reserved_addr(addr) { + return Err(Error::AddressReserved(addr)); + } + + let p = T::regs(); + p.ic_enable().write(|w| w.set_enable(false)); + p.ic_tar().write(|w| w.set_ic_tar(addr)); + p.ic_enable().write(|w| w.set_enable(true)); + Ok(()) + } + + #[inline] + fn tx_fifo_full() -> bool { + Self::tx_fifo_capacity() == 0 + } + + #[inline] + fn tx_fifo_capacity() -> u8 { + let p = T::regs(); + FIFO_SIZE - p.ic_txflr().read().txflr() + } + + #[inline] + fn rx_fifo_len() -> u8 { + let p = T::regs(); + p.ic_rxflr().read().rxflr() + } + + fn read_and_clear_abort_reason(&mut self) -> Result<(), Error> { + let p = T::regs(); + let abort_reason = p.ic_tx_abrt_source().read(); + if abort_reason.0 != 0 { + // Note clearing the abort flag also clears the reason, and this + // instance of flag is clear-on-read! Note also the + // IC_CLR_TX_ABRT register always reads as 0. + p.ic_clr_tx_abrt().read(); + + let reason = if abort_reason.abrt_7b_addr_noack() + | abort_reason.abrt_10addr1_noack() + | abort_reason.abrt_10addr2_noack() + { + AbortReason::NoAcknowledge + } else if abort_reason.arb_lost() { + AbortReason::ArbitrationLoss + } else { + AbortReason::Other(abort_reason.0) + }; + + Err(Error::Abort(reason)) + } else { + Ok(()) + } + } + + fn read_blocking_internal(&mut self, read: &mut [u8], restart: bool, send_stop: bool) -> Result<(), Error> { + if read.is_empty() { + return Err(Error::InvalidReadBufferLength); + } + + let p = T::regs(); + let lastindex = read.len() - 1; + for (i, byte) in read.iter_mut().enumerate() { + let first = i == 0; + let last = i == lastindex; + + // wait until there is space in the FIFO to write the next byte + while Self::tx_fifo_full() {} + + p.ic_data_cmd().write(|w| { + w.set_restart(restart && first); + w.set_stop(send_stop && last); + + w.set_cmd(true); + }); + + while Self::rx_fifo_len() == 0 { + self.read_and_clear_abort_reason()?; + } + + *byte = p.ic_data_cmd().read().dat(); + } + + Ok(()) + } + + fn write_blocking_internal(&mut self, write: &[u8], send_stop: bool) -> Result<(), Error> { + if write.is_empty() { + return Err(Error::InvalidWriteBufferLength); + } + + let p = T::regs(); + + for (i, byte) in write.iter().enumerate() { + let last = i == write.len() - 1; + + p.ic_data_cmd().write(|w| { + w.set_stop(send_stop && last); + w.set_dat(*byte); + }); + + // Wait until the transmission of the address/data from the + // internal shift register has completed. For this to function + // correctly, the TX_EMPTY_CTRL flag in IC_CON must be set. The + // TX_EMPTY_CTRL flag was set in i2c_init. + while !p.ic_raw_intr_stat().read().tx_empty() {} + + let abort_reason = self.read_and_clear_abort_reason(); + + if abort_reason.is_err() || (send_stop && last) { + // If the transaction was aborted or if it completed + // successfully wait until the STOP condition has occurred. + + while !p.ic_raw_intr_stat().read().stop_det() {} + + p.ic_clr_stop_det().read().clr_stop_det(); + } + + // Note the hardware issues a STOP automatically on an abort + // condition. Note also the hardware clears RX FIFO as well as + // TX on abort, ecause we set hwparam + // IC_AVOID_RX_FIFO_FLUSH_ON_TX_ABRT to 0. + abort_reason?; + } + Ok(()) + } + + // ========================= + // Blocking public API + // ========================= + + pub fn blocking_read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Error> { + Self::setup(address.into())?; + self.read_blocking_internal(read, true, true) + // Automatic Stop + } + + pub fn blocking_write(&mut self, address: u8, write: &[u8]) -> Result<(), Error> { + Self::setup(address.into())?; + self.write_blocking_internal(write, true) + } + + pub fn blocking_write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error> { + Self::setup(address.into())?; + self.write_blocking_internal(write, false)?; + self.read_blocking_internal(read, true, true) + // Automatic Stop + } +} + +mod eh02 { + use super::*; + + impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::Read for I2c<'d, T, M> { + type Error = Error; + + fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_read(address, buffer) + } + } + + impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::Write for I2c<'d, T, M> { + type Error = Error; + + fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(address, bytes) + } + } + + impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::WriteRead for I2c<'d, T, M> { + type Error = Error; + + fn write_read(&mut self, address: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_write_read(address, bytes, buffer) + } + } + + impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::Transactional for I2c<'d, T, M> { + type Error = Error; + + fn exec( + &mut self, + address: u8, + operations: &mut [embedded_hal_02::blocking::i2c::Operation<'_>], + ) -> Result<(), Self::Error> { + Self::setup(address.into())?; + for i in 0..operations.len() { + let last = i == operations.len() - 1; + match &mut operations[i] { + embedded_hal_02::blocking::i2c::Operation::Read(buf) => { + self.read_blocking_internal(buf, false, last)? + } + embedded_hal_02::blocking::i2c::Operation::Write(buf) => self.write_blocking_internal(buf, last)?, + } + } + Ok(()) + } + } +} + +#[cfg(feature = "unstable-traits")] +mod eh1 { + use super::*; + + impl embedded_hal_1::i2c::Error for Error { + fn kind(&self) -> embedded_hal_1::i2c::ErrorKind { + match *self { + Self::Abort(AbortReason::ArbitrationLoss) => embedded_hal_1::i2c::ErrorKind::ArbitrationLoss, + Self::Abort(AbortReason::NoAcknowledge) => { + embedded_hal_1::i2c::ErrorKind::NoAcknowledge(embedded_hal_1::i2c::NoAcknowledgeSource::Address) + } + Self::Abort(AbortReason::Other(_)) => embedded_hal_1::i2c::ErrorKind::Other, + Self::InvalidReadBufferLength => embedded_hal_1::i2c::ErrorKind::Other, + Self::InvalidWriteBufferLength => embedded_hal_1::i2c::ErrorKind::Other, + Self::AddressOutOfRange(_) => embedded_hal_1::i2c::ErrorKind::Other, + Self::AddressReserved(_) => embedded_hal_1::i2c::ErrorKind::Other, + } + } + } + + impl<'d, T: Instance, M: Mode> embedded_hal_1::i2c::ErrorType for I2c<'d, T, M> { + type Error = Error; + } + + impl<'d, T: Instance, M: Mode> embedded_hal_1::i2c::I2c for I2c<'d, T, M> { + fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_read(address, read) + } + + fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(address, write) + } + + fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_write_read(address, write, read) + } + + fn transaction( + &mut self, + address: u8, + operations: &mut [embedded_hal_1::i2c::Operation<'_>], + ) -> Result<(), Self::Error> { + Self::setup(address.into())?; + for i in 0..operations.len() { + let last = i == operations.len() - 1; + match &mut operations[i] { + embedded_hal_1::i2c::Operation::Read(buf) => self.read_blocking_internal(buf, false, last)?, + embedded_hal_1::i2c::Operation::Write(buf) => self.write_blocking_internal(buf, last)?, + } + } + Ok(()) + } + } +} +#[cfg(all(feature = "unstable-traits", feature = "nightly"))] +mod nightly { + use embedded_hal_1::i2c::Operation; + use embedded_hal_async::i2c::AddressMode; + + use super::*; + + impl<'d, A, T> embedded_hal_async::i2c::I2c for I2c<'d, T, Async> + where + A: AddressMode + Into + 'static, + T: Instance + 'd, + { + async fn read(&mut self, address: A, read: &mut [u8]) -> Result<(), Self::Error> { + let addr: u16 = address.into(); + + Self::setup(addr)?; + self.read_async_internal(read, false, true).await + } + + async fn write(&mut self, address: A, write: &[u8]) -> Result<(), Self::Error> { + let addr: u16 = address.into(); + + Self::setup(addr)?; + self.write_async_internal(write.iter().copied(), true).await + } + + async fn write_read(&mut self, address: A, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> { + let addr: u16 = address.into(); + + Self::setup(addr)?; + self.write_async_internal(write.iter().cloned(), false).await?; + self.read_async_internal(read, false, true).await + } + + async fn transaction(&mut self, address: A, operations: &mut [Operation<'_>]) -> Result<(), Self::Error> { + let addr: u16 = address.into(); + + let mut iterator = operations.iter_mut(); + + while let Some(op) = iterator.next() { + let last = iterator.len() == 0; + + match op { + Operation::Read(buffer) => { + Self::setup(addr)?; + self.read_async_internal(buffer, false, last).await?; + } + Operation::Write(buffer) => { + Self::setup(addr)?; + self.write_async_internal(buffer.into_iter().cloned(), last).await?; + } + } + } + Ok(()) + } + } +} + +fn i2c_reserved_addr(addr: u16) -> bool { + (addr & 0x78) == 0 || (addr & 0x78) == 0x78 +} + +mod sealed { + use embassy_sync::waitqueue::AtomicWaker; + + use crate::interrupt; + + pub trait Instance { + const TX_DREQ: u8; + const RX_DREQ: u8; + + type Interrupt: interrupt::typelevel::Interrupt; + + fn regs() -> crate::pac::i2c::I2c; + fn reset() -> crate::pac::resets::regs::Peripherals; + fn waker() -> &'static AtomicWaker; + } + + pub trait Mode {} + + pub trait SdaPin {} + pub trait SclPin {} +} + +pub trait Mode: sealed::Mode {} + +macro_rules! impl_mode { + ($name:ident) => { + impl sealed::Mode for $name {} + impl Mode for $name {} + }; +} + +pub struct Blocking; +pub struct Async; + +impl_mode!(Blocking); +impl_mode!(Async); + +pub trait Instance: sealed::Instance {} + +macro_rules! impl_instance { + ($type:ident, $irq:ident, $reset:ident, $tx_dreq:expr, $rx_dreq:expr) => { + impl sealed::Instance for peripherals::$type { + const TX_DREQ: u8 = $tx_dreq; + const RX_DREQ: u8 = $rx_dreq; + + type Interrupt = crate::interrupt::typelevel::$irq; + + #[inline] + fn regs() -> pac::i2c::I2c { + pac::$type + } + + #[inline] + fn reset() -> pac::resets::regs::Peripherals { + let mut ret = pac::resets::regs::Peripherals::default(); + ret.$reset(true); + ret + } + + #[inline] + fn waker() -> &'static AtomicWaker { + static WAKER: AtomicWaker = AtomicWaker::new(); + + &WAKER + } + } + impl Instance for peripherals::$type {} + }; +} + +impl_instance!(I2C0, I2C0_IRQ, set_i2c0, 32, 33); +impl_instance!(I2C1, I2C1_IRQ, set_i2c1, 34, 35); + +pub trait SdaPin: sealed::SdaPin + crate::gpio::Pin {} +pub trait SclPin: sealed::SclPin + crate::gpio::Pin {} + +macro_rules! impl_pin { + ($pin:ident, $instance:ident, $function:ident) => { + impl sealed::$function for peripherals::$pin {} + impl $function for peripherals::$pin {} + }; +} + +impl_pin!(PIN_0, I2C0, SdaPin); +impl_pin!(PIN_1, I2C0, SclPin); +impl_pin!(PIN_2, I2C1, SdaPin); +impl_pin!(PIN_3, I2C1, SclPin); +impl_pin!(PIN_4, I2C0, SdaPin); +impl_pin!(PIN_5, I2C0, SclPin); +impl_pin!(PIN_6, I2C1, SdaPin); +impl_pin!(PIN_7, I2C1, SclPin); +impl_pin!(PIN_8, I2C0, SdaPin); +impl_pin!(PIN_9, I2C0, SclPin); +impl_pin!(PIN_10, I2C1, SdaPin); +impl_pin!(PIN_11, I2C1, SclPin); +impl_pin!(PIN_12, I2C0, SdaPin); +impl_pin!(PIN_13, I2C0, SclPin); +impl_pin!(PIN_14, I2C1, SdaPin); +impl_pin!(PIN_15, I2C1, SclPin); +impl_pin!(PIN_16, I2C0, SdaPin); +impl_pin!(PIN_17, I2C0, SclPin); +impl_pin!(PIN_18, I2C1, SdaPin); +impl_pin!(PIN_19, I2C1, SclPin); +impl_pin!(PIN_20, I2C0, SdaPin); +impl_pin!(PIN_21, I2C0, SclPin); +impl_pin!(PIN_22, I2C1, SdaPin); +impl_pin!(PIN_23, I2C1, SclPin); +impl_pin!(PIN_24, I2C0, SdaPin); +impl_pin!(PIN_25, I2C0, SclPin); +impl_pin!(PIN_26, I2C1, SdaPin); +impl_pin!(PIN_27, I2C1, SclPin); +impl_pin!(PIN_28, I2C0, SdaPin); +impl_pin!(PIN_29, I2C0, SclPin); diff --git a/embassy-rp/src/interrupt.rs b/embassy-rp/src/interrupt.rs deleted file mode 100644 index f21a5433b..000000000 --- a/embassy-rp/src/interrupt.rs +++ /dev/null @@ -1,36 +0,0 @@ -//! Interrupt management -//! -//! This module implements an API for managing interrupts compatible with -//! nrf_softdevice::interrupt. Intended for switching between the two at compile-time. - -// Re-exports -use embassy_cortex_m::interrupt::_export::declare; -pub use embassy_cortex_m::interrupt::*; - -use crate::pac::Interrupt as InterruptEnum; -declare!(TIMER_IRQ_0); -declare!(TIMER_IRQ_1); -declare!(TIMER_IRQ_2); -declare!(TIMER_IRQ_3); -declare!(PWM_IRQ_WRAP); -declare!(USBCTRL_IRQ); -declare!(XIP_IRQ); -declare!(PIO0_IRQ_0); -declare!(PIO0_IRQ_1); -declare!(PIO1_IRQ_0); -declare!(PIO1_IRQ_1); -declare!(DMA_IRQ_0); -declare!(DMA_IRQ_1); -declare!(IO_IRQ_BANK0); -declare!(IO_IRQ_QSPI); -declare!(SIO_IRQ_PROC0); -declare!(SIO_IRQ_PROC1); -declare!(CLOCKS_IRQ); -declare!(SPI0_IRQ); -declare!(SPI1_IRQ); -declare!(UART0_IRQ); -declare!(UART1_IRQ); -declare!(ADC_IRQ_FIFO); -declare!(I2C0_IRQ); -declare!(I2C1_IRQ); -declare!(RTC_IRQ); diff --git a/embassy-rp/src/intrinsics.rs b/embassy-rp/src/intrinsics.rs new file mode 100644 index 000000000..5b9c127ba --- /dev/null +++ b/embassy-rp/src/intrinsics.rs @@ -0,0 +1,477 @@ +#![macro_use] + +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/intrinsics.rs + +/// Generate a series of aliases for an intrinsic function. +macro_rules! intrinsics_aliases { + ( + extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty, + ) => {}; + ( + unsafe extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty, + ) => {}; + + ( + extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty, + $alias:ident + $($rest:ident)* + ) => { + #[cfg(all(target_arch = "arm", feature = "intrinsics"))] + intrinsics! { + extern $abi fn $alias( $($argname: $ty),* ) -> $ret { + $name($($argname),*) + } + } + + intrinsics_aliases! { + extern $abi fn $name( $($argname: $ty),* ) -> $ret, + $($rest)* + } + }; + + ( + unsafe extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty, + $alias:ident + $($rest:ident)* + ) => { + #[cfg(all(target_arch = "arm", feature = "intrinsics"))] + intrinsics! { + unsafe extern $abi fn $alias( $($argname: $ty),* ) -> $ret { + $name($($argname),*) + } + } + + intrinsics_aliases! { + unsafe extern $abi fn $name( $($argname: $ty),* ) -> $ret, + $($rest)* + } + }; +} + +/// The macro used to define overridden intrinsics. +/// +/// This is heavily inspired by the macro used by compiler-builtins. The idea +/// is to abstract anything special that needs to be done to override an +/// intrinsic function. Intrinsic generation is disabled for non-ARM targets +/// so things like CI and docs generation do not have problems. Additionally +/// they can be disabled by disabling the crate feature `intrinsics` for +/// testing or comparing performance. +/// +/// Like the compiler-builtins macro, it accepts a series of functions that +/// looks like normal Rust code: +/// +/// ```rust,ignore +/// intrinsics! { +/// extern "C" fn foo(a: i32) -> u32 { +/// // ... +/// } +/// #[nonstandard_attribute] +/// extern "C" fn bar(a: i32) -> u32 { +/// // ... +/// } +/// } +/// ``` +/// +/// Each function can also be decorated with nonstandard attributes to control +/// additional behaviour: +/// +/// * `slower_than_default` - indicates that the override is slower than the +/// default implementation. Currently this just disables the override +/// entirely. +/// * `bootrom_v2` - indicates that the override is only available +/// on a V2 bootrom or higher. Only enabled when the feature +/// `rom-v2-intrinsics` is set. +/// * `alias` - accepts a list of names to alias the intrinsic to. +/// * `aeabi` - accepts a list of ARM EABI names to alias to. +/// +macro_rules! intrinsics { + () => {}; + + ( + #[slower_than_default] + $(#[$($attr:tt)*])* + extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty { + $($body:tt)* + } + + $($rest:tt)* + ) => { + // Not exported, but defined so the actual implementation is + // considered used + #[allow(dead_code)] + fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + + intrinsics!($($rest)*); + }; + + ( + #[bootrom_v2] + $(#[$($attr:tt)*])* + extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty { + $($body:tt)* + } + + $($rest:tt)* + ) => { + // Not exported, but defined so the actual implementation is + // considered used + #[cfg(not(feature = "rom-v2-intrinsics"))] + #[allow(dead_code)] + fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + + #[cfg(feature = "rom-v2-intrinsics")] + intrinsics! { + $(#[$($attr)*])* + extern $abi fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + } + + intrinsics!($($rest)*); + }; + + ( + #[alias = $($alias:ident),*] + $(#[$($attr:tt)*])* + extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty { + $($body:tt)* + } + + $($rest:tt)* + ) => { + intrinsics! { + $(#[$($attr)*])* + extern $abi fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + } + + intrinsics_aliases! { + extern $abi fn $name( $($argname: $ty),* ) -> $ret, + $($alias) * + } + + intrinsics!($($rest)*); + }; + + ( + #[alias = $($alias:ident),*] + $(#[$($attr:tt)*])* + unsafe extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty { + $($body:tt)* + } + + $($rest:tt)* + ) => { + intrinsics! { + $(#[$($attr)*])* + unsafe extern $abi fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + } + + intrinsics_aliases! { + unsafe extern $abi fn $name( $($argname: $ty),* ) -> $ret, + $($alias) * + } + + intrinsics!($($rest)*); + }; + + ( + #[aeabi = $($alias:ident),*] + $(#[$($attr:tt)*])* + extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty { + $($body:tt)* + } + + $($rest:tt)* + ) => { + intrinsics! { + $(#[$($attr)*])* + extern $abi fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + } + + intrinsics_aliases! { + extern "aapcs" fn $name( $($argname: $ty),* ) -> $ret, + $($alias) * + } + + intrinsics!($($rest)*); + }; + + ( + $(#[$($attr:tt)*])* + extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty { + $($body:tt)* + } + + $($rest:tt)* + ) => { + #[cfg(all(target_arch = "arm", feature = "intrinsics"))] + $(#[$($attr)*])* + extern $abi fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + + #[cfg(all(target_arch = "arm", feature = "intrinsics"))] + mod $name { + #[no_mangle] + $(#[$($attr)*])* + pub extern $abi fn $name( $($argname: $ty),* ) -> $ret { + super::$name($($argname),*) + } + } + + // Not exported, but defined so the actual implementation is + // considered used + #[cfg(not(all(target_arch = "arm", feature = "intrinsics")))] + #[allow(dead_code)] + fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + + intrinsics!($($rest)*); + }; + + ( + $(#[$($attr:tt)*])* + unsafe extern $abi:tt fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty { + $($body:tt)* + } + + $($rest:tt)* + ) => { + #[cfg(all(target_arch = "arm", feature = "intrinsics"))] + $(#[$($attr)*])* + unsafe extern $abi fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + + #[cfg(all(target_arch = "arm", feature = "intrinsics"))] + mod $name { + #[no_mangle] + $(#[$($attr)*])* + pub unsafe extern $abi fn $name( $($argname: $ty),* ) -> $ret { + super::$name($($argname),*) + } + } + + // Not exported, but defined so the actual implementation is + // considered used + #[cfg(not(all(target_arch = "arm", feature = "intrinsics")))] + #[allow(dead_code)] + unsafe fn $name( $($argname: $ty),* ) -> $ret { + $($body)* + } + + intrinsics!($($rest)*); + }; +} + +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/sio.rs + +// This takes advantage of how AAPCS defines a 64-bit return on 32-bit registers +// by packing it into r0[0:31] and r1[32:63]. So all we need to do is put +// the remainder in the high order 32 bits of a 64 bit result. We can also +// alias the division operators to these for a similar reason r0 is the +// result either way and r1 a scratch register, so the caller can't assume it +// retains the argument value. +#[cfg(target_arch = "arm")] +core::arch::global_asm!( + ".macro hwdivider_head", + "ldr r2, =(0xd0000000)", // SIO_BASE + // Check the DIRTY state of the divider by shifting it into the C + // status bit. + "ldr r3, [r2, #0x078]", // DIV_CSR + "lsrs r3, #2", // DIRTY = 1, so shift 2 down + // We only need to save the state when DIRTY, otherwise we can just do the + // division directly. + "bcs 2f", + "1:", + // Do the actual division now, we're either not DIRTY, or we've saved the + // state and branched back here so it's safe now. + ".endm", + ".macro hwdivider_tail", + // 8 cycle delay to wait for the result. Each branch takes two cycles + // and fits into a 2-byte Thumb instruction, so this is smaller than + // 8 NOPs. + "b 3f", + "3: b 3f", + "3: b 3f", + "3: b 3f", + "3:", + // Read the quotient last, since that's what clears the dirty flag. + "ldr r1, [r2, #0x074]", // DIV_REMAINDER + "ldr r0, [r2, #0x070]", // DIV_QUOTIENT + // Either return to the caller or back to the state restore. + "bx lr", + "2:", + // Since we can't save the signed-ness of the calculation, we have to make + // sure that there's at least an 8 cycle delay before we read the result. + // The push takes 5 cycles, and we've already spent at least 7 checking + // the DIRTY state to get here. + "push {{r4-r6, lr}}", + // Read the quotient last, since that's what clears the dirty flag. + "ldr r3, [r2, #0x060]", // DIV_UDIVIDEND + "ldr r4, [r2, #0x064]", // DIV_UDIVISOR + "ldr r5, [r2, #0x074]", // DIV_REMAINDER + "ldr r6, [r2, #0x070]", // DIV_QUOTIENT + // If we get interrupted here (before a write sets the DIRTY flag) it's + // fine, since we have the full state, so the interruptor doesn't have to + // restore it. Once the write happens and the DIRTY flag is set, the + // interruptor becomes responsible for restoring our state. + "bl 1b", + // If we are interrupted here, then the interruptor will start an incorrect + // calculation using a wrong divisor, but we'll restore the divisor and + // result ourselves correctly. This sets DIRTY, so any interruptor will + // save the state. + "str r3, [r2, #0x060]", // DIV_UDIVIDEND + // If we are interrupted here, the the interruptor may start the + // calculation using incorrectly signed inputs, but we'll restore the + // result ourselves. This sets DIRTY, so any interruptor will save the + // state. + "str r4, [r2, #0x064]", // DIV_UDIVISOR + // If we are interrupted here, the interruptor will have restored + // everything but the quotient may be wrongly signed. If the calculation + // started by the above writes is still ongoing it is stopped, so it won't + // replace the result we're restoring. DIRTY and READY set, but only + // DIRTY matters to make the interruptor save the state. + "str r5, [r2, #0x074]", // DIV_REMAINDER + // State fully restored after the quotient write. This sets both DIRTY + // and READY, so whatever we may have interrupted can read the result. + "str r6, [r2, #0x070]", // DIV_QUOTIENT + "pop {{r4-r6, pc}}", + ".endm", +); + +macro_rules! division_function { + ( + $name:ident $($intrinsic:ident)* ( $argty:ty ) { + $($begin:literal),+ + } + ) => { + #[cfg(all(target_arch = "arm", feature = "intrinsics"))] + core::arch::global_asm!( + // Mangle the name slightly, since this is a global symbol. + concat!(".section .text._erphal_", stringify!($name)), + concat!(".global _erphal_", stringify!($name)), + concat!(".type _erphal_", stringify!($name), ", %function"), + ".align 2", + concat!("_erphal_", stringify!($name), ":"), + $( + concat!(".global ", stringify!($intrinsic)), + concat!(".type ", stringify!($intrinsic), ", %function"), + concat!(stringify!($intrinsic), ":"), + )* + + "hwdivider_head", + $($begin),+ , + "hwdivider_tail", + ); + + #[cfg(all(target_arch = "arm", not(feature = "intrinsics")))] + core::arch::global_asm!( + // Mangle the name slightly, since this is a global symbol. + concat!(".section .text._erphal_", stringify!($name)), + concat!(".global _erphal_", stringify!($name)), + concat!(".type _erphal_", stringify!($name), ", %function"), + ".align 2", + concat!("_erphal_", stringify!($name), ":"), + + "hwdivider_head", + $($begin),+ , + "hwdivider_tail", + ); + + #[cfg(target_arch = "arm")] + extern "aapcs" { + // Connect a local name to global symbol above through FFI. + #[link_name = concat!("_erphal_", stringify!($name)) ] + fn $name(n: $argty, d: $argty) -> u64; + } + + #[cfg(not(target_arch = "arm"))] + #[allow(unused_variables)] + unsafe fn $name(n: $argty, d: $argty) -> u64 { 0 } + }; +} + +division_function! { + unsigned_divmod __aeabi_uidivmod __aeabi_uidiv ( u32 ) { + "str r0, [r2, #0x060]", // DIV_UDIVIDEND + "str r1, [r2, #0x064]" // DIV_UDIVISOR + } +} + +division_function! { + signed_divmod __aeabi_idivmod __aeabi_idiv ( i32 ) { + "str r0, [r2, #0x068]", // DIV_SDIVIDEND + "str r1, [r2, #0x06c]" // DIV_SDIVISOR + } +} + +fn divider_unsigned(n: u32, d: u32) -> DivResult { + let packed = unsafe { unsigned_divmod(n, d) }; + DivResult { + quotient: packed as u32, + remainder: (packed >> 32) as u32, + } +} + +fn divider_signed(n: i32, d: i32) -> DivResult { + let packed = unsafe { signed_divmod(n, d) }; + // Double casts to avoid sign extension + DivResult { + quotient: packed as u32 as i32, + remainder: (packed >> 32) as u32 as i32, + } +} + +/// Result of divide/modulo operation +struct DivResult { + /// The quotient of divide/modulo operation + pub quotient: T, + /// The remainder of divide/modulo operation + pub remainder: T, +} + +intrinsics! { + extern "C" fn __udivsi3(n: u32, d: u32) -> u32 { + divider_unsigned(n, d).quotient + } + + extern "C" fn __umodsi3(n: u32, d: u32) -> u32 { + divider_unsigned(n, d).remainder + } + + extern "C" fn __udivmodsi4(n: u32, d: u32, rem: Option<&mut u32>) -> u32 { + let quo_rem = divider_unsigned(n, d); + if let Some(rem) = rem { + *rem = quo_rem.remainder; + } + quo_rem.quotient + } + + extern "C" fn __divsi3(n: i32, d: i32) -> i32 { + divider_signed(n, d).quotient + } + + extern "C" fn __modsi3(n: i32, d: i32) -> i32 { + divider_signed(n, d).remainder + } + + extern "C" fn __divmodsi4(n: i32, d: i32, rem: &mut i32) -> i32 { + let quo_rem = divider_signed(n, d); + *rem = quo_rem.remainder; + quo_rem.quotient + } +} diff --git a/embassy-rp/src/lib.rs b/embassy-rp/src/lib.rs index 8c053a4f7..4f205a16e 100644 --- a/embassy-rp/src/lib.rs +++ b/embassy-rp/src/lib.rs @@ -1,28 +1,111 @@ #![no_std] -#![cfg_attr(feature = "nightly", feature(generic_associated_types, type_alias_impl_trait))] +#![cfg_attr(feature = "nightly", feature(async_fn_in_trait, impl_trait_projections))] // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; +#[cfg(feature = "critical-section-impl")] +mod critical_section_impl; + +mod intrinsics; + +pub mod adc; +pub mod clocks; pub mod dma; +pub mod flash; +mod float; pub mod gpio; -pub mod interrupt; +pub mod i2c; +pub mod multicore; +pub mod pwm; +mod reset; +pub mod rom_data; +pub mod rtc; pub mod spi; +#[cfg(feature = "time-driver")] pub mod timer; pub mod uart; +#[cfg(feature = "nightly")] +pub mod usb; +pub mod watchdog; -mod clocks; -mod reset; +// PIO +// TODO: move `pio_instr_util` and `relocate` to inside `pio` +pub mod pio; +pub mod pio_instr_util; +pub mod relocate; // Reexports - -pub use embassy_cortex_m::executor; -pub use embassy_cortex_m::interrupt::_export::interrupt; pub use embassy_hal_common::{into_ref, Peripheral, PeripheralRef}; #[cfg(feature = "unstable-pac")] -pub use rp2040_pac2 as pac; +pub use rp_pac as pac; #[cfg(not(feature = "unstable-pac"))] -pub(crate) use rp2040_pac2 as pac; +pub(crate) use rp_pac as pac; + +#[cfg(feature = "rt")] +pub use crate::pac::NVIC_PRIO_BITS; + +embassy_hal_common::interrupt_mod!( + TIMER_IRQ_0, + TIMER_IRQ_1, + TIMER_IRQ_2, + TIMER_IRQ_3, + PWM_IRQ_WRAP, + USBCTRL_IRQ, + XIP_IRQ, + PIO0_IRQ_0, + PIO0_IRQ_1, + PIO1_IRQ_0, + PIO1_IRQ_1, + DMA_IRQ_0, + DMA_IRQ_1, + IO_IRQ_BANK0, + IO_IRQ_QSPI, + SIO_IRQ_PROC0, + SIO_IRQ_PROC1, + CLOCKS_IRQ, + SPI0_IRQ, + SPI1_IRQ, + UART0_IRQ, + UART1_IRQ, + ADC_IRQ_FIFO, + I2C0_IRQ, + I2C1_IRQ, + RTC_IRQ, + SWI_IRQ_0, + SWI_IRQ_1, + SWI_IRQ_2, + SWI_IRQ_3, + SWI_IRQ_4, + SWI_IRQ_5, +); + +/// Macro to bind interrupts to handlers. +/// +/// This defines the right interrupt handlers, and creates a unit struct (like `struct Irqs;`) +/// and implements the right [`Binding`]s for it. You can pass this struct to drivers to +/// prove at compile-time that the right interrupts have been bound. +// developer note: this macro can't be in `embassy-hal-common` due to the use of `$crate`. +#[macro_export] +macro_rules! bind_interrupts { + ($vis:vis struct $name:ident { $($irq:ident => $($handler:ty),*;)* }) => { + $vis struct $name; + + $( + #[allow(non_snake_case)] + #[no_mangle] + unsafe extern "C" fn $irq() { + $( + <$handler as $crate::interrupt::typelevel::Handler<$crate::interrupt::typelevel::$irq>>::on_interrupt(); + )* + } + + $( + unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {} + )* + )* + }; +} embassy_hal_common::peripherals! { PIN_0, @@ -68,6 +151,9 @@ embassy_hal_common::peripherals! { SPI0, SPI1, + I2C0, + I2C1, + DMA_CH0, DMA_CH1, DMA_CH2, @@ -80,32 +166,133 @@ embassy_hal_common::peripherals! { DMA_CH9, DMA_CH10, DMA_CH11, + + PWM_CH0, + PWM_CH1, + PWM_CH2, + PWM_CH3, + PWM_CH4, + PWM_CH5, + PWM_CH6, + PWM_CH7, + + USB, + + RTC, + + FLASH, + + ADC, + + CORE1, + + PIO0, + PIO1, + + WATCHDOG, } -#[link_section = ".boot2"] -#[used] -static BOOT2: [u8; 256] = *include_bytes!("boot2.bin"); +macro_rules! select_bootloader { + ( $( $feature:literal => $loader:ident, )+ default => $default:ident ) => { + $( + #[cfg(feature = $feature)] + #[link_section = ".boot2"] + #[used] + static BOOT2: [u8; 256] = rp2040_boot2::$loader; + )* + + #[cfg(not(any( $( feature = $feature),* )))] + #[link_section = ".boot2"] + #[used] + static BOOT2: [u8; 256] = rp2040_boot2::$default; + } +} + +select_bootloader! { + "boot2-at25sf128a" => BOOT_LOADER_AT25SF128A, + "boot2-gd25q64cs" => BOOT_LOADER_GD25Q64CS, + "boot2-generic-03h" => BOOT_LOADER_GENERIC_03H, + "boot2-is25lp080" => BOOT_LOADER_IS25LP080, + "boot2-ram-memcpy" => BOOT_LOADER_RAM_MEMCPY, + "boot2-w25q080" => BOOT_LOADER_W25Q080, + "boot2-w25x10cl" => BOOT_LOADER_W25X10CL, + default => BOOT_LOADER_W25Q080 +} pub mod config { + use crate::clocks::ClockConfig; + #[non_exhaustive] - pub struct Config {} + pub struct Config { + pub clocks: ClockConfig, + } impl Default for Config { fn default() -> Self { - Self {} + Self { + clocks: ClockConfig::crystal(12_000_000), + } + } + } + + impl Config { + pub fn new(clocks: ClockConfig) -> Self { + Self { clocks } } } } -pub fn init(_config: config::Config) -> Peripherals { +pub fn init(config: config::Config) -> Peripherals { // Do this first, so that it panics if user is calling `init` a second time // before doing anything important. let peripherals = Peripherals::take(); unsafe { - clocks::init(); + clocks::init(config.clocks); + #[cfg(feature = "time-driver")] timer::init(); + dma::init(); + gpio::init(); } peripherals } + +/// Extension trait for PAC regs, adding atomic xor/bitset/bitclear writes. +trait RegExt { + fn write_xor(&self, f: impl FnOnce(&mut T) -> R) -> R; + fn write_set(&self, f: impl FnOnce(&mut T) -> R) -> R; + fn write_clear(&self, f: impl FnOnce(&mut T) -> R) -> R; +} + +impl RegExt for pac::common::Reg { + fn write_xor(&self, f: impl FnOnce(&mut T) -> R) -> R { + let mut val = Default::default(); + let res = f(&mut val); + unsafe { + let ptr = (self.as_ptr() as *mut u8).add(0x1000) as *mut T; + ptr.write_volatile(val); + } + res + } + + fn write_set(&self, f: impl FnOnce(&mut T) -> R) -> R { + let mut val = Default::default(); + let res = f(&mut val); + unsafe { + let ptr = (self.as_ptr() as *mut u8).add(0x2000) as *mut T; + ptr.write_volatile(val); + } + res + } + + fn write_clear(&self, f: impl FnOnce(&mut T) -> R) -> R { + let mut val = Default::default(); + let res = f(&mut val); + unsafe { + let ptr = (self.as_ptr() as *mut u8).add(0x3000) as *mut T; + ptr.write_volatile(val); + } + res + } +} diff --git a/embassy-rp/src/multicore.rs b/embassy-rp/src/multicore.rs new file mode 100644 index 000000000..468e8470a --- /dev/null +++ b/embassy-rp/src/multicore.rs @@ -0,0 +1,327 @@ +//! Multicore support +//! +//! This module handles setup of the 2nd cpu core on the rp2040, which we refer to as core1. +//! It provides functionality for setting up the stack, and starting core1. +//! +//! The entrypoint for core1 can be any function that never returns, including closures. +//! +//! Enable the `critical-section-impl` feature in embassy-rp when sharing data across cores using +//! the `embassy-sync` primitives and `CriticalSectionRawMutex`. +//! +//! # Usage +//! +//! ```no_run +//! # #![feature(type_alias_impl_trait)] +//! use embassy_rp::multicore::Stack; +//! use static_cell::StaticCell; +//! use embassy_executor::Executor; +//! +//! static mut CORE1_STACK: Stack<4096> = Stack::new(); +//! static EXECUTOR0: StaticCell = StaticCell::new(); +//! static EXECUTOR1: StaticCell = StaticCell::new(); +//! +//! # // workaround weird error: `main` function not found in crate `rust_out` +//! # let _ = (); +//! +//! #[embassy_executor::task] +//! async fn core0_task() { +//! // ... +//! } +//! +//! #[embassy_executor::task] +//! async fn core1_task() { +//! // ... +//! } +//! +//! #[cortex_m_rt::entry] +//! fn main() -> ! { +//! let p = embassy_rp::init(Default::default()); +//! +//! embassy_rp::multicore::spawn_core1(p.CORE1, unsafe { &mut CORE1_STACK }, move || { +//! let executor1 = EXECUTOR1.init(Executor::new()); +//! executor1.run(|spawner| spawner.spawn(core1_task()).unwrap()); +//! }); +//! +//! let executor0 = EXECUTOR0.init(Executor::new()); +//! executor0.run(|spawner| spawner.spawn(core0_task()).unwrap()) +//! } +//! ``` + +use core::mem::ManuallyDrop; +use core::sync::atomic::{compiler_fence, AtomicBool, Ordering}; + +use crate::interrupt::InterruptExt; +use crate::peripherals::CORE1; +use crate::{gpio, interrupt, pac}; + +const PAUSE_TOKEN: u32 = 0xDEADBEEF; +const RESUME_TOKEN: u32 = !0xDEADBEEF; +static IS_CORE1_INIT: AtomicBool = AtomicBool::new(false); + +#[inline(always)] +fn install_stack_guard(stack_bottom: *mut usize) { + let core = unsafe { cortex_m::Peripherals::steal() }; + + // Trap if MPU is already configured + if core.MPU.ctrl.read() != 0 { + cortex_m::asm::udf(); + } + + // The minimum we can protect is 32 bytes on a 32 byte boundary, so round up which will + // just shorten the valid stack range a tad. + let addr = (stack_bottom as u32 + 31) & !31; + // Mask is 1 bit per 32 bytes of the 256 byte range... clear the bit for the segment we want + let subregion_select = 0xff ^ (1 << ((addr >> 5) & 7)); + unsafe { + core.MPU.ctrl.write(5); // enable mpu with background default map + core.MPU.rbar.write((addr & !0xff) | 0x8); + core.MPU.rasr.write( + 1 // enable region + | (0x7 << 1) // size 2^(7 + 1) = 256 + | (subregion_select << 8) + | 0x10000000, // XN = disable instruction fetch; no other bits means no permissions + ); + } +} + +#[inline(always)] +fn core1_setup(stack_bottom: *mut usize) { + install_stack_guard(stack_bottom); + unsafe { + gpio::init(); + } +} + +/// Data type for a properly aligned stack of N bytes +#[repr(C, align(32))] +pub struct Stack { + /// Memory to be used for the stack + pub mem: [u8; SIZE], +} + +impl Stack { + /// Construct a stack of length SIZE, initialized to 0 + pub const fn new() -> Stack { + Stack { mem: [0_u8; SIZE] } + } +} + +#[cfg(feature = "rt")] +#[interrupt] +#[link_section = ".data.ram_func"] +unsafe fn SIO_IRQ_PROC1() { + let sio = pac::SIO; + // Clear IRQ + sio.fifo().st().write(|w| w.set_wof(false)); + + while sio.fifo().st().read().vld() { + // Pause CORE1 execution and disable interrupts + if fifo_read_wfe() == PAUSE_TOKEN { + cortex_m::interrupt::disable(); + // Signal to CORE0 that execution is paused + fifo_write(PAUSE_TOKEN); + // Wait for `resume` signal from CORE0 + while fifo_read_wfe() != RESUME_TOKEN { + cortex_m::asm::nop(); + } + cortex_m::interrupt::enable(); + // Signal to CORE0 that execution is resumed + fifo_write(RESUME_TOKEN); + } + } +} + +/// Spawn a function on this core +pub fn spawn_core1(_core1: CORE1, stack: &'static mut Stack, entry: F) +where + F: FnOnce() -> bad::Never + Send + 'static, +{ + // The first two ignored `u64` parameters are there to take up all of the registers, + // which means that the rest of the arguments are taken from the stack, + // where we're able to put them from core 0. + extern "C" fn core1_startup bad::Never>( + _: u64, + _: u64, + entry: *mut ManuallyDrop, + stack_bottom: *mut usize, + ) -> ! { + core1_setup(stack_bottom); + + let entry = unsafe { ManuallyDrop::take(&mut *entry) }; + + // make sure the preceding read doesn't get reordered past the following fifo write + compiler_fence(Ordering::SeqCst); + + // Signal that it's safe for core 0 to get rid of the original value now. + fifo_write(1); + + IS_CORE1_INIT.store(true, Ordering::Release); + // Enable fifo interrupt on CORE1 for `pause` functionality. + unsafe { interrupt::SIO_IRQ_PROC1.enable() }; + + entry() + } + + // Reset the core + let psm = pac::PSM; + psm.frce_off().modify(|w| w.set_proc1(true)); + while !psm.frce_off().read().proc1() { + cortex_m::asm::nop(); + } + psm.frce_off().modify(|w| w.set_proc1(false)); + + // The ARM AAPCS ABI requires 8-byte stack alignment. + // #[align] on `struct Stack` ensures the bottom is aligned, but the top could still be + // unaligned if the user chooses a stack size that's not multiple of 8. + // So, we round down to the next multiple of 8. + let stack_words = stack.mem.len() / 8 * 2; + let mem = unsafe { core::slice::from_raw_parts_mut(stack.mem.as_mut_ptr() as *mut usize, stack_words) }; + + // Set up the stack + let mut stack_ptr = unsafe { mem.as_mut_ptr().add(mem.len()) }; + + // We don't want to drop this, since it's getting moved to the other core. + let mut entry = ManuallyDrop::new(entry); + + // Push the arguments to `core1_startup` onto the stack. + unsafe { + // Push `stack_bottom`. + stack_ptr = stack_ptr.sub(1); + stack_ptr.cast::<*mut usize>().write(mem.as_mut_ptr()); + + // Push `entry`. + stack_ptr = stack_ptr.sub(1); + stack_ptr.cast::<*mut ManuallyDrop>().write(&mut entry); + } + + // Make sure the compiler does not reorder the stack writes after to after the + // below FIFO writes, which would result in them not being seen by the second + // core. + // + // From the compiler perspective, this doesn't guarantee that the second core + // actually sees those writes. However, we know that the RP2040 doesn't have + // memory caches, and writes happen in-order. + compiler_fence(Ordering::Release); + + let p = unsafe { cortex_m::Peripherals::steal() }; + let vector_table = p.SCB.vtor.read(); + + // After reset, core 1 is waiting to receive commands over FIFO. + // This is the sequence to have it jump to some code. + let cmd_seq = [ + 0, + 0, + 1, + vector_table as usize, + stack_ptr as usize, + core1_startup:: as usize, + ]; + + let mut seq = 0; + let mut fails = 0; + loop { + let cmd = cmd_seq[seq] as u32; + if cmd == 0 { + fifo_drain(); + cortex_m::asm::sev(); + } + fifo_write(cmd); + + let response = fifo_read(); + if cmd == response { + seq += 1; + } else { + seq = 0; + fails += 1; + if fails > 16 { + // The second core isn't responding, and isn't going to take the entrypoint + panic!("CORE1 not responding"); + } + } + if seq >= cmd_seq.len() { + break; + } + } + + // Wait until the other core has copied `entry` before returning. + fifo_read(); +} + +/// Pause execution on CORE1. +pub fn pause_core1() { + if IS_CORE1_INIT.load(Ordering::Acquire) { + fifo_write(PAUSE_TOKEN); + // Wait for CORE1 to signal it has paused execution. + while fifo_read() != PAUSE_TOKEN {} + } +} + +/// Resume CORE1 execution. +pub fn resume_core1() { + if IS_CORE1_INIT.load(Ordering::Acquire) { + fifo_write(RESUME_TOKEN); + // Wait for CORE1 to signal it has resumed execution. + while fifo_read() != RESUME_TOKEN {} + } +} + +// Push a value to the inter-core FIFO, block until space is available +#[inline(always)] +fn fifo_write(value: u32) { + let sio = pac::SIO; + // Wait for the FIFO to have enough space + while !sio.fifo().st().read().rdy() { + cortex_m::asm::nop(); + } + sio.fifo().wr().write_value(value); + // Fire off an event to the other core. + // This is required as the other core may be `wfe` (waiting for event) + cortex_m::asm::sev(); +} + +// Pop a value from inter-core FIFO, block until available +#[inline(always)] +fn fifo_read() -> u32 { + let sio = pac::SIO; + // Wait until FIFO has data + while !sio.fifo().st().read().vld() { + cortex_m::asm::nop(); + } + sio.fifo().rd().read() +} + +// Pop a value from inter-core FIFO, `wfe` until available +#[inline(always)] +#[allow(unused)] +fn fifo_read_wfe() -> u32 { + let sio = pac::SIO; + // Wait until FIFO has data + while !sio.fifo().st().read().vld() { + cortex_m::asm::wfe(); + } + sio.fifo().rd().read() +} + +// Drain inter-core FIFO +#[inline(always)] +fn fifo_drain() { + let sio = pac::SIO; + while sio.fifo().st().read().vld() { + let _ = sio.fifo().rd().read(); + } +} + +// https://github.com/nvzqz/bad-rs/blob/master/src/never.rs +mod bad { + pub(crate) type Never = ::Output; + + pub trait HasOutput { + type Output; + } + + impl HasOutput for fn() -> O { + type Output = O; + } + + type F = fn() -> !; +} diff --git a/embassy-rp/src/pio.rs b/embassy-rp/src/pio.rs new file mode 100644 index 000000000..72a2f44ed --- /dev/null +++ b/embassy-rp/src/pio.rs @@ -0,0 +1,1052 @@ +use core::future::Future; +use core::marker::PhantomData; +use core::pin::Pin as FuturePin; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::{Context, Poll}; + +use atomic_polyfill::{AtomicU32, AtomicU8}; +use embassy_hal_common::{into_ref, Peripheral, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; +use fixed::types::extra::U8; +use fixed::FixedU32; +use pac::io::vals::Gpio0ctrlFuncsel; +use pac::pio::vals::SmExecctrlStatusSel; +use pio::{SideSet, Wrap}; + +use crate::dma::{Channel, Transfer, Word}; +use crate::gpio::sealed::Pin as SealedPin; +use crate::gpio::{self, AnyPin, Drive, Level, Pull, SlewRate}; +use crate::interrupt::typelevel::{Binding, Handler, Interrupt}; +use crate::pac::dma::vals::TreqSel; +use crate::relocate::RelocatedProgram; +use crate::{pac, peripherals, pio_instr_util, RegExt}; + +pub struct Wakers([AtomicWaker; 12]); + +impl Wakers { + #[inline(always)] + fn fifo_in(&self) -> &[AtomicWaker] { + &self.0[0..4] + } + #[inline(always)] + fn fifo_out(&self) -> &[AtomicWaker] { + &self.0[4..8] + } + #[inline(always)] + fn irq(&self) -> &[AtomicWaker] { + &self.0[8..12] + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum FifoJoin { + /// Both TX and RX fifo is enabled + #[default] + Duplex, + /// Rx fifo twice as deep. TX fifo disabled + RxOnly, + /// Tx fifo twice as deep. RX fifo disabled + TxOnly, +} + +#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum ShiftDirection { + #[default] + Right = 1, + Left = 0, +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum Direction { + In = 0, + Out = 1, +} + +#[derive(Clone, Copy, PartialEq, Eq, Default, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[repr(u8)] +pub enum StatusSource { + #[default] + TxFifoLevel = 0, + RxFifoLevel = 1, +} + +const RXNEMPTY_MASK: u32 = 1 << 0; +const TXNFULL_MASK: u32 = 1 << 4; +const SMIRQ_MASK: u32 = 1 << 8; + +pub struct InterruptHandler { + _pio: PhantomData, +} + +impl Handler for InterruptHandler { + unsafe fn on_interrupt() { + let ints = PIO::PIO.irqs(0).ints().read().0; + for bit in 0..12 { + if ints & (1 << bit) != 0 { + PIO::wakers().0[bit].wake(); + } + } + PIO::PIO.irqs(0).inte().write_clear(|m| m.0 = ints); + } +} + +/// Future that waits for TX-FIFO to become writable +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct FifoOutFuture<'a, 'd, PIO: Instance, const SM: usize> { + sm_tx: &'a mut StateMachineTx<'d, PIO, SM>, + value: u32, +} + +impl<'a, 'd, PIO: Instance, const SM: usize> FifoOutFuture<'a, 'd, PIO, SM> { + pub fn new(sm: &'a mut StateMachineTx<'d, PIO, SM>, value: u32) -> Self { + FifoOutFuture { sm_tx: sm, value } + } +} + +impl<'a, 'd, PIO: Instance, const SM: usize> Future for FifoOutFuture<'a, 'd, PIO, SM> { + type Output = (); + fn poll(self: FuturePin<&mut Self>, cx: &mut Context<'_>) -> Poll { + //debug!("Poll {},{}", PIO::PIO_NO, SM); + let value = self.value; + if self.get_mut().sm_tx.try_push(value) { + Poll::Ready(()) + } else { + PIO::wakers().fifo_out()[SM].register(cx.waker()); + PIO::PIO.irqs(0).inte().write_set(|m| { + m.0 = TXNFULL_MASK << SM; + }); + // debug!("Pending"); + Poll::Pending + } + } +} + +impl<'a, 'd, PIO: Instance, const SM: usize> Drop for FifoOutFuture<'a, 'd, PIO, SM> { + fn drop(&mut self) { + PIO::PIO.irqs(0).inte().write_clear(|m| { + m.0 = TXNFULL_MASK << SM; + }); + } +} + +/// Future that waits for RX-FIFO to become readable +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct FifoInFuture<'a, 'd, PIO: Instance, const SM: usize> { + sm_rx: &'a mut StateMachineRx<'d, PIO, SM>, +} + +impl<'a, 'd, PIO: Instance, const SM: usize> FifoInFuture<'a, 'd, PIO, SM> { + pub fn new(sm: &'a mut StateMachineRx<'d, PIO, SM>) -> Self { + FifoInFuture { sm_rx: sm } + } +} + +impl<'a, 'd, PIO: Instance, const SM: usize> Future for FifoInFuture<'a, 'd, PIO, SM> { + type Output = u32; + fn poll(mut self: FuturePin<&mut Self>, cx: &mut Context<'_>) -> Poll { + //debug!("Poll {},{}", PIO::PIO_NO, SM); + if let Some(v) = self.sm_rx.try_pull() { + Poll::Ready(v) + } else { + PIO::wakers().fifo_in()[SM].register(cx.waker()); + PIO::PIO.irqs(0).inte().write_set(|m| { + m.0 = RXNEMPTY_MASK << SM; + }); + //debug!("Pending"); + Poll::Pending + } + } +} + +impl<'a, 'd, PIO: Instance, const SM: usize> Drop for FifoInFuture<'a, 'd, PIO, SM> { + fn drop(&mut self) { + PIO::PIO.irqs(0).inte().write_clear(|m| { + m.0 = RXNEMPTY_MASK << SM; + }); + } +} + +/// Future that waits for IRQ +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct IrqFuture<'a, 'd, PIO: Instance> { + pio: PhantomData<&'a mut Irq<'d, PIO, 0>>, + irq_no: u8, +} + +impl<'a, 'd, PIO: Instance> Future for IrqFuture<'a, 'd, PIO> { + type Output = (); + fn poll(self: FuturePin<&mut Self>, cx: &mut Context<'_>) -> Poll { + //debug!("Poll {},{}", PIO::PIO_NO, SM); + + // Check if IRQ flag is already set + if PIO::PIO.irq().read().0 & (1 << self.irq_no) != 0 { + PIO::PIO.irq().write(|m| m.0 = 1 << self.irq_no); + return Poll::Ready(()); + } + + PIO::wakers().irq()[self.irq_no as usize].register(cx.waker()); + PIO::PIO.irqs(0).inte().write_set(|m| { + m.0 = SMIRQ_MASK << self.irq_no; + }); + Poll::Pending + } +} + +impl<'a, 'd, PIO: Instance> Drop for IrqFuture<'a, 'd, PIO> { + fn drop(&mut self) { + PIO::PIO.irqs(0).inte().write_clear(|m| { + m.0 = SMIRQ_MASK << self.irq_no; + }); + } +} + +pub struct Pin<'l, PIO: Instance> { + pin: PeripheralRef<'l, AnyPin>, + pio: PhantomData, +} + +impl<'l, PIO: Instance> Pin<'l, PIO> { + /// Set the pin's drive strength. + #[inline] + pub fn set_drive_strength(&mut self, strength: Drive) { + self.pin.pad_ctrl().modify(|w| { + w.set_drive(match strength { + Drive::_2mA => pac::pads::vals::Drive::_2MA, + Drive::_4mA => pac::pads::vals::Drive::_4MA, + Drive::_8mA => pac::pads::vals::Drive::_8MA, + Drive::_12mA => pac::pads::vals::Drive::_12MA, + }); + }); + } + + // Set the pin's slew rate. + #[inline] + pub fn set_slew_rate(&mut self, slew_rate: SlewRate) { + self.pin.pad_ctrl().modify(|w| { + w.set_slewfast(slew_rate == SlewRate::Fast); + }); + } + + /// Set the pin's pull. + #[inline] + pub fn set_pull(&mut self, pull: Pull) { + self.pin.pad_ctrl().modify(|w| { + w.set_pue(pull == Pull::Up); + w.set_pde(pull == Pull::Down); + }); + } + + /// Set the pin's schmitt trigger. + #[inline] + pub fn set_schmitt(&mut self, enable: bool) { + self.pin.pad_ctrl().modify(|w| { + w.set_schmitt(enable); + }); + } + + pub fn set_input_sync_bypass<'a>(&mut self, bypass: bool) { + let mask = 1 << self.pin(); + if bypass { + PIO::PIO.input_sync_bypass().write_set(|w| *w = mask); + } else { + PIO::PIO.input_sync_bypass().write_clear(|w| *w = mask); + } + } + + pub fn pin(&self) -> u8 { + self.pin._pin() + } +} + +pub struct StateMachineRx<'d, PIO: Instance, const SM: usize> { + pio: PhantomData<&'d mut PIO>, +} + +impl<'d, PIO: Instance, const SM: usize> StateMachineRx<'d, PIO, SM> { + pub fn empty(&self) -> bool { + PIO::PIO.fstat().read().rxempty() & (1u8 << SM) != 0 + } + + pub fn full(&self) -> bool { + PIO::PIO.fstat().read().rxfull() & (1u8 << SM) != 0 + } + + pub fn level(&self) -> u8 { + (PIO::PIO.flevel().read().0 >> (SM * 8 + 4)) as u8 & 0x0f + } + + pub fn stalled(&self) -> bool { + let fdebug = PIO::PIO.fdebug(); + let ret = fdebug.read().rxstall() & (1 << SM) != 0; + if ret { + fdebug.write(|w| w.set_rxstall(1 << SM)); + } + ret + } + + pub fn underflowed(&self) -> bool { + let fdebug = PIO::PIO.fdebug(); + let ret = fdebug.read().rxunder() & (1 << SM) != 0; + if ret { + fdebug.write(|w| w.set_rxunder(1 << SM)); + } + ret + } + + pub fn pull(&mut self) -> u32 { + PIO::PIO.rxf(SM).read() + } + + pub fn try_pull(&mut self) -> Option { + if self.empty() { + return None; + } + Some(self.pull()) + } + + pub fn wait_pull<'a>(&'a mut self) -> FifoInFuture<'a, 'd, PIO, SM> { + FifoInFuture::new(self) + } + + pub fn dma_pull<'a, C: Channel, W: Word>( + &'a mut self, + ch: PeripheralRef<'a, C>, + data: &'a mut [W], + ) -> Transfer<'a, C> { + let pio_no = PIO::PIO_NO; + let p = ch.regs(); + p.write_addr().write_value(data.as_ptr() as u32); + p.read_addr().write_value(PIO::PIO.rxf(SM).as_ptr() as u32); + p.trans_count().write_value(data.len() as u32); + compiler_fence(Ordering::SeqCst); + p.ctrl_trig().write(|w| { + // Set RX DREQ for this statemachine + w.set_treq_sel(TreqSel(pio_no * 8 + SM as u8 + 4)); + w.set_data_size(W::size()); + w.set_chain_to(ch.number()); + w.set_incr_read(false); + w.set_incr_write(true); + w.set_en(true); + }); + compiler_fence(Ordering::SeqCst); + Transfer::new(ch) + } +} + +pub struct StateMachineTx<'d, PIO: Instance, const SM: usize> { + pio: PhantomData<&'d mut PIO>, +} + +impl<'d, PIO: Instance, const SM: usize> StateMachineTx<'d, PIO, SM> { + pub fn empty(&self) -> bool { + PIO::PIO.fstat().read().txempty() & (1u8 << SM) != 0 + } + pub fn full(&self) -> bool { + PIO::PIO.fstat().read().txfull() & (1u8 << SM) != 0 + } + + pub fn level(&self) -> u8 { + (PIO::PIO.flevel().read().0 >> (SM * 8)) as u8 & 0x0f + } + + pub fn stalled(&self) -> bool { + let fdebug = PIO::PIO.fdebug(); + let ret = fdebug.read().txstall() & (1 << SM) != 0; + if ret { + fdebug.write(|w| w.set_txstall(1 << SM)); + } + ret + } + + pub fn overflowed(&self) -> bool { + let fdebug = PIO::PIO.fdebug(); + let ret = fdebug.read().txover() & (1 << SM) != 0; + if ret { + fdebug.write(|w| w.set_txover(1 << SM)); + } + ret + } + + pub fn push(&mut self, v: u32) { + PIO::PIO.txf(SM).write_value(v); + } + + pub fn try_push(&mut self, v: u32) -> bool { + if self.full() { + return false; + } + self.push(v); + true + } + + pub fn wait_push<'a>(&'a mut self, value: u32) -> FifoOutFuture<'a, 'd, PIO, SM> { + FifoOutFuture::new(self, value) + } + + pub fn dma_push<'a, C: Channel, W: Word>(&'a mut self, ch: PeripheralRef<'a, C>, data: &'a [W]) -> Transfer<'a, C> { + let pio_no = PIO::PIO_NO; + let p = ch.regs(); + p.read_addr().write_value(data.as_ptr() as u32); + p.write_addr().write_value(PIO::PIO.txf(SM).as_ptr() as u32); + p.trans_count().write_value(data.len() as u32); + compiler_fence(Ordering::SeqCst); + p.ctrl_trig().write(|w| { + // Set TX DREQ for this statemachine + w.set_treq_sel(TreqSel(pio_no * 8 + SM as u8)); + w.set_data_size(W::size()); + w.set_chain_to(ch.number()); + w.set_incr_read(true); + w.set_incr_write(false); + w.set_en(true); + }); + compiler_fence(Ordering::SeqCst); + Transfer::new(ch) + } +} + +pub struct StateMachine<'d, PIO: Instance, const SM: usize> { + rx: StateMachineRx<'d, PIO, SM>, + tx: StateMachineTx<'d, PIO, SM>, +} + +impl<'d, PIO: Instance, const SM: usize> Drop for StateMachine<'d, PIO, SM> { + fn drop(&mut self) { + PIO::PIO.ctrl().write_clear(|w| w.set_sm_enable(1 << SM)); + on_pio_drop::(); + } +} + +fn assert_consecutive<'d, PIO: Instance>(pins: &[&Pin<'d, PIO>]) { + for (p1, p2) in pins.iter().zip(pins.iter().skip(1)) { + // purposely does not allow wrap-around because we can't claim pins 30 and 31. + assert!(p1.pin() + 1 == p2.pin(), "pins must be consecutive"); + } +} + +#[derive(Clone, Copy, Default, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct ExecConfig { + pub side_en: bool, + pub side_pindir: bool, + pub jmp_pin: u8, + pub wrap_top: u8, + pub wrap_bottom: u8, +} + +#[derive(Clone, Copy, Default, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ShiftConfig { + pub threshold: u8, + pub direction: ShiftDirection, + pub auto_fill: bool, +} + +#[derive(Clone, Copy, Default, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PinConfig { + pub sideset_count: u8, + pub set_count: u8, + pub out_count: u8, + pub in_base: u8, + pub sideset_base: u8, + pub set_base: u8, + pub out_base: u8, +} + +#[derive(Clone, Copy, Debug)] +pub struct Config<'d, PIO: Instance> { + // CLKDIV + pub clock_divider: FixedU32, + // EXECCTRL + pub out_en_sel: u8, + pub inline_out_en: bool, + pub out_sticky: bool, + pub status_sel: StatusSource, + pub status_n: u8, + exec: ExecConfig, + origin: Option, + // SHIFTCTRL + pub fifo_join: FifoJoin, + pub shift_in: ShiftConfig, + pub shift_out: ShiftConfig, + // PINCTRL + pins: PinConfig, + in_count: u8, + _pio: PhantomData<&'d mut PIO>, +} + +impl<'d, PIO: Instance> Default for Config<'d, PIO> { + fn default() -> Self { + Self { + clock_divider: 1u8.into(), + out_en_sel: Default::default(), + inline_out_en: Default::default(), + out_sticky: Default::default(), + status_sel: Default::default(), + status_n: Default::default(), + exec: Default::default(), + origin: Default::default(), + fifo_join: Default::default(), + shift_in: Default::default(), + shift_out: Default::default(), + pins: Default::default(), + in_count: Default::default(), + _pio: Default::default(), + } + } +} + +impl<'d, PIO: Instance> Config<'d, PIO> { + pub fn get_exec(&self) -> ExecConfig { + self.exec + } + pub unsafe fn set_exec(&mut self, e: ExecConfig) { + self.exec = e; + } + + pub fn get_pins(&self) -> PinConfig { + self.pins + } + pub unsafe fn set_pins(&mut self, p: PinConfig) { + self.pins = p; + } + + /// Configures this state machine to use the given program, including jumping to the origin + /// of the program. The state machine is not started. + /// + /// `side_set` sets the range of pins affected by side-sets. The range must be consecutive. + /// Side-set pins must configured as outputs using [`StateMachine::set_pin_dirs`] to be + /// effective. + pub fn use_program(&mut self, prog: &LoadedProgram<'d, PIO>, side_set: &[&Pin<'d, PIO>]) { + assert!((prog.side_set.bits() - prog.side_set.optional() as u8) as usize == side_set.len()); + assert_consecutive(side_set); + self.exec.side_en = prog.side_set.optional(); + self.exec.side_pindir = prog.side_set.pindirs(); + self.exec.wrap_bottom = prog.wrap.target; + self.exec.wrap_top = prog.wrap.source; + self.pins.sideset_count = prog.side_set.bits(); + self.pins.sideset_base = side_set.first().map_or(0, |p| p.pin()); + self.origin = Some(prog.origin); + } + + pub fn set_jmp_pin(&mut self, pin: &Pin<'d, PIO>) { + self.exec.jmp_pin = pin.pin(); + } + + /// Sets the range of pins affected by SET instructions. The range must be consecutive. + /// Set pins must configured as outputs using [`StateMachine::set_pin_dirs`] to be + /// effective. + pub fn set_set_pins(&mut self, pins: &[&Pin<'d, PIO>]) { + assert!(pins.len() <= 5); + assert_consecutive(pins); + self.pins.set_base = pins.first().map_or(0, |p| p.pin()); + self.pins.set_count = pins.len() as u8; + } + + /// Sets the range of pins affected by OUT instructions. The range must be consecutive. + /// Out pins must configured as outputs using [`StateMachine::set_pin_dirs`] to be + /// effective. + pub fn set_out_pins(&mut self, pins: &[&Pin<'d, PIO>]) { + assert_consecutive(pins); + self.pins.out_base = pins.first().map_or(0, |p| p.pin()); + self.pins.out_count = pins.len() as u8; + } + + /// Sets the range of pins used by IN instructions. The range must be consecutive. + /// In pins must configured as inputs using [`StateMachine::set_pin_dirs`] to be + /// effective. + pub fn set_in_pins(&mut self, pins: &[&Pin<'d, PIO>]) { + assert_consecutive(pins); + self.pins.in_base = pins.first().map_or(0, |p| p.pin()); + self.in_count = pins.len() as u8; + } +} + +impl<'d, PIO: Instance + 'd, const SM: usize> StateMachine<'d, PIO, SM> { + pub fn set_config(&mut self, config: &Config<'d, PIO>) { + // sm expects 0 for 65536, truncation makes that happen + assert!(config.clock_divider <= 65536, "clkdiv must be <= 65536"); + assert!(config.clock_divider >= 1, "clkdiv must be >= 1"); + assert!(config.out_en_sel < 32, "out_en_sel must be < 32"); + assert!(config.status_n < 32, "status_n must be < 32"); + // sm expects 0 for 32, truncation makes that happen + assert!(config.shift_in.threshold <= 32, "shift_in.threshold must be <= 32"); + assert!(config.shift_out.threshold <= 32, "shift_out.threshold must be <= 32"); + let sm = Self::this_sm(); + sm.clkdiv().write(|w| w.0 = config.clock_divider.to_bits() << 8); + sm.execctrl().write(|w| { + w.set_side_en(config.exec.side_en); + w.set_side_pindir(config.exec.side_pindir); + w.set_jmp_pin(config.exec.jmp_pin); + w.set_out_en_sel(config.out_en_sel); + w.set_inline_out_en(config.inline_out_en); + w.set_out_sticky(config.out_sticky); + w.set_wrap_top(config.exec.wrap_top); + w.set_wrap_bottom(config.exec.wrap_bottom); + w.set_status_sel(match config.status_sel { + StatusSource::TxFifoLevel => SmExecctrlStatusSel::TXLEVEL, + StatusSource::RxFifoLevel => SmExecctrlStatusSel::RXLEVEL, + }); + w.set_status_n(config.status_n); + }); + sm.shiftctrl().write(|w| { + w.set_fjoin_rx(config.fifo_join == FifoJoin::RxOnly); + w.set_fjoin_tx(config.fifo_join == FifoJoin::TxOnly); + w.set_pull_thresh(config.shift_out.threshold); + w.set_push_thresh(config.shift_in.threshold); + w.set_out_shiftdir(config.shift_out.direction == ShiftDirection::Right); + w.set_in_shiftdir(config.shift_in.direction == ShiftDirection::Right); + w.set_autopull(config.shift_out.auto_fill); + w.set_autopush(config.shift_in.auto_fill); + }); + sm.pinctrl().write(|w| { + w.set_sideset_count(config.pins.sideset_count); + w.set_set_count(config.pins.set_count); + w.set_out_count(config.pins.out_count); + w.set_in_base(config.pins.in_base); + w.set_sideset_base(config.pins.sideset_base); + w.set_set_base(config.pins.set_base); + w.set_out_base(config.pins.out_base); + }); + if let Some(origin) = config.origin { + unsafe { pio_instr_util::exec_jmp(self, origin) } + } + } + + #[inline(always)] + fn this_sm() -> crate::pac::pio::StateMachine { + PIO::PIO.sm(SM) + } + + pub fn restart(&mut self) { + let mask = 1u8 << SM; + PIO::PIO.ctrl().write_set(|w| w.set_sm_restart(mask)); + } + pub fn set_enable(&mut self, enable: bool) { + let mask = 1u8 << SM; + if enable { + PIO::PIO.ctrl().write_set(|w| w.set_sm_enable(mask)); + } else { + PIO::PIO.ctrl().write_clear(|w| w.set_sm_enable(mask)); + } + } + + pub fn is_enabled(&self) -> bool { + PIO::PIO.ctrl().read().sm_enable() & (1u8 << SM) != 0 + } + + pub fn clkdiv_restart(&mut self) { + let mask = 1u8 << SM; + PIO::PIO.ctrl().write_set(|w| w.set_clkdiv_restart(mask)); + } + + fn with_paused(&mut self, f: impl FnOnce(&mut Self)) { + let enabled = self.is_enabled(); + self.set_enable(false); + let pincfg = Self::this_sm().pinctrl().read(); + let execcfg = Self::this_sm().execctrl().read(); + Self::this_sm().execctrl().write_clear(|w| w.set_out_sticky(true)); + f(self); + Self::this_sm().pinctrl().write_value(pincfg); + Self::this_sm().execctrl().write_value(execcfg); + self.set_enable(enabled); + } + + /// Sets pin directions. This pauses the current state machine to run `SET` commands + /// and temporarily unsets the `OUT_STICKY` bit. + pub fn set_pin_dirs(&mut self, dir: Direction, pins: &[&Pin<'d, PIO>]) { + self.with_paused(|sm| { + for pin in pins { + Self::this_sm().pinctrl().write(|w| { + w.set_set_base(pin.pin()); + w.set_set_count(1); + }); + // SET PINDIRS, (dir) + unsafe { sm.exec_instr(0b111_00000_100_00000 | dir as u16) }; + } + }); + } + + /// Sets pin output values. This pauses the current state machine to run + /// `SET` commands and temporarily unsets the `OUT_STICKY` bit. + pub fn set_pins(&mut self, level: Level, pins: &[&Pin<'d, PIO>]) { + self.with_paused(|sm| { + for pin in pins { + Self::this_sm().pinctrl().write(|w| { + w.set_set_base(pin.pin()); + w.set_set_count(1); + }); + // SET PINS, (dir) + unsafe { sm.exec_instr(0b111_00000_000_00000 | level as u16) }; + } + }); + } + + pub fn clear_fifos(&mut self) { + // Toggle FJOIN_RX to flush FIFOs + let shiftctrl = Self::this_sm().shiftctrl(); + shiftctrl.modify(|w| { + w.set_fjoin_rx(!w.fjoin_rx()); + }); + shiftctrl.modify(|w| { + w.set_fjoin_rx(!w.fjoin_rx()); + }); + } + + pub unsafe fn exec_instr(&mut self, instr: u16) { + Self::this_sm().instr().write(|w| w.set_instr(instr)); + } + + pub fn rx(&mut self) -> &mut StateMachineRx<'d, PIO, SM> { + &mut self.rx + } + pub fn tx(&mut self) -> &mut StateMachineTx<'d, PIO, SM> { + &mut self.tx + } + pub fn rx_tx(&mut self) -> (&mut StateMachineRx<'d, PIO, SM>, &mut StateMachineTx<'d, PIO, SM>) { + (&mut self.rx, &mut self.tx) + } +} + +pub struct Common<'d, PIO: Instance> { + instructions_used: u32, + pio: PhantomData<&'d mut PIO>, +} + +impl<'d, PIO: Instance> Drop for Common<'d, PIO> { + fn drop(&mut self) { + on_pio_drop::(); + } +} + +pub struct InstanceMemory<'d, PIO: Instance> { + used_mask: u32, + pio: PhantomData<&'d mut PIO>, +} + +pub struct LoadedProgram<'d, PIO: Instance> { + pub used_memory: InstanceMemory<'d, PIO>, + origin: u8, + wrap: Wrap, + side_set: SideSet, +} + +impl<'d, PIO: Instance> Common<'d, PIO> { + pub fn load_program(&mut self, prog: &RelocatedProgram) -> LoadedProgram<'d, PIO> { + match self.try_load_program(prog) { + Ok(r) => r, + Err(at) => panic!("Trying to write already used PIO instruction memory at {}", at), + } + } + + pub fn try_load_program( + &mut self, + prog: &RelocatedProgram, + ) -> Result, usize> { + let used_memory = self.try_write_instr(prog.origin() as _, prog.code())?; + Ok(LoadedProgram { + used_memory, + origin: prog.origin(), + wrap: prog.wrap(), + side_set: prog.side_set(), + }) + } + + pub fn try_write_instr(&mut self, start: usize, instrs: I) -> Result, usize> + where + I: Iterator, + { + let mut used_mask = 0; + for (i, instr) in instrs.enumerate() { + // wrapping around the end of program memory is valid, let's make use of that. + let addr = (i + start) % 32; + let mask = 1 << addr; + if (self.instructions_used | used_mask) & mask != 0 { + return Err(addr); + } + PIO::PIO.instr_mem(addr).write(|w| { + w.set_instr_mem(instr); + }); + used_mask |= mask; + } + self.instructions_used |= used_mask; + Ok(InstanceMemory { + used_mask, + pio: PhantomData, + }) + } + + /// Free instruction memory. This is always possible but unsafe if any + /// state machine is still using this bit of memory. + pub unsafe fn free_instr(&mut self, instrs: InstanceMemory) { + self.instructions_used &= !instrs.used_mask; + } + + pub fn set_input_sync_bypass<'a>(&'a mut self, bypass: u32, mask: u32) { + // this can interfere with per-pin bypass functions. splitting the + // modification is going to be fine since nothing that relies on + // it can reasonably run before we finish. + PIO::PIO.input_sync_bypass().write_set(|w| *w = mask & bypass); + PIO::PIO.input_sync_bypass().write_clear(|w| *w = mask & !bypass); + } + + pub fn get_input_sync_bypass(&self) -> u32 { + PIO::PIO.input_sync_bypass().read() + } + + /// Register a pin for PIO usage. Pins will be released from the PIO block + /// (i.e., have their `FUNCSEL` reset to `NULL`) when the [`Common`] *and* + /// all [`StateMachine`]s for this block have been dropped. **Other members + /// of [`Pio`] do not keep pin registrations alive.** + pub fn make_pio_pin(&mut self, pin: impl Peripheral

+ 'd) -> Pin<'d, PIO> { + into_ref!(pin); + pin.io().ctrl().write(|w| w.set_funcsel(PIO::FUNCSEL as _)); + // we can be relaxed about this because we're &mut here and nothing is cached + PIO::state().used_pins.fetch_or(1 << pin.pin_bank(), Ordering::Relaxed); + Pin { + pin: pin.into_ref().map_into(), + pio: PhantomData::default(), + } + } + + pub fn apply_sm_batch(&mut self, f: impl FnOnce(&mut PioBatch<'d, PIO>)) { + let mut batch = PioBatch { + clkdiv_restart: 0, + sm_restart: 0, + sm_enable_mask: 0, + sm_enable: 0, + _pio: PhantomData, + }; + f(&mut batch); + PIO::PIO.ctrl().modify(|w| { + w.set_clkdiv_restart(batch.clkdiv_restart); + w.set_sm_restart(batch.sm_restart); + w.set_sm_enable((w.sm_enable() & !batch.sm_enable_mask) | batch.sm_enable); + }); + } +} + +pub struct PioBatch<'a, PIO: Instance> { + clkdiv_restart: u8, + sm_restart: u8, + sm_enable_mask: u8, + sm_enable: u8, + _pio: PhantomData<&'a PIO>, +} + +impl<'a, PIO: Instance> PioBatch<'a, PIO> { + pub fn restart_clockdiv(&mut self, _sm: &mut StateMachine<'a, PIO, SM>) { + self.clkdiv_restart |= 1 << SM; + } + + pub fn restart(&mut self, _sm: &mut StateMachine<'a, PIO, SM>) { + self.clkdiv_restart |= 1 << SM; + } + + pub fn set_enable(&mut self, _sm: &mut StateMachine<'a, PIO, SM>, enable: bool) { + self.sm_enable_mask |= 1 << SM; + self.sm_enable |= (enable as u8) << SM; + } +} + +pub struct Irq<'d, PIO: Instance, const N: usize> { + pio: PhantomData<&'d mut PIO>, +} + +impl<'d, PIO: Instance, const N: usize> Irq<'d, PIO, N> { + pub fn wait<'a>(&'a mut self) -> IrqFuture<'a, 'd, PIO> { + IrqFuture { + pio: PhantomData, + irq_no: N as u8, + } + } +} + +#[derive(Clone)] +pub struct IrqFlags<'d, PIO: Instance> { + pio: PhantomData<&'d mut PIO>, +} + +impl<'d, PIO: Instance> IrqFlags<'d, PIO> { + pub fn check(&self, irq_no: u8) -> bool { + assert!(irq_no < 8); + self.check_any(1 << irq_no) + } + + pub fn check_any(&self, irqs: u8) -> bool { + PIO::PIO.irq().read().irq() & irqs != 0 + } + + pub fn check_all(&self, irqs: u8) -> bool { + PIO::PIO.irq().read().irq() & irqs == irqs + } + + pub fn clear(&self, irq_no: usize) { + assert!(irq_no < 8); + self.clear_all(1 << irq_no); + } + + pub fn clear_all(&self, irqs: u8) { + PIO::PIO.irq().write(|w| w.set_irq(irqs)) + } + + pub fn set(&self, irq_no: usize) { + assert!(irq_no < 8); + self.set_all(1 << irq_no); + } + + pub fn set_all(&self, irqs: u8) { + PIO::PIO.irq_force().write(|w| w.set_irq_force(irqs)) + } +} + +pub struct Pio<'d, PIO: Instance> { + pub common: Common<'d, PIO>, + pub irq_flags: IrqFlags<'d, PIO>, + pub irq0: Irq<'d, PIO, 0>, + pub irq1: Irq<'d, PIO, 1>, + pub irq2: Irq<'d, PIO, 2>, + pub irq3: Irq<'d, PIO, 3>, + pub sm0: StateMachine<'d, PIO, 0>, + pub sm1: StateMachine<'d, PIO, 1>, + pub sm2: StateMachine<'d, PIO, 2>, + pub sm3: StateMachine<'d, PIO, 3>, + _pio: PhantomData<&'d mut PIO>, +} + +impl<'d, PIO: Instance> Pio<'d, PIO> { + pub fn new(_pio: impl Peripheral

+ 'd, _irq: impl Binding>) -> Self { + PIO::state().users.store(5, Ordering::Release); + PIO::state().used_pins.store(0, Ordering::Release); + PIO::Interrupt::unpend(); + unsafe { PIO::Interrupt::enable() }; + Self { + common: Common { + instructions_used: 0, + pio: PhantomData, + }, + irq_flags: IrqFlags { pio: PhantomData }, + irq0: Irq { pio: PhantomData }, + irq1: Irq { pio: PhantomData }, + irq2: Irq { pio: PhantomData }, + irq3: Irq { pio: PhantomData }, + sm0: StateMachine { + rx: StateMachineRx { pio: PhantomData }, + tx: StateMachineTx { pio: PhantomData }, + }, + sm1: StateMachine { + rx: StateMachineRx { pio: PhantomData }, + tx: StateMachineTx { pio: PhantomData }, + }, + sm2: StateMachine { + rx: StateMachineRx { pio: PhantomData }, + tx: StateMachineTx { pio: PhantomData }, + }, + sm3: StateMachine { + rx: StateMachineRx { pio: PhantomData }, + tx: StateMachineTx { pio: PhantomData }, + }, + _pio: PhantomData, + } + } +} + +// we need to keep a record of which pins are assigned to each PIO. make_pio_pin +// notionally takes ownership of the pin it is given, but the wrapped pin cannot +// be treated as an owned resource since dropping it would have to deconfigure +// the pin, breaking running state machines in the process. pins are also shared +// between all state machines, which makes ownership even messier to track any +// other way. +pub struct State { + users: AtomicU8, + used_pins: AtomicU32, +} + +fn on_pio_drop() { + let state = PIO::state(); + if state.users.fetch_sub(1, Ordering::AcqRel) == 1 { + let used_pins = state.used_pins.load(Ordering::Relaxed); + let null = Gpio0ctrlFuncsel::NULL as _; + // we only have 30 pins. don't test the other two since gpio() asserts. + for i in 0..30 { + if used_pins & (1 << i) != 0 { + pac::IO_BANK0.gpio(i).ctrl().write(|w| w.set_funcsel(null)); + } + } + } +} + +mod sealed { + use super::*; + + pub trait PioPin {} + + pub trait Instance { + const PIO_NO: u8; + const PIO: &'static crate::pac::pio::Pio; + const FUNCSEL: crate::pac::io::vals::Gpio0ctrlFuncsel; + type Interrupt: crate::interrupt::typelevel::Interrupt; + + #[inline] + fn wakers() -> &'static Wakers { + const NEW_AW: AtomicWaker = AtomicWaker::new(); + static WAKERS: Wakers = Wakers([NEW_AW; 12]); + + &WAKERS + } + + #[inline] + fn state() -> &'static State { + static STATE: State = State { + users: AtomicU8::new(0), + used_pins: AtomicU32::new(0), + }; + + &STATE + } + } +} + +pub trait Instance: sealed::Instance + Sized + Unpin {} + +macro_rules! impl_pio { + ($name:ident, $pio:expr, $pac:ident, $funcsel:ident, $irq:ident) => { + impl sealed::Instance for peripherals::$name { + const PIO_NO: u8 = $pio; + const PIO: &'static pac::pio::Pio = &pac::$pac; + const FUNCSEL: pac::io::vals::Gpio0ctrlFuncsel = pac::io::vals::Gpio0ctrlFuncsel::$funcsel; + type Interrupt = crate::interrupt::typelevel::$irq; + } + impl Instance for peripherals::$name {} + }; +} + +impl_pio!(PIO0, 0, PIO0, PIO0_0, PIO0_IRQ_0); +impl_pio!(PIO1, 1, PIO1, PIO1_0, PIO1_IRQ_0); + +pub trait PioPin: sealed::PioPin + gpio::Pin {} + +macro_rules! impl_pio_pin { + ($( $num:tt )*) => { + $( + paste::paste!{ + impl sealed::PioPin for peripherals::[< PIN_ $num >] {} + impl PioPin for peripherals::[< PIN_ $num >] {} + } + )* + }; +} + +impl_pio_pin! { + 0 1 2 3 4 5 6 7 8 9 + 10 11 12 13 14 15 16 17 18 19 + 20 21 22 23 24 25 26 27 28 29 +} diff --git a/embassy-rp/src/pio_instr_util.rs b/embassy-rp/src/pio_instr_util.rs new file mode 100644 index 000000000..25393b476 --- /dev/null +++ b/embassy-rp/src/pio_instr_util.rs @@ -0,0 +1,90 @@ +use pio::{InSource, InstructionOperands, JmpCondition, OutDestination, SetDestination}; + +use crate::pio::{Instance, StateMachine}; + +pub unsafe fn set_x(sm: &mut StateMachine, value: u32) { + const OUT: u16 = InstructionOperands::OUT { + destination: OutDestination::X, + bit_count: 32, + } + .encode(); + sm.tx().push(value); + sm.exec_instr(OUT); +} + +pub unsafe fn get_x(sm: &mut StateMachine) -> u32 { + const IN: u16 = InstructionOperands::IN { + source: InSource::X, + bit_count: 32, + } + .encode(); + sm.exec_instr(IN); + sm.rx().pull() +} + +pub unsafe fn set_y(sm: &mut StateMachine, value: u32) { + const OUT: u16 = InstructionOperands::OUT { + destination: OutDestination::Y, + bit_count: 32, + } + .encode(); + sm.tx().push(value); + sm.exec_instr(OUT); +} + +pub unsafe fn get_y(sm: &mut StateMachine) -> u32 { + const IN: u16 = InstructionOperands::IN { + source: InSource::Y, + bit_count: 32, + } + .encode(); + sm.exec_instr(IN); + + sm.rx().pull() +} + +pub unsafe fn set_pindir(sm: &mut StateMachine, data: u8) { + let set: u16 = InstructionOperands::SET { + destination: SetDestination::PINDIRS, + data, + } + .encode(); + sm.exec_instr(set); +} + +pub unsafe fn set_pin(sm: &mut StateMachine, data: u8) { + let set: u16 = InstructionOperands::SET { + destination: SetDestination::PINS, + data, + } + .encode(); + sm.exec_instr(set); +} + +pub unsafe fn set_out_pin(sm: &mut StateMachine, data: u32) { + const OUT: u16 = InstructionOperands::OUT { + destination: OutDestination::PINS, + bit_count: 32, + } + .encode(); + sm.tx().push(data); + sm.exec_instr(OUT); +} +pub unsafe fn set_out_pindir(sm: &mut StateMachine, data: u32) { + const OUT: u16 = InstructionOperands::OUT { + destination: OutDestination::PINDIRS, + bit_count: 32, + } + .encode(); + sm.tx().push(data); + sm.exec_instr(OUT); +} + +pub unsafe fn exec_jmp(sm: &mut StateMachine, to_addr: u8) { + let jmp: u16 = InstructionOperands::JMP { + address: to_addr, + condition: JmpCondition::Always, + } + .encode(); + sm.exec_instr(jmp); +} diff --git a/embassy-rp/src/pwm.rs b/embassy-rp/src/pwm.rs new file mode 100644 index 000000000..20bb88446 --- /dev/null +++ b/embassy-rp/src/pwm.rs @@ -0,0 +1,324 @@ +//! Pulse Width Modulation (PWM) + +use embassy_hal_common::{into_ref, Peripheral, PeripheralRef}; +use fixed::traits::ToFixed; +use fixed::FixedU16; +use pac::pwm::regs::{ChDiv, Intr}; +use pac::pwm::vals::Divmode; + +use crate::gpio::sealed::Pin as _; +use crate::gpio::{AnyPin, Pin as GpioPin}; +use crate::{pac, peripherals, RegExt}; + +#[non_exhaustive] +#[derive(Clone)] +pub struct Config { + pub invert_a: bool, + pub invert_b: bool, + pub phase_correct: bool, + pub enable: bool, + pub divider: fixed::FixedU16, + pub compare_a: u16, + pub compare_b: u16, + pub top: u16, +} + +impl Default for Config { + fn default() -> Self { + Self { + invert_a: false, + invert_b: false, + phase_correct: false, + enable: true, // differs from reset value + divider: 1.to_fixed(), + compare_a: 0, + compare_b: 0, + top: 0xffff, + } + } +} + +pub enum InputMode { + Level, + RisingEdge, + FallingEdge, +} + +impl From for Divmode { + fn from(value: InputMode) -> Self { + match value { + InputMode::Level => Divmode::LEVEL, + InputMode::RisingEdge => Divmode::RISE, + InputMode::FallingEdge => Divmode::FALL, + } + } +} + +pub struct Pwm<'d, T: Channel> { + inner: PeripheralRef<'d, T>, + pin_a: Option>, + pin_b: Option>, +} + +impl<'d, T: Channel> Pwm<'d, T> { + fn new_inner( + inner: impl Peripheral

+ 'd, + a: Option>, + b: Option>, + config: Config, + divmode: Divmode, + ) -> Self { + into_ref!(inner); + + let p = inner.regs(); + p.csr().modify(|w| { + w.set_divmode(divmode); + w.set_en(false); + }); + p.ctr().write(|w| w.0 = 0); + Self::configure(p, &config); + + if let Some(pin) = &a { + pin.io().ctrl().write(|w| w.set_funcsel(4)); + } + if let Some(pin) = &b { + pin.io().ctrl().write(|w| w.set_funcsel(4)); + } + Self { + inner, + pin_a: a.into(), + pin_b: b.into(), + } + } + + #[inline] + pub fn new_free(inner: impl Peripheral

+ 'd, config: Config) -> Self { + Self::new_inner(inner, None, None, config, Divmode::DIV) + } + + #[inline] + pub fn new_output_a( + inner: impl Peripheral

+ 'd, + a: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + into_ref!(a); + Self::new_inner(inner, Some(a.map_into()), None, config, Divmode::DIV) + } + + #[inline] + pub fn new_output_b( + inner: impl Peripheral

+ 'd, + b: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + into_ref!(b); + Self::new_inner(inner, None, Some(b.map_into()), config, Divmode::DIV) + } + + #[inline] + pub fn new_output_ab( + inner: impl Peripheral

+ 'd, + a: impl Peripheral

> + 'd, + b: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + into_ref!(a, b); + Self::new_inner(inner, Some(a.map_into()), Some(b.map_into()), config, Divmode::DIV) + } + + #[inline] + pub fn new_input( + inner: impl Peripheral

+ 'd, + b: impl Peripheral

> + 'd, + mode: InputMode, + config: Config, + ) -> Self { + into_ref!(b); + Self::new_inner(inner, None, Some(b.map_into()), config, mode.into()) + } + + #[inline] + pub fn new_output_input( + inner: impl Peripheral

+ 'd, + a: impl Peripheral

> + 'd, + b: impl Peripheral

> + 'd, + mode: InputMode, + config: Config, + ) -> Self { + into_ref!(a, b); + Self::new_inner(inner, Some(a.map_into()), Some(b.map_into()), config, mode.into()) + } + + pub fn set_config(&mut self, config: &Config) { + Self::configure(self.inner.regs(), config); + } + + fn configure(p: pac::pwm::Channel, config: &Config) { + if config.divider > FixedU16::::from_bits(0xFF_F) { + panic!("Requested divider is too large"); + } + + p.div().write_value(ChDiv(config.divider.to_bits() as u32)); + p.cc().write(|w| { + w.set_a(config.compare_a); + w.set_b(config.compare_b); + }); + p.top().write(|w| w.set_top(config.top)); + p.csr().modify(|w| { + w.set_a_inv(config.invert_a); + w.set_b_inv(config.invert_b); + w.set_ph_correct(config.phase_correct); + w.set_en(config.enable); + }); + } + + #[inline] + pub fn phase_advance(&mut self) { + let p = self.inner.regs(); + p.csr().write_set(|w| w.set_ph_adv(true)); + while p.csr().read().ph_adv() {} + } + + #[inline] + pub fn phase_retard(&mut self) { + let p = self.inner.regs(); + p.csr().write_set(|w| w.set_ph_ret(true)); + while p.csr().read().ph_ret() {} + } + + #[inline] + pub fn counter(&self) -> u16 { + self.inner.regs().ctr().read().ctr() + } + + #[inline] + pub fn set_counter(&self, ctr: u16) { + self.inner.regs().ctr().write(|w| w.set_ctr(ctr)) + } + + #[inline] + pub fn wait_for_wrap(&mut self) { + while !self.wrapped() {} + self.clear_wrapped(); + } + + #[inline] + pub fn wrapped(&mut self) -> bool { + pac::PWM.intr().read().0 & self.bit() != 0 + } + + #[inline] + pub fn clear_wrapped(&mut self) { + pac::PWM.intr().write_value(Intr(self.bit() as _)); + } + + #[inline] + fn bit(&self) -> u32 { + 1 << self.inner.number() as usize + } +} + +pub struct PwmBatch(u32); + +impl PwmBatch { + #[inline] + pub fn enable(&mut self, pwm: &Pwm<'_, impl Channel>) { + self.0 |= pwm.bit(); + } + + #[inline] + pub fn set_enabled(enabled: bool, batch: impl FnOnce(&mut PwmBatch)) { + let mut en = PwmBatch(0); + batch(&mut en); + if enabled { + pac::PWM.en().write_set(|w| w.0 = en.0); + } else { + pac::PWM.en().write_clear(|w| w.0 = en.0); + } + } +} + +impl<'d, T: Channel> Drop for Pwm<'d, T> { + fn drop(&mut self) { + self.inner.regs().csr().write_clear(|w| w.set_en(false)); + if let Some(pin) = &self.pin_a { + pin.io().ctrl().write(|w| w.set_funcsel(31)); + } + if let Some(pin) = &self.pin_b { + pin.io().ctrl().write(|w| w.set_funcsel(31)); + } + } +} + +mod sealed { + pub trait Channel {} +} + +pub trait Channel: Peripheral

+ sealed::Channel + Sized + 'static { + fn number(&self) -> u8; + + fn regs(&self) -> pac::pwm::Channel { + pac::PWM.ch(self.number() as _) + } +} + +macro_rules! channel { + ($name:ident, $num:expr) => { + impl sealed::Channel for peripherals::$name {} + impl Channel for peripherals::$name { + fn number(&self) -> u8 { + $num + } + } + }; +} + +channel!(PWM_CH0, 0); +channel!(PWM_CH1, 1); +channel!(PWM_CH2, 2); +channel!(PWM_CH3, 3); +channel!(PWM_CH4, 4); +channel!(PWM_CH5, 5); +channel!(PWM_CH6, 6); +channel!(PWM_CH7, 7); + +pub trait PwmPinA: GpioPin {} +pub trait PwmPinB: GpioPin {} + +macro_rules! impl_pin { + ($pin:ident, $channel:ident, $kind:ident) => { + impl $kind for peripherals::$pin {} + }; +} + +impl_pin!(PIN_0, PWM_CH0, PwmPinA); +impl_pin!(PIN_1, PWM_CH0, PwmPinB); +impl_pin!(PIN_2, PWM_CH1, PwmPinA); +impl_pin!(PIN_3, PWM_CH1, PwmPinB); +impl_pin!(PIN_4, PWM_CH2, PwmPinA); +impl_pin!(PIN_5, PWM_CH2, PwmPinB); +impl_pin!(PIN_6, PWM_CH3, PwmPinA); +impl_pin!(PIN_7, PWM_CH3, PwmPinB); +impl_pin!(PIN_8, PWM_CH4, PwmPinA); +impl_pin!(PIN_9, PWM_CH4, PwmPinB); +impl_pin!(PIN_10, PWM_CH5, PwmPinA); +impl_pin!(PIN_11, PWM_CH5, PwmPinB); +impl_pin!(PIN_12, PWM_CH6, PwmPinA); +impl_pin!(PIN_13, PWM_CH6, PwmPinB); +impl_pin!(PIN_14, PWM_CH7, PwmPinA); +impl_pin!(PIN_15, PWM_CH7, PwmPinB); +impl_pin!(PIN_16, PWM_CH0, PwmPinA); +impl_pin!(PIN_17, PWM_CH0, PwmPinB); +impl_pin!(PIN_18, PWM_CH1, PwmPinA); +impl_pin!(PIN_19, PWM_CH1, PwmPinB); +impl_pin!(PIN_20, PWM_CH2, PwmPinA); +impl_pin!(PIN_21, PWM_CH2, PwmPinB); +impl_pin!(PIN_22, PWM_CH3, PwmPinA); +impl_pin!(PIN_23, PWM_CH3, PwmPinB); +impl_pin!(PIN_24, PWM_CH4, PwmPinA); +impl_pin!(PIN_25, PWM_CH4, PwmPinB); +impl_pin!(PIN_26, PWM_CH5, PwmPinA); +impl_pin!(PIN_27, PWM_CH5, PwmPinB); +impl_pin!(PIN_28, PWM_CH6, PwmPinA); +impl_pin!(PIN_29, PWM_CH6, PwmPinB); diff --git a/embassy-rp/src/relocate.rs b/embassy-rp/src/relocate.rs new file mode 100644 index 000000000..9cb279ccd --- /dev/null +++ b/embassy-rp/src/relocate.rs @@ -0,0 +1,73 @@ +use core::iter::Iterator; + +use pio::{Program, SideSet, Wrap}; + +pub struct CodeIterator<'a, I> +where + I: Iterator, +{ + iter: I, + offset: u8, +} + +impl<'a, I: Iterator> CodeIterator<'a, I> { + pub fn new(iter: I, offset: u8) -> CodeIterator<'a, I> { + CodeIterator { iter, offset } + } +} + +impl<'a, I> Iterator for CodeIterator<'a, I> +where + I: Iterator, +{ + type Item = u16; + fn next(&mut self) -> Option { + self.iter.next().and_then(|&instr| { + Some(if instr & 0b1110_0000_0000_0000 == 0 { + // this is a JMP instruction -> add offset to address + let address = (instr & 0b1_1111) as u8; + let address = address.wrapping_add(self.offset) % 32; + instr & (!0b11111) | address as u16 + } else { + instr + }) + }) + } +} + +pub struct RelocatedProgram<'a, const PROGRAM_SIZE: usize> { + program: &'a Program, + origin: u8, +} + +impl<'a, const PROGRAM_SIZE: usize> RelocatedProgram<'a, PROGRAM_SIZE> { + pub fn new(program: &Program) -> RelocatedProgram { + let origin = program.origin.unwrap_or(0); + RelocatedProgram { program, origin } + } + + pub fn new_with_origin(program: &Program, origin: u8) -> RelocatedProgram { + RelocatedProgram { program, origin } + } + + pub fn code(&'a self) -> CodeIterator<'a, core::slice::Iter<'a, u16>> { + CodeIterator::new(self.program.code.iter(), self.origin) + } + + pub fn wrap(&self) -> Wrap { + let wrap = self.program.wrap; + let origin = self.origin; + Wrap { + source: wrap.source.wrapping_add(origin) % 32, + target: wrap.target.wrapping_add(origin) % 32, + } + } + + pub fn side_set(&self) -> SideSet { + self.program.side_set + } + + pub fn origin(&self) -> u8 { + self.origin + } +} diff --git a/embassy-rp/src/reset.rs b/embassy-rp/src/reset.rs index edd47c223..70512fa14 100644 --- a/embassy-rp/src/reset.rs +++ b/embassy-rp/src/reset.rs @@ -4,11 +4,11 @@ use crate::pac; pub const ALL_PERIPHERALS: Peripherals = Peripherals(0x01ffffff); -pub unsafe fn reset(peris: Peripherals) { +pub(crate) fn reset(peris: Peripherals) { pac::RESETS.reset().write_value(peris); } -pub unsafe fn unreset_wait(peris: Peripherals) { +pub(crate) fn unreset_wait(peris: Peripherals) { // TODO use the "atomic clear" register version pac::RESETS.reset().modify(|v| *v = Peripherals(v.0 & !peris.0)); while ((!pac::RESETS.reset_done().read().0) & peris.0) != 0 {} diff --git a/embassy-rp/src/rom_data.rs b/embassy-rp/src/rom_data.rs new file mode 100644 index 000000000..805c1f09f --- /dev/null +++ b/embassy-rp/src/rom_data.rs @@ -0,0 +1,726 @@ +//! Functions and data from the RPI Bootrom. +//! +//! From the [RP2040 datasheet](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf), Section 2.8.2.1: +//! +//! > The Bootrom contains a number of public functions that provide useful +//! > RP2040 functionality that might be needed in the absence of any other code +//! > on the device, as well as highly optimized versions of certain key +//! > functionality that would otherwise have to take up space in most user +//! > binaries. + +// Credit: taken from `rp-hal` (also licensed Apache+MIT) +// https://github.com/rp-rs/rp-hal/blob/main/rp2040-hal/src/rom_data.rs + +/// A bootrom function table code. +pub type RomFnTableCode = [u8; 2]; + +/// This function searches for (table) +type RomTableLookupFn = unsafe extern "C" fn(*const u16, u32) -> T; + +/// The following addresses are described at `2.8.2. Bootrom Contents` +/// Pointer to the lookup table function supplied by the rom. +const ROM_TABLE_LOOKUP_PTR: *const u16 = 0x0000_0018 as _; + +/// Pointer to helper functions lookup table. +const FUNC_TABLE: *const u16 = 0x0000_0014 as _; + +/// Pointer to the public data lookup table. +const DATA_TABLE: *const u16 = 0x0000_0016 as _; + +/// Address of the version number of the ROM. +const VERSION_NUMBER: *const u8 = 0x0000_0013 as _; + +/// Retrive rom content from a table using a code. +fn rom_table_lookup(table: *const u16, tag: RomFnTableCode) -> T { + unsafe { + let rom_table_lookup_ptr: *const u32 = rom_hword_as_ptr(ROM_TABLE_LOOKUP_PTR); + let rom_table_lookup: RomTableLookupFn = core::mem::transmute(rom_table_lookup_ptr); + rom_table_lookup(rom_hword_as_ptr(table) as *const u16, u16::from_le_bytes(tag) as u32) + } +} + +/// To save space, the ROM likes to store memory pointers (which are 32-bit on +/// the Cortex-M0+) using only the bottom 16-bits. The assumption is that the +/// values they point at live in the first 64 KiB of ROM, and the ROM is mapped +/// to address `0x0000_0000` and so 16-bits are always sufficient. +/// +/// This functions grabs a 16-bit value from ROM and expands it out to a full 32-bit pointer. +unsafe fn rom_hword_as_ptr(rom_address: *const u16) -> *const u32 { + let ptr: u16 = *rom_address; + ptr as *const u32 +} + +macro_rules! declare_rom_function { + ( + $(#[$outer:meta])* + fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty + $lookup:block + ) => { + declare_rom_function!{ + __internal , + $(#[$outer])* + fn $name( $($argname: $ty),* ) -> $ret + $lookup + } + }; + + ( + $(#[$outer:meta])* + unsafe fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty + $lookup:block + ) => { + declare_rom_function!{ + __internal unsafe , + $(#[$outer])* + fn $name( $($argname: $ty),* ) -> $ret + $lookup + } + }; + + ( + __internal + $( $maybe_unsafe:ident )? , + $(#[$outer:meta])* + fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty + $lookup:block + ) => { + #[doc = r"Additional access for the `"] + #[doc = stringify!($name)] + #[doc = r"` ROM function."] + pub mod $name { + /// Retrieve a function pointer. + #[cfg(not(feature = "rom-func-cache"))] + pub fn ptr() -> $( $maybe_unsafe )? extern "C" fn( $($argname: $ty),* ) -> $ret { + let p: *const u32 = $lookup; + unsafe { + let func : $( $maybe_unsafe )? extern "C" fn( $($argname: $ty),* ) -> $ret + = core::mem::transmute(p); + func + } + } + + #[cfg(feature = "rom-func-cache")] + // unlike rp2040-hal we store a full word, containing the full function pointer. + // rp2040-hal saves two bytes by storing only the rom offset, at the cost of + // having to do an indirection and an atomic operation on every rom call. + static mut CACHE: $( $maybe_unsafe )? extern "C" fn( $($argname: $ty),* ) -> $ret + = trampoline; + + #[cfg(feature = "rom-func-cache")] + $( $maybe_unsafe )? extern "C" fn trampoline( $($argname: $ty),* ) -> $ret { + use core::sync::atomic::{compiler_fence, Ordering}; + + let p: *const u32 = $lookup; + #[allow(unused_unsafe)] + unsafe { + CACHE = core::mem::transmute(p); + compiler_fence(Ordering::Release); + CACHE($($argname),*) + } + } + + /// Retrieve a function pointer. + #[cfg(feature = "rom-func-cache")] + pub fn ptr() -> $( $maybe_unsafe )? extern "C" fn( $($argname: $ty),* ) -> $ret { + use core::sync::atomic::{compiler_fence, Ordering}; + + // This is safe because the lookup will always resolve + // to the same value. So even if an interrupt or another + // core starts at the same time, it just repeats some + // work and eventually writes back the correct value. + // + // We easily get away with using only compiler fences here + // because RP2040 SRAM is not cached. If it were we'd need + // to make sure updates propagate quickly, or just take the + // hit and let each core resolve every function once. + compiler_fence(Ordering::Acquire); + unsafe { + CACHE + } + } + } + + $(#[$outer])* + pub $( $maybe_unsafe )? extern "C" fn $name( $($argname: $ty),* ) -> $ret { + $name::ptr()($($argname),*) + } + }; +} + +macro_rules! rom_functions { + () => {}; + + ( + $(#[$outer:meta])* + $c:literal fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty; + + $($rest:tt)* + ) => { + declare_rom_function! { + $(#[$outer])* + fn $name( $($argname: $ty),* ) -> $ret { + $crate::rom_data::rom_table_lookup($crate::rom_data::FUNC_TABLE, *$c) + } + } + + rom_functions!($($rest)*); + }; + + ( + $(#[$outer:meta])* + $c:literal unsafe fn $name:ident( $($argname:ident: $ty:ty),* ) -> $ret:ty; + + $($rest:tt)* + ) => { + declare_rom_function! { + $(#[$outer])* + unsafe fn $name( $($argname: $ty),* ) -> $ret { + $crate::rom_data::rom_table_lookup($crate::rom_data::FUNC_TABLE, *$c) + } + } + + rom_functions!($($rest)*); + }; +} + +rom_functions! { + /// Return a count of the number of 1 bits in value. + b"P3" fn popcount32(value: u32) -> u32; + + /// Return the bits of value in the reverse order. + b"R3" fn reverse32(value: u32) -> u32; + + /// Return the number of consecutive high order 0 bits of value. If value is zero, returns 32. + b"L3" fn clz32(value: u32) -> u32; + + /// Return the number of consecutive low order 0 bits of value. If value is zero, returns 32. + b"T3" fn ctz32(value: u32) -> u32; + + /// Resets the RP2040 and uses the watchdog facility to re-start in BOOTSEL mode: + /// * gpio_activity_pin_mask is provided to enable an 'activity light' via GPIO attached LED + /// for the USB Mass Storage Device: + /// * 0 No pins are used as per cold boot. + /// * Otherwise a single bit set indicating which GPIO pin should be set to output and + /// raised whenever there is mass storage activity from the host. + /// * disable_interface_mask may be used to control the exposed USB interfaces: + /// * 0 To enable both interfaces (as per cold boot). + /// * 1 To disable the USB Mass Storage Interface. + /// * 2 to Disable the USB PICOBOOT Interface. + b"UB" fn reset_to_usb_boot(gpio_activity_pin_mask: u32, disable_interface_mask: u32) -> (); + + /// Sets n bytes start at ptr to the value c and returns ptr + b"MS" unsafe fn memset(ptr: *mut u8, c: u8, n: u32) -> *mut u8; + + /// Sets n bytes start at ptr to the value c and returns ptr. + /// + /// Note this is a slightly more efficient variant of _memset that may only + /// be used if ptr is word aligned. + // Note the datasheet does not match the actual ROM for the code here, see + // https://github.com/raspberrypi/pico-feedback/issues/217 + b"S4" unsafe fn memset4(ptr: *mut u32, c: u8, n: u32) -> *mut u32; + + /// Copies n bytes starting at src to dest and returns dest. The results are undefined if the + /// regions overlap. + b"MC" unsafe fn memcpy(dest: *mut u8, src: *const u8, n: u32) -> *mut u8; + + /// Copies n bytes starting at src to dest and returns dest. The results are undefined if the + /// regions overlap. + /// + /// Note this is a slightly more efficient variant of _memcpy that may only be + /// used if dest and src are word aligned. + b"C4" unsafe fn memcpy44(dest: *mut u32, src: *const u32, n: u32) -> *mut u8; + + /// Restore all QSPI pad controls to their default state, and connect the SSI to the QSPI pads. + b"IF" unsafe fn connect_internal_flash() -> (); + + /// First set up the SSI for serial-mode operations, then issue the fixed XIP exit sequence. + /// + /// Note that the bootrom code uses the IO forcing logic to drive the CS pin, which must be + /// cleared before returning the SSI to XIP mode (e.g. by a call to _flash_flush_cache). This + /// function configures the SSI with a fixed SCK clock divisor of /6. + b"EX" unsafe fn flash_exit_xip() -> (); + + /// Erase a count bytes, starting at addr (offset from start of flash). Optionally, pass a + /// block erase command e.g. D8h block erase, and the size of the block erased by this + /// command — this function will use the larger block erase where possible, for much higher + /// erase speed. addr must be aligned to a 4096-byte sector, and count must be a multiple of + /// 4096 bytes. + b"RE" unsafe fn flash_range_erase(addr: u32, count: usize, block_size: u32, block_cmd: u8) -> (); + + /// Program data to a range of flash addresses starting at `addr` (and + /// offset from the start of flash) and `count` bytes in size. The value + /// `addr` must be aligned to a 256-byte boundary, and `count` must be a + /// multiple of 256. + b"RP" unsafe fn flash_range_program(addr: u32, data: *const u8, count: usize) -> (); + + /// Flush and enable the XIP cache. Also clears the IO forcing on QSPI CSn, so that the SSI can + /// drive the flashchip select as normal. + b"FC" unsafe fn flash_flush_cache() -> (); + + /// Configure the SSI to generate a standard 03h serial read command, with 24 address bits, + /// upon each XIP access. This is a very slow XIP configuration, but is very widely supported. + /// The debugger calls this function after performing a flash erase/programming operation, so + /// that the freshly-programmed code and data is visible to the debug host, without having to + /// know exactly what kind of flash device is connected. + b"CX" unsafe fn flash_enter_cmd_xip() -> (); + + /// This is the method that is entered by core 1 on reset to wait to be launched by core 0. + /// There are few cases where you should call this method (resetting core 1 is much better). + /// This method does not return and should only ever be called on core 1. + b"WV" unsafe fn wait_for_vector() -> !; +} + +// Various C intrinsics in the ROM +intrinsics! { + #[alias = __popcountdi2] + extern "C" fn __popcountsi2(x: u32) -> u32 { + popcount32(x) + } + + #[alias = __clzdi2] + extern "C" fn __clzsi2(x: u32) -> u32 { + clz32(x) + } + + #[alias = __ctzdi2] + extern "C" fn __ctzsi2(x: u32) -> u32 { + ctz32(x) + } + + // __rbit is only unofficial, but it show up in the ARM documentation, + // so may as well hook it up. + #[alias = __rbitl] + extern "C" fn __rbit(x: u32) -> u32 { + reverse32(x) + } + + unsafe extern "aapcs" fn __aeabi_memset(dest: *mut u8, n: usize, c: i32) -> () { + // Different argument order + memset(dest, c as u8, n as u32); + } + + #[alias = __aeabi_memset8] + unsafe extern "aapcs" fn __aeabi_memset4(dest: *mut u8, n: usize, c: i32) -> () { + // Different argument order + memset4(dest as *mut u32, c as u8, n as u32); + } + + unsafe extern "aapcs" fn __aeabi_memclr(dest: *mut u8, n: usize) -> () { + memset(dest, 0, n as u32); + } + + #[alias = __aeabi_memclr8] + unsafe extern "aapcs" fn __aeabi_memclr4(dest: *mut u8, n: usize) -> () { + memset4(dest as *mut u32, 0, n as u32); + } + + unsafe extern "aapcs" fn __aeabi_memcpy(dest: *mut u8, src: *const u8, n: usize) -> () { + memcpy(dest, src, n as u32); + } + + #[alias = __aeabi_memcpy8] + unsafe extern "aapcs" fn __aeabi_memcpy4(dest: *mut u8, src: *const u8, n: usize) -> () { + memcpy44(dest as *mut u32, src as *const u32, n as u32); + } +} + +unsafe fn convert_str(s: *const u8) -> &'static str { + let mut end = s; + while *end != 0 { + end = end.add(1); + } + let s = core::slice::from_raw_parts(s, end.offset_from(s) as usize); + core::str::from_utf8_unchecked(s) +} + +/// The version number of the rom. +pub fn rom_version_number() -> u8 { + unsafe { *VERSION_NUMBER } +} + +/// The Raspberry Pi Trading Ltd copyright string. +pub fn copyright_string() -> &'static str { + let s: *const u8 = rom_table_lookup(DATA_TABLE, *b"CR"); + unsafe { convert_str(s) } +} + +/// The 8 most significant hex digits of the Bootrom git revision. +pub fn git_revision() -> u32 { + let s: *const u32 = rom_table_lookup(DATA_TABLE, *b"GR"); + unsafe { *s } +} + +/// The start address of the floating point library code and data. +/// +/// This and fplib_end along with the individual function pointers in +/// soft_float_table can be used to copy the floating point implementation into +/// RAM if desired. +pub fn fplib_start() -> *const u8 { + rom_table_lookup(DATA_TABLE, *b"FS") +} + +/// See Table 180 in the RP2040 datasheet for the contents of this table. +#[cfg_attr(feature = "rom-func-cache", inline(never))] +pub fn soft_float_table() -> *const usize { + rom_table_lookup(DATA_TABLE, *b"SF") +} + +/// The end address of the floating point library code and data. +pub fn fplib_end() -> *const u8 { + rom_table_lookup(DATA_TABLE, *b"FE") +} + +/// This entry is only present in the V2 bootrom. See Table 182 in the RP2040 datasheet for the contents of this table. +#[cfg_attr(feature = "rom-func-cache", inline(never))] +pub fn soft_double_table() -> *const usize { + if rom_version_number() < 2 { + panic!( + "Double precision operations require V2 bootrom (found: V{})", + rom_version_number() + ); + } + rom_table_lookup(DATA_TABLE, *b"SD") +} + +/// ROM functions using single-precision arithmetic (i.e. 'f32' in Rust terms) +pub mod float_funcs { + + macro_rules! make_functions { + ( + $( + $(#[$outer:meta])* + $offset:literal $name:ident ( + $( $aname:ident : $aty:ty ),* + ) -> $ret:ty; + )* + ) => { + $( + declare_rom_function! { + $(#[$outer])* + fn $name( $( $aname : $aty ),* ) -> $ret { + let table: *const usize = $crate::rom_data::soft_float_table(); + unsafe { + // This is the entry in the table. Our offset is given as a + // byte offset, but we want the table index (each pointer in + // the table is 4 bytes long) + let entry: *const usize = table.offset($offset / 4); + // Read the pointer from the table + core::ptr::read(entry) as *const u32 + } + } + } + )* + } + } + + make_functions! { + /// Calculates `a + b` + 0x00 fadd(a: f32, b: f32) -> f32; + /// Calculates `a - b` + 0x04 fsub(a: f32, b: f32) -> f32; + /// Calculates `a * b` + 0x08 fmul(a: f32, b: f32) -> f32; + /// Calculates `a / b` + 0x0c fdiv(a: f32, b: f32) -> f32; + + // 0x10 and 0x14 are deprecated + + /// Calculates `sqrt(v)` (or return -Infinity if v is negative) + 0x18 fsqrt(v: f32) -> f32; + /// Converts an f32 to a signed integer, + /// rounding towards -Infinity, and clamping the result to lie within the + /// range `-0x80000000` to `0x7FFFFFFF` + 0x1c float_to_int(v: f32) -> i32; + /// Converts an f32 to an signed fixed point + /// integer representation where n specifies the position of the binary + /// point in the resulting fixed point representation, e.g. + /// `f(0.5f, 16) == 0x8000`. This method rounds towards -Infinity, + /// and clamps the resulting integer to lie within the range `0x00000000` to + /// `0xFFFFFFFF` + 0x20 float_to_fix(v: f32, n: i32) -> i32; + /// Converts an f32 to an unsigned integer, + /// rounding towards -Infinity, and clamping the result to lie within the + /// range `0x00000000` to `0xFFFFFFFF` + 0x24 float_to_uint(v: f32) -> u32; + /// Converts an f32 to an unsigned fixed point + /// integer representation where n specifies the position of the binary + /// point in the resulting fixed point representation, e.g. + /// `f(0.5f, 16) == 0x8000`. This method rounds towards -Infinity, + /// and clamps the resulting integer to lie within the range `0x00000000` to + /// `0xFFFFFFFF` + 0x28 float_to_ufix(v: f32, n: i32) -> u32; + /// Converts a signed integer to the nearest + /// f32 value, rounding to even on tie + 0x2c int_to_float(v: i32) -> f32; + /// Converts a signed fixed point integer + /// representation to the nearest f32 value, rounding to even on tie. `n` + /// specifies the position of the binary point in fixed point, so `f = + /// nearest(v/(2^n))` + 0x30 fix_to_float(v: i32, n: i32) -> f32; + /// Converts an unsigned integer to the nearest + /// f32 value, rounding to even on tie + 0x34 uint_to_float(v: u32) -> f32; + /// Converts an unsigned fixed point integer + /// representation to the nearest f32 value, rounding to even on tie. `n` + /// specifies the position of the binary point in fixed point, so `f = + /// nearest(v/(2^n))` + 0x38 ufix_to_float(v: u32, n: i32) -> f32; + /// Calculates the cosine of `angle`. The value + /// of `angle` is in radians, and must be in the range `-1024` to `1024` + 0x3c fcos(angle: f32) -> f32; + /// Calculates the sine of `angle`. The value of + /// `angle` is in radians, and must be in the range `-1024` to `1024` + 0x40 fsin(angle: f32) -> f32; + /// Calculates the tangent of `angle`. The value + /// of `angle` is in radians, and must be in the range `-1024` to `1024` + 0x44 ftan(angle: f32) -> f32; + + // 0x48 is deprecated + + /// Calculates the exponential value of `v`, + /// i.e. `e ** v` + 0x4c fexp(v: f32) -> f32; + /// Calculates the natural logarithm of `v`. If `v <= 0` return -Infinity + 0x50 fln(v: f32) -> f32; + } + + macro_rules! make_functions_v2 { + ( + $( + $(#[$outer:meta])* + $offset:literal $name:ident ( + $( $aname:ident : $aty:ty ),* + ) -> $ret:ty; + )* + ) => { + $( + declare_rom_function! { + $(#[$outer])* + fn $name( $( $aname : $aty ),* ) -> $ret { + if $crate::rom_data::rom_version_number() < 2 { + panic!( + "Floating point function requires V2 bootrom (found: V{})", + $crate::rom_data::rom_version_number() + ); + } + let table: *const usize = $crate::rom_data::soft_float_table(); + unsafe { + // This is the entry in the table. Our offset is given as a + // byte offset, but we want the table index (each pointer in + // the table is 4 bytes long) + let entry: *const usize = table.offset($offset / 4); + // Read the pointer from the table + core::ptr::read(entry) as *const u32 + } + } + } + )* + } + } + + // These are only on BootROM v2 or higher + make_functions_v2! { + /// Compares two floating point numbers, returning: + /// • 0 if a == b + /// • -1 if a < b + /// • 1 if a > b + 0x54 fcmp(a: f32, b: f32) -> i32; + /// Computes the arc tangent of `y/x` using the + /// signs of arguments to determine the correct quadrant + 0x58 fatan2(y: f32, x: f32) -> f32; + /// Converts a signed 64-bit integer to the + /// nearest f32 value, rounding to even on tie + 0x5c int64_to_float(v: i64) -> f32; + /// Converts a signed fixed point 64-bit integer + /// representation to the nearest f32 value, rounding to even on tie. `n` + /// specifies the position of the binary point in fixed point, so `f = + /// nearest(v/(2^n))` + 0x60 fix64_to_float(v: i64, n: i32) -> f32; + /// Converts an unsigned 64-bit integer to the + /// nearest f32 value, rounding to even on tie + 0x64 uint64_to_float(v: u64) -> f32; + /// Converts an unsigned fixed point 64-bit + /// integer representation to the nearest f32 value, rounding to even on + /// tie. `n` specifies the position of the binary point in fixed point, so + /// `f = nearest(v/(2^n))` + 0x68 ufix64_to_float(v: u64, n: i32) -> f32; + /// Convert an f32 to a signed 64-bit integer, rounding towards -Infinity, + /// and clamping the result to lie within the range `-0x8000000000000000` to + /// `0x7FFFFFFFFFFFFFFF` + 0x6c float_to_int64(v: f32) -> i64; + /// Converts an f32 to a signed fixed point + /// 64-bit integer representation where n specifies the position of the + /// binary point in the resulting fixed point representation - e.g. `f(0.5f, + /// 16) == 0x8000`. This method rounds towards -Infinity, and clamps the + /// resulting integer to lie within the range `-0x8000000000000000` to + /// `0x7FFFFFFFFFFFFFFF` + 0x70 float_to_fix64(v: f32, n: i32) -> f32; + /// Converts an f32 to an unsigned 64-bit + /// integer, rounding towards -Infinity, and clamping the result to lie + /// within the range `0x0000000000000000` to `0xFFFFFFFFFFFFFFFF` + 0x74 float_to_uint64(v: f32) -> u64; + /// Converts an f32 to an unsigned fixed point + /// 64-bit integer representation where n specifies the position of the + /// binary point in the resulting fixed point representation, e.g. `f(0.5f, + /// 16) == 0x8000`. This method rounds towards -Infinity, and clamps the + /// resulting integer to lie within the range `0x0000000000000000` to + /// `0xFFFFFFFFFFFFFFFF` + 0x78 float_to_ufix64(v: f32, n: i32) -> u64; + /// Converts an f32 to an f64. + 0x7c float_to_double(v: f32) -> f64; + } +} + +/// Functions using double-precision arithmetic (i.e. 'f64' in Rust terms) +pub mod double_funcs { + + macro_rules! make_double_funcs { + ( + $( + $(#[$outer:meta])* + $offset:literal $name:ident ( + $( $aname:ident : $aty:ty ),* + ) -> $ret:ty; + )* + ) => { + $( + declare_rom_function! { + $(#[$outer])* + fn $name( $( $aname : $aty ),* ) -> $ret { + let table: *const usize = $crate::rom_data::soft_double_table(); + unsafe { + // This is the entry in the table. Our offset is given as a + // byte offset, but we want the table index (each pointer in + // the table is 4 bytes long) + let entry: *const usize = table.offset($offset / 4); + // Read the pointer from the table + core::ptr::read(entry) as *const u32 + } + } + } + )* + } + } + + make_double_funcs! { + /// Calculates `a + b` + 0x00 dadd(a: f64, b: f64) -> f64; + /// Calculates `a - b` + 0x04 dsub(a: f64, b: f64) -> f64; + /// Calculates `a * b` + 0x08 dmul(a: f64, b: f64) -> f64; + /// Calculates `a / b` + 0x0c ddiv(a: f64, b: f64) -> f64; + + // 0x10 and 0x14 are deprecated + + /// Calculates `sqrt(v)` (or return -Infinity if v is negative) + 0x18 dsqrt(v: f64) -> f64; + /// Converts an f64 to a signed integer, + /// rounding towards -Infinity, and clamping the result to lie within the + /// range `-0x80000000` to `0x7FFFFFFF` + 0x1c double_to_int(v: f64) -> i32; + /// Converts an f64 to an signed fixed point + /// integer representation where n specifies the position of the binary + /// point in the resulting fixed point representation, e.g. + /// `f(0.5f, 16) == 0x8000`. This method rounds towards -Infinity, + /// and clamps the resulting integer to lie within the range `0x00000000` to + /// `0xFFFFFFFF` + 0x20 double_to_fix(v: f64, n: i32) -> i32; + /// Converts an f64 to an unsigned integer, + /// rounding towards -Infinity, and clamping the result to lie within the + /// range `0x00000000` to `0xFFFFFFFF` + 0x24 double_to_uint(v: f64) -> u32; + /// Converts an f64 to an unsigned fixed point + /// integer representation where n specifies the position of the binary + /// point in the resulting fixed point representation, e.g. + /// `f(0.5f, 16) == 0x8000`. This method rounds towards -Infinity, + /// and clamps the resulting integer to lie within the range `0x00000000` to + /// `0xFFFFFFFF` + 0x28 double_to_ufix(v: f64, n: i32) -> u32; + /// Converts a signed integer to the nearest + /// double value, rounding to even on tie + 0x2c int_to_double(v: i32) -> f64; + /// Converts a signed fixed point integer + /// representation to the nearest double value, rounding to even on tie. `n` + /// specifies the position of the binary point in fixed point, so `f = + /// nearest(v/(2^n))` + 0x30 fix_to_double(v: i32, n: i32) -> f64; + /// Converts an unsigned integer to the nearest + /// double value, rounding to even on tie + 0x34 uint_to_double(v: u32) -> f64; + /// Converts an unsigned fixed point integer + /// representation to the nearest double value, rounding to even on tie. `n` + /// specifies the position of the binary point in fixed point, so f = + /// nearest(v/(2^n)) + 0x38 ufix_to_double(v: u32, n: i32) -> f64; + /// Calculates the cosine of `angle`. The value + /// of `angle` is in radians, and must be in the range `-1024` to `1024` + 0x3c dcos(angle: f64) -> f64; + /// Calculates the sine of `angle`. The value of + /// `angle` is in radians, and must be in the range `-1024` to `1024` + 0x40 dsin(angle: f64) -> f64; + /// Calculates the tangent of `angle`. The value + /// of `angle` is in radians, and must be in the range `-1024` to `1024` + 0x44 dtan(angle: f64) -> f64; + + // 0x48 is deprecated + + /// Calculates the exponential value of `v`, + /// i.e. `e ** v` + 0x4c dexp(v: f64) -> f64; + /// Calculates the natural logarithm of v. If v <= 0 return -Infinity + 0x50 dln(v: f64) -> f64; + + // These are only on BootROM v2 or higher + + /// Compares two floating point numbers, returning: + /// • 0 if a == b + /// • -1 if a < b + /// • 1 if a > b + 0x54 dcmp(a: f64, b: f64) -> i32; + /// Computes the arc tangent of `y/x` using the + /// signs of arguments to determine the correct quadrant + 0x58 datan2(y: f64, x: f64) -> f64; + /// Converts a signed 64-bit integer to the + /// nearest double value, rounding to even on tie + 0x5c int64_to_double(v: i64) -> f64; + /// Converts a signed fixed point 64-bit integer + /// representation to the nearest double value, rounding to even on tie. `n` + /// specifies the position of the binary point in fixed point, so `f = + /// nearest(v/(2^n))` + 0x60 fix64_to_doubl(v: i64, n: i32) -> f64; + /// Converts an unsigned 64-bit integer to the + /// nearest double value, rounding to even on tie + 0x64 uint64_to_double(v: u64) -> f64; + /// Converts an unsigned fixed point 64-bit + /// integer representation to the nearest double value, rounding to even on + /// tie. `n` specifies the position of the binary point in fixed point, so + /// `f = nearest(v/(2^n))` + 0x68 ufix64_to_double(v: u64, n: i32) -> f64; + /// Convert an f64 to a signed 64-bit integer, rounding towards -Infinity, + /// and clamping the result to lie within the range `-0x8000000000000000` to + /// `0x7FFFFFFFFFFFFFFF` + 0x6c double_to_int64(v: f64) -> i64; + /// Converts an f64 to a signed fixed point + /// 64-bit integer representation where n specifies the position of the + /// binary point in the resulting fixed point representation - e.g. `f(0.5f, + /// 16) == 0x8000`. This method rounds towards -Infinity, and clamps the + /// resulting integer to lie within the range `-0x8000000000000000` to + /// `0x7FFFFFFFFFFFFFFF` + 0x70 double_to_fix64(v: f64, n: i32) -> i64; + /// Converts an f64 to an unsigned 64-bit + /// integer, rounding towards -Infinity, and clamping the result to lie + /// within the range `0x0000000000000000` to `0xFFFFFFFFFFFFFFFF` + 0x74 double_to_uint64(v: f64) -> u64; + /// Converts an f64 to an unsigned fixed point + /// 64-bit integer representation where n specifies the position of the + /// binary point in the resulting fixed point representation, e.g. `f(0.5f, + /// 16) == 0x8000`. This method rounds towards -Infinity, and clamps the + /// resulting integer to lie within the range `0x0000000000000000` to + /// `0xFFFFFFFFFFFFFFFF` + 0x78 double_to_ufix64(v: f64, n: i32) -> u64; + /// Converts an f64 to an f32 + 0x7c double_to_float(v: f64) -> f32; + } +} diff --git a/embassy-rp/src/rtc/datetime_chrono.rs b/embassy-rp/src/rtc/datetime_chrono.rs new file mode 100644 index 000000000..b3c78dd47 --- /dev/null +++ b/embassy-rp/src/rtc/datetime_chrono.rs @@ -0,0 +1,62 @@ +use chrono::{Datelike, Timelike}; + +use crate::pac::rtc::regs::{Rtc0, Rtc1, Setup0, Setup1}; + +/// Alias for [`chrono::NaiveDateTime`] +pub type DateTime = chrono::NaiveDateTime; +/// Alias for [`chrono::Weekday`] +pub type DayOfWeek = chrono::Weekday; + +/// Errors regarding the [`DateTime`] and [`DateTimeFilter`] structs. +/// +/// [`DateTimeFilter`]: struct.DateTimeFilter.html +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Error { + /// The [DateTime] has an invalid year. The year must be between 0 and 4095. + InvalidYear, + /// The [DateTime] contains an invalid date. + InvalidDate, + /// The [DateTime] contains an invalid time. + InvalidTime, +} + +pub(super) fn day_of_week_to_u8(dotw: DayOfWeek) -> u8 { + dotw.num_days_from_sunday() as u8 +} + +pub(crate) fn validate_datetime(dt: &DateTime) -> Result<(), Error> { + if dt.year() < 0 || dt.year() > 4095 { + // rp2040 can't hold these years + Err(Error::InvalidYear) + } else { + // The rest of the chrono date is assumed to be valid + Ok(()) + } +} + +pub(super) fn write_setup_0(dt: &DateTime, w: &mut Setup0) { + w.set_year(dt.year() as u16); + w.set_month(dt.month() as u8); + w.set_day(dt.day() as u8); +} + +pub(super) fn write_setup_1(dt: &DateTime, w: &mut Setup1) { + w.set_dotw(dt.weekday().num_days_from_sunday() as u8); + w.set_hour(dt.hour() as u8); + w.set_min(dt.minute() as u8); + w.set_sec(dt.second() as u8); +} + +pub(super) fn datetime_from_registers(rtc_0: Rtc0, rtc_1: Rtc1) -> Result { + let year = rtc_1.year() as i32; + let month = rtc_1.month() as u32; + let day = rtc_1.day() as u32; + + let hour = rtc_0.hour() as u32; + let minute = rtc_0.min() as u32; + let second = rtc_0.sec() as u32; + + let date = chrono::NaiveDate::from_ymd_opt(year, month, day).ok_or(Error::InvalidDate)?; + let time = chrono::NaiveTime::from_hms_opt(hour, minute, second).ok_or(Error::InvalidTime)?; + Ok(DateTime::new(date, time)) +} diff --git a/embassy-rp/src/rtc/datetime_no_deps.rs b/embassy-rp/src/rtc/datetime_no_deps.rs new file mode 100644 index 000000000..ea899c339 --- /dev/null +++ b/embassy-rp/src/rtc/datetime_no_deps.rs @@ -0,0 +1,128 @@ +use crate::pac::rtc::regs::{Rtc0, Rtc1, Setup0, Setup1}; + +/// Errors regarding the [`DateTime`] and [`DateTimeFilter`] structs. +/// +/// [`DateTimeFilter`]: struct.DateTimeFilter.html +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Error { + /// The [DateTime] contains an invalid year value. Must be between `0..=4095`. + InvalidYear, + /// The [DateTime] contains an invalid month value. Must be between `1..=12`. + InvalidMonth, + /// The [DateTime] contains an invalid day value. Must be between `1..=31`. + InvalidDay, + /// The [DateTime] contains an invalid day of week. Must be between `0..=6` where 0 is Sunday. + InvalidDayOfWeek( + /// The value of the DayOfWeek that was given. + u8, + ), + /// The [DateTime] contains an invalid hour value. Must be between `0..=23`. + InvalidHour, + /// The [DateTime] contains an invalid minute value. Must be between `0..=59`. + InvalidMinute, + /// The [DateTime] contains an invalid second value. Must be between `0..=59`. + InvalidSecond, +} + +/// Structure containing date and time information +#[derive(Clone, Debug)] +pub struct DateTime { + /// 0..4095 + pub year: u16, + /// 1..12, 1 is January + pub month: u8, + /// 1..28,29,30,31 depending on month + pub day: u8, + /// + pub day_of_week: DayOfWeek, + /// 0..23 + pub hour: u8, + /// 0..59 + pub minute: u8, + /// 0..59 + pub second: u8, +} + +/// A day of the week +#[repr(u8)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] +#[allow(missing_docs)] +pub enum DayOfWeek { + Sunday = 0, + Monday = 1, + Tuesday = 2, + Wednesday = 3, + Thursday = 4, + Friday = 5, + Saturday = 6, +} + +fn day_of_week_from_u8(v: u8) -> Result { + Ok(match v { + 0 => DayOfWeek::Sunday, + 1 => DayOfWeek::Monday, + 2 => DayOfWeek::Tuesday, + 3 => DayOfWeek::Wednesday, + 4 => DayOfWeek::Thursday, + 5 => DayOfWeek::Friday, + 6 => DayOfWeek::Saturday, + x => return Err(Error::InvalidDayOfWeek(x)), + }) +} + +pub(super) fn day_of_week_to_u8(dotw: DayOfWeek) -> u8 { + dotw as u8 +} + +pub(super) fn validate_datetime(dt: &DateTime) -> Result<(), Error> { + if dt.year > 4095 { + Err(Error::InvalidYear) + } else if dt.month < 1 || dt.month > 12 { + Err(Error::InvalidMonth) + } else if dt.day < 1 || dt.day > 31 { + Err(Error::InvalidDay) + } else if dt.hour > 23 { + Err(Error::InvalidHour) + } else if dt.minute > 59 { + Err(Error::InvalidMinute) + } else if dt.second > 59 { + Err(Error::InvalidSecond) + } else { + Ok(()) + } +} + +pub(super) fn write_setup_0(dt: &DateTime, w: &mut Setup0) { + w.set_year(dt.year); + w.set_month(dt.month); + w.set_day(dt.day); +} + +pub(super) fn write_setup_1(dt: &DateTime, w: &mut Setup1) { + w.set_dotw(dt.day_of_week as u8); + w.set_hour(dt.hour); + w.set_min(dt.minute); + w.set_sec(dt.second); +} + +pub(super) fn datetime_from_registers(rtc_0: Rtc0, rtc_1: Rtc1) -> Result { + let year = rtc_1.year(); + let month = rtc_1.month(); + let day = rtc_1.day(); + + let day_of_week = rtc_0.dotw(); + let hour = rtc_0.hour(); + let minute = rtc_0.min(); + let second = rtc_0.sec(); + + let day_of_week = day_of_week_from_u8(day_of_week)?; + Ok(DateTime { + year, + month, + day, + day_of_week, + hour, + minute, + second, + }) +} diff --git a/embassy-rp/src/rtc/filter.rs b/embassy-rp/src/rtc/filter.rs new file mode 100644 index 000000000..d4a3bab2f --- /dev/null +++ b/embassy-rp/src/rtc/filter.rs @@ -0,0 +1,100 @@ +use super::DayOfWeek; +use crate::pac::rtc::regs::{IrqSetup0, IrqSetup1}; + +/// A filter used for [`RealTimeClock::schedule_alarm`]. +/// +/// [`RealTimeClock::schedule_alarm`]: struct.RealTimeClock.html#method.schedule_alarm +#[derive(Default)] +pub struct DateTimeFilter { + /// The year that this alarm should trigger on, `None` if the RTC alarm should not trigger on a year value. + pub year: Option, + /// The month that this alarm should trigger on, `None` if the RTC alarm should not trigger on a month value. + pub month: Option, + /// The day that this alarm should trigger on, `None` if the RTC alarm should not trigger on a day value. + pub day: Option, + /// The day of week that this alarm should trigger on, `None` if the RTC alarm should not trigger on a day of week value. + pub day_of_week: Option, + /// The hour that this alarm should trigger on, `None` if the RTC alarm should not trigger on a hour value. + pub hour: Option, + /// The minute that this alarm should trigger on, `None` if the RTC alarm should not trigger on a minute value. + pub minute: Option, + /// The second that this alarm should trigger on, `None` if the RTC alarm should not trigger on a second value. + pub second: Option, +} + +impl DateTimeFilter { + /// Set a filter on the given year + pub fn year(mut self, year: u16) -> Self { + self.year = Some(year); + self + } + /// Set a filter on the given month + pub fn month(mut self, month: u8) -> Self { + self.month = Some(month); + self + } + /// Set a filter on the given day + pub fn day(mut self, day: u8) -> Self { + self.day = Some(day); + self + } + /// Set a filter on the given day of the week + pub fn day_of_week(mut self, day_of_week: DayOfWeek) -> Self { + self.day_of_week = Some(day_of_week); + self + } + /// Set a filter on the given hour + pub fn hour(mut self, hour: u8) -> Self { + self.hour = Some(hour); + self + } + /// Set a filter on the given minute + pub fn minute(mut self, minute: u8) -> Self { + self.minute = Some(minute); + self + } + /// Set a filter on the given second + pub fn second(mut self, second: u8) -> Self { + self.second = Some(second); + self + } +} + +// register helper functions +impl DateTimeFilter { + pub(super) fn write_setup_0(&self, w: &mut IrqSetup0) { + if let Some(year) = self.year { + w.set_year_ena(true); + + w.set_year(year); + } + if let Some(month) = self.month { + w.set_month_ena(true); + w.set_month(month); + } + if let Some(day) = self.day { + w.set_day_ena(true); + w.set_day(day); + } + } + pub(super) fn write_setup_1(&self, w: &mut IrqSetup1) { + if let Some(day_of_week) = self.day_of_week { + w.set_dotw_ena(true); + let bits = super::datetime::day_of_week_to_u8(day_of_week); + + w.set_dotw(bits); + } + if let Some(hour) = self.hour { + w.set_hour_ena(true); + w.set_hour(hour); + } + if let Some(minute) = self.minute { + w.set_min_ena(true); + w.set_min(minute); + } + if let Some(second) = self.second { + w.set_sec_ena(true); + w.set_sec(second); + } + } +} diff --git a/embassy-rp/src/rtc/mod.rs b/embassy-rp/src/rtc/mod.rs new file mode 100644 index 000000000..1b33fdf8d --- /dev/null +++ b/embassy-rp/src/rtc/mod.rs @@ -0,0 +1,204 @@ +mod filter; + +use embassy_hal_common::{into_ref, Peripheral, PeripheralRef}; + +pub use self::filter::DateTimeFilter; + +#[cfg_attr(feature = "chrono", path = "datetime_chrono.rs")] +#[cfg_attr(not(feature = "chrono"), path = "datetime_no_deps.rs")] +mod datetime; + +pub use self::datetime::{DateTime, DayOfWeek, Error as DateTimeError}; +use crate::clocks::clk_rtc_freq; + +/// A reference to the real time clock of the system +pub struct Rtc<'d, T: Instance> { + inner: PeripheralRef<'d, T>, +} + +impl<'d, T: Instance> Rtc<'d, T> { + /// Create a new instance of the real time clock, with the given date as an initial value. + /// + /// # Errors + /// + /// Will return `RtcError::InvalidDateTime` if the datetime is not a valid range. + pub fn new(inner: impl Peripheral

+ 'd) -> Self { + into_ref!(inner); + + // Set the RTC divider + inner.regs().clkdiv_m1().write(|w| w.set_clkdiv_m1(clk_rtc_freq() - 1)); + + let result = Self { inner }; + result + } + + /// Enable or disable the leap year check. The rp2040 chip will always add a Feb 29th on every year that is divisable by 4, but this may be incorrect (e.g. on century years). This function allows you to disable this check. + /// + /// Leap year checking is enabled by default. + pub fn set_leap_year_check(&mut self, leap_year_check_enabled: bool) { + self.inner.regs().ctrl().modify(|w| { + w.set_force_notleapyear(!leap_year_check_enabled); + }); + } + + /// Set the time from internal format + pub fn restore(&mut self, ymd: rp_pac::rtc::regs::Rtc1, hms: rp_pac::rtc::regs::Rtc0) { + // disable RTC while we configure it + self.inner.regs().ctrl().modify(|w| w.set_rtc_enable(false)); + while self.inner.regs().ctrl().read().rtc_active() { + core::hint::spin_loop(); + } + + self.inner.regs().setup_0().write(|w| { + *w = rp_pac::rtc::regs::Setup0(ymd.0); + }); + self.inner.regs().setup_1().write(|w| { + *w = rp_pac::rtc::regs::Setup1(hms.0); + }); + + // Load the new datetime and re-enable RTC + self.inner.regs().ctrl().write(|w| w.set_load(true)); + self.inner.regs().ctrl().write(|w| w.set_rtc_enable(true)); + while !self.inner.regs().ctrl().read().rtc_active() { + core::hint::spin_loop(); + } + } + + /// Get the time in internal format + pub fn save(&mut self) -> (rp_pac::rtc::regs::Rtc1, rp_pac::rtc::regs::Rtc0) { + let rtc_0: rp_pac::rtc::regs::Rtc0 = self.inner.regs().rtc_0().read(); + let rtc_1 = self.inner.regs().rtc_1().read(); + (rtc_1, rtc_0) + } + + /// Checks to see if this Rtc is running + pub fn is_running(&self) -> bool { + self.inner.regs().ctrl().read().rtc_active() + } + + /// Set the datetime to a new value. + /// + /// # Errors + /// + /// Will return `RtcError::InvalidDateTime` if the datetime is not a valid range. + pub fn set_datetime(&mut self, t: DateTime) -> Result<(), RtcError> { + self::datetime::validate_datetime(&t).map_err(RtcError::InvalidDateTime)?; + + // disable RTC while we configure it + self.inner.regs().ctrl().modify(|w| w.set_rtc_enable(false)); + while self.inner.regs().ctrl().read().rtc_active() { + core::hint::spin_loop(); + } + + self.inner.regs().setup_0().write(|w| { + self::datetime::write_setup_0(&t, w); + }); + self.inner.regs().setup_1().write(|w| { + self::datetime::write_setup_1(&t, w); + }); + + // Load the new datetime and re-enable RTC + self.inner.regs().ctrl().write(|w| w.set_load(true)); + self.inner.regs().ctrl().write(|w| w.set_rtc_enable(true)); + while !self.inner.regs().ctrl().read().rtc_active() { + core::hint::spin_loop(); + } + Ok(()) + } + + /// Return the current datetime. + /// + /// # Errors + /// + /// Will return an `RtcError::InvalidDateTime` if the stored value in the system is not a valid [`DayOfWeek`]. + pub fn now(&self) -> Result { + if !self.is_running() { + return Err(RtcError::NotRunning); + } + + let rtc_0 = self.inner.regs().rtc_0().read(); + let rtc_1 = self.inner.regs().rtc_1().read(); + + self::datetime::datetime_from_registers(rtc_0, rtc_1).map_err(RtcError::InvalidDateTime) + } + + /// Disable the alarm that was scheduled with [`schedule_alarm`]. + /// + /// [`schedule_alarm`]: #method.schedule_alarm + pub fn disable_alarm(&mut self) { + self.inner.regs().irq_setup_0().modify(|s| s.set_match_ena(false)); + + while self.inner.regs().irq_setup_0().read().match_active() { + core::hint::spin_loop(); + } + } + + /// Schedule an alarm. The `filter` determines at which point in time this alarm is set. + /// + /// Keep in mind that the filter only triggers on the specified time. If you want to schedule this alarm every minute, you have to call: + /// ```no_run + /// # #[cfg(feature = "chrono")] + /// # fn main() { } + /// # #[cfg(not(feature = "chrono"))] + /// # fn main() { + /// # use embassy_rp::rtc::{Rtc, DateTimeFilter}; + /// # let mut real_time_clock: Rtc = unsafe { core::mem::zeroed() }; + /// let now = real_time_clock.now().unwrap(); + /// real_time_clock.schedule_alarm( + /// DateTimeFilter::default() + /// .minute(if now.minute == 59 { 0 } else { now.minute + 1 }) + /// ); + /// # } + /// ``` + pub fn schedule_alarm(&mut self, filter: DateTimeFilter) { + self.disable_alarm(); + + self.inner.regs().irq_setup_0().write(|w| { + filter.write_setup_0(w); + }); + self.inner.regs().irq_setup_1().write(|w| { + filter.write_setup_1(w); + }); + + self.inner.regs().inte().modify(|w| w.set_rtc(true)); + + // Set the enable bit and check if it is set + self.inner.regs().irq_setup_0().modify(|w| w.set_match_ena(true)); + while !self.inner.regs().irq_setup_0().read().match_active() { + core::hint::spin_loop(); + } + } + + /// Clear the interrupt. This should be called every time the `RTC_IRQ` interrupt is triggered, + /// or the next [`schedule_alarm`] will never fire. + /// + /// [`schedule_alarm`]: #method.schedule_alarm + pub fn clear_interrupt(&mut self) { + self.disable_alarm(); + } +} + +/// Errors that can occur on methods on [Rtc] +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum RtcError { + /// An invalid DateTime was given or stored on the hardware. + InvalidDateTime(DateTimeError), + + /// The RTC clock is not running + NotRunning, +} + +mod sealed { + pub trait Instance { + fn regs(&self) -> crate::pac::rtc::Rtc; + } +} + +pub trait Instance: sealed::Instance {} + +impl sealed::Instance for crate::peripherals::RTC { + fn regs(&self) -> crate::pac::rtc::Rtc { + crate::pac::RTC + } +} +impl Instance for crate::peripherals::RTC {} diff --git a/embassy-rp/src/spi.rs b/embassy-rp/src/spi.rs index d0261598e..af101cf4a 100644 --- a/embassy-rp/src/spi.rs +++ b/embassy-rp/src/spi.rs @@ -1,7 +1,12 @@ +//! Serial Peripheral Interface +use core::marker::PhantomData; + use embassy_embedded_hal::SetConfig; +use embassy_futures::join::join; use embassy_hal_common::{into_ref, PeripheralRef}; pub use embedded_hal_02::spi::{Phase, Polarity}; +use crate::dma::{AnyChannel, Channel}; use crate::gpio::sealed::Pin as _; use crate::gpio::{AnyPin, Pin as GpioPin}; use crate::{pac, peripherals, Peripheral}; @@ -14,6 +19,7 @@ pub enum Error { } #[non_exhaustive] +#[derive(Clone)] pub struct Config { pub frequency: u32, pub phase: Phase, @@ -30,8 +36,11 @@ impl Default for Config { } } -pub struct Spi<'d, T: Instance> { +pub struct Spi<'d, T: Instance, M: Mode> { inner: PeripheralRef<'d, T>, + tx_dma: Option>, + rx_dma: Option>, + phantom: PhantomData<(&'d mut T, M)>, } fn div_roundup(a: u32, b: u32) -> u32 { @@ -57,8 +66,137 @@ fn calc_prescs(freq: u32) -> (u8, u8) { ((presc * 2) as u8, (postdiv - 1) as u8) } -impl<'d, T: Instance> Spi<'d, T> { - pub fn new( +impl<'d, T: Instance, M: Mode> Spi<'d, T, M> { + fn new_inner( + inner: impl Peripheral

+ 'd, + clk: Option>, + mosi: Option>, + miso: Option>, + cs: Option>, + tx_dma: Option>, + rx_dma: Option>, + config: Config, + ) -> Self { + into_ref!(inner); + + let p = inner.regs(); + let (presc, postdiv) = calc_prescs(config.frequency); + + p.cpsr().write(|w| w.set_cpsdvsr(presc)); + p.cr0().write(|w| { + w.set_dss(0b0111); // 8bit + w.set_spo(config.polarity == Polarity::IdleHigh); + w.set_sph(config.phase == Phase::CaptureOnSecondTransition); + w.set_scr(postdiv); + }); + + // Always enable DREQ signals -- harmless if DMA is not listening + p.dmacr().write(|reg| { + reg.set_rxdmae(true); + reg.set_txdmae(true); + }); + + // finally, enable. + p.cr1().write(|w| w.set_sse(true)); + + if let Some(pin) = &clk { + pin.io().ctrl().write(|w| w.set_funcsel(1)); + } + if let Some(pin) = &mosi { + pin.io().ctrl().write(|w| w.set_funcsel(1)); + } + if let Some(pin) = &miso { + pin.io().ctrl().write(|w| w.set_funcsel(1)); + } + if let Some(pin) = &cs { + pin.io().ctrl().write(|w| w.set_funcsel(1)); + } + Self { + inner, + tx_dma, + rx_dma, + phantom: PhantomData, + } + } + + pub fn blocking_write(&mut self, data: &[u8]) -> Result<(), Error> { + let p = self.inner.regs(); + for &b in data { + while !p.sr().read().tnf() {} + p.dr().write(|w| w.set_data(b as _)); + while !p.sr().read().rne() {} + let _ = p.dr().read(); + } + self.flush()?; + Ok(()) + } + + pub fn blocking_transfer_in_place(&mut self, data: &mut [u8]) -> Result<(), Error> { + let p = self.inner.regs(); + for b in data { + while !p.sr().read().tnf() {} + p.dr().write(|w| w.set_data(*b as _)); + while !p.sr().read().rne() {} + *b = p.dr().read().data() as u8; + } + self.flush()?; + Ok(()) + } + + pub fn blocking_read(&mut self, data: &mut [u8]) -> Result<(), Error> { + let p = self.inner.regs(); + for b in data { + while !p.sr().read().tnf() {} + p.dr().write(|w| w.set_data(0)); + while !p.sr().read().rne() {} + *b = p.dr().read().data() as u8; + } + self.flush()?; + Ok(()) + } + + pub fn blocking_transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Error> { + let p = self.inner.regs(); + let len = read.len().max(write.len()); + for i in 0..len { + let wb = write.get(i).copied().unwrap_or(0); + while !p.sr().read().tnf() {} + p.dr().write(|w| w.set_data(wb as _)); + while !p.sr().read().rne() {} + let rb = p.dr().read().data() as u8; + if let Some(r) = read.get_mut(i) { + *r = rb; + } + } + self.flush()?; + Ok(()) + } + + pub fn flush(&mut self) -> Result<(), Error> { + let p = self.inner.regs(); + while p.sr().read().bsy() {} + Ok(()) + } + + pub fn set_frequency(&mut self, freq: u32) { + let (presc, postdiv) = calc_prescs(freq); + let p = self.inner.regs(); + // disable + p.cr1().write(|w| w.set_sse(false)); + + // change stuff + p.cpsr().write(|w| w.set_cpsdvsr(presc)); + p.cr0().modify(|w| { + w.set_scr(postdiv); + }); + + // enable + p.cr1().write(|w| w.set_sse(true)); + } +} + +impl<'d, T: Instance> Spi<'d, T, Blocking> { + pub fn new_blocking( inner: impl Peripheral

+ 'd, clk: impl Peripheral

+ 'd> + 'd, mosi: impl Peripheral

+ 'd> + 'd, @@ -72,6 +210,70 @@ impl<'d, T: Instance> Spi<'d, T> { Some(mosi.map_into()), Some(miso.map_into()), None, + None, + None, + config, + ) + } + + pub fn new_blocking_txonly( + inner: impl Peripheral

+ 'd, + clk: impl Peripheral

+ 'd> + 'd, + mosi: impl Peripheral

+ 'd> + 'd, + config: Config, + ) -> Self { + into_ref!(clk, mosi); + Self::new_inner( + inner, + Some(clk.map_into()), + Some(mosi.map_into()), + None, + None, + None, + None, + config, + ) + } + + pub fn new_blocking_rxonly( + inner: impl Peripheral

+ 'd, + clk: impl Peripheral

+ 'd> + 'd, + miso: impl Peripheral

+ 'd> + 'd, + config: Config, + ) -> Self { + into_ref!(clk, miso); + Self::new_inner( + inner, + Some(clk.map_into()), + None, + Some(miso.map_into()), + None, + None, + None, + config, + ) + } +} + +impl<'d, T: Instance> Spi<'d, T, Async> { + pub fn new( + inner: impl Peripheral

+ 'd, + clk: impl Peripheral

+ 'd> + 'd, + mosi: impl Peripheral

+ 'd> + 'd, + miso: impl Peripheral

+ 'd> + 'd, + tx_dma: impl Peripheral

+ 'd, + rx_dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(tx_dma, rx_dma, clk, mosi, miso); + Self::new_inner( + inner, + Some(clk.map_into()), + Some(mosi.map_into()), + Some(miso.map_into()), + None, + Some(tx_dma.map_into()), + Some(rx_dma.map_into()), config, ) } @@ -80,164 +282,167 @@ impl<'d, T: Instance> Spi<'d, T> { inner: impl Peripheral

+ 'd, clk: impl Peripheral

+ 'd> + 'd, mosi: impl Peripheral

+ 'd> + 'd, + tx_dma: impl Peripheral

+ 'd, config: Config, ) -> Self { - into_ref!(clk, mosi); - Self::new_inner(inner, Some(clk.map_into()), Some(mosi.map_into()), None, None, config) + into_ref!(tx_dma, clk, mosi); + Self::new_inner( + inner, + Some(clk.map_into()), + Some(mosi.map_into()), + None, + None, + Some(tx_dma.map_into()), + None, + config, + ) } pub fn new_rxonly( inner: impl Peripheral

+ 'd, clk: impl Peripheral

+ 'd> + 'd, miso: impl Peripheral

+ 'd> + 'd, + rx_dma: impl Peripheral

+ 'd, config: Config, ) -> Self { - into_ref!(clk, miso); - Self::new_inner(inner, Some(clk.map_into()), None, Some(miso.map_into()), None, config) + into_ref!(rx_dma, clk, miso); + Self::new_inner( + inner, + Some(clk.map_into()), + None, + Some(miso.map_into()), + None, + None, + Some(rx_dma.map_into()), + config, + ) } - fn new_inner( - inner: impl Peripheral

+ 'd, - clk: Option>, - mosi: Option>, - miso: Option>, - cs: Option>, - config: Config, - ) -> Self { - into_ref!(inner); + pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { + let tx_ch = self.tx_dma.as_mut().unwrap(); + let tx_transfer = unsafe { + // If we don't assign future to a variable, the data register pointer + // is held across an await and makes the future non-Send. + crate::dma::write(tx_ch, buffer, self.inner.regs().dr().as_ptr() as *mut _, T::TX_DREQ) + }; + tx_transfer.await; - unsafe { - let p = inner.regs(); - let (presc, postdiv) = calc_prescs(config.frequency); + let p = self.inner.regs(); + while p.sr().read().bsy() {} - p.cpsr().write(|w| w.set_cpsdvsr(presc)); - p.cr0().write(|w| { - w.set_dss(0b0111); // 8bit - w.set_spo(config.polarity == Polarity::IdleHigh); - w.set_sph(config.phase == Phase::CaptureOnSecondTransition); - w.set_scr(postdiv); - }); - p.cr1().write(|w| { - w.set_sse(true); // enable - }); - - if let Some(pin) = &clk { - pin.io().ctrl().write(|w| w.set_funcsel(1)); - } - if let Some(pin) = &mosi { - pin.io().ctrl().write(|w| w.set_funcsel(1)); - } - if let Some(pin) = &miso { - pin.io().ctrl().write(|w| w.set_funcsel(1)); - } - if let Some(pin) = &cs { - pin.io().ctrl().write(|w| w.set_funcsel(1)); - } + // clear RX FIFO contents to prevent stale reads + while p.sr().read().rne() { + let _: u16 = p.dr().read().data(); } - Self { inner } - } + // clear RX overrun interrupt + p.icr().write(|w| w.set_roric(true)); - pub fn blocking_write(&mut self, data: &[u8]) -> Result<(), Error> { - unsafe { - let p = self.inner.regs(); - for &b in data { - while !p.sr().read().tnf() {} - p.dr().write(|w| w.set_data(b as _)); - while !p.sr().read().rne() {} - let _ = p.dr().read(); - } - } - self.flush()?; Ok(()) } - pub fn blocking_transfer_in_place(&mut self, data: &mut [u8]) -> Result<(), Error> { - unsafe { - let p = self.inner.regs(); - for b in data { - while !p.sr().read().tnf() {} - p.dr().write(|w| w.set_data(*b as _)); - while !p.sr().read().rne() {} - *b = p.dr().read().data() as u8; - } - } - self.flush()?; + pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + // Start RX first. Transfer starts when TX starts, if RX + // is not started yet we might lose bytes. + let rx_ch = self.rx_dma.as_mut().unwrap(); + let rx_transfer = unsafe { + // If we don't assign future to a variable, the data register pointer + // is held across an await and makes the future non-Send. + crate::dma::read(rx_ch, self.inner.regs().dr().as_ptr() as *const _, buffer, T::RX_DREQ) + }; + + let tx_ch = self.tx_dma.as_mut().unwrap(); + let tx_transfer = unsafe { + // If we don't assign future to a variable, the data register pointer + // is held across an await and makes the future non-Send. + crate::dma::write_repeated( + tx_ch, + self.inner.regs().dr().as_ptr() as *mut u8, + buffer.len(), + T::TX_DREQ, + ) + }; + join(tx_transfer, rx_transfer).await; Ok(()) } - pub fn blocking_read(&mut self, data: &mut [u8]) -> Result<(), Error> { - unsafe { - let p = self.inner.regs(); - for b in data { - while !p.sr().read().tnf() {} - p.dr().write(|w| w.set_data(0)); - while !p.sr().read().rne() {} - *b = p.dr().read().data() as u8; - } - } - self.flush()?; - Ok(()) + pub async fn transfer(&mut self, rx_buffer: &mut [u8], tx_buffer: &[u8]) -> Result<(), Error> { + self.transfer_inner(rx_buffer, tx_buffer).await } - pub fn blocking_transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Error> { - unsafe { + pub async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Error> { + self.transfer_inner(words, words).await + } + + async fn transfer_inner(&mut self, rx_ptr: *mut [u8], tx_ptr: *const [u8]) -> Result<(), Error> { + let (_, tx_len) = crate::dma::slice_ptr_parts(tx_ptr); + let (_, rx_len) = crate::dma::slice_ptr_parts_mut(rx_ptr); + + // Start RX first. Transfer starts when TX starts, if RX + // is not started yet we might lose bytes. + let rx_ch = self.rx_dma.as_mut().unwrap(); + let rx_transfer = unsafe { + // If we don't assign future to a variable, the data register pointer + // is held across an await and makes the future non-Send. + crate::dma::read(rx_ch, self.inner.regs().dr().as_ptr() as *const _, rx_ptr, T::RX_DREQ) + }; + + let mut tx_ch = self.tx_dma.as_mut().unwrap(); + // If we don't assign future to a variable, the data register pointer + // is held across an await and makes the future non-Send. + let tx_transfer = async { let p = self.inner.regs(); - let len = read.len().max(write.len()); - for i in 0..len { - let wb = write.get(i).copied().unwrap_or(0); - while !p.sr().read().tnf() {} - p.dr().write(|w| w.set_data(wb as _)); - while !p.sr().read().rne() {} - let rb = p.dr().read().data() as u8; - if let Some(r) = read.get_mut(i) { - *r = rb; + unsafe { + crate::dma::write(&mut tx_ch, tx_ptr, p.dr().as_ptr() as *mut _, T::TX_DREQ).await; + + if rx_len > tx_len { + let write_bytes_len = rx_len - tx_len; + // write dummy data + // this will disable incrementation of the buffers + crate::dma::write_repeated(tx_ch, p.dr().as_ptr() as *mut u8, write_bytes_len, T::TX_DREQ).await } } - } - self.flush()?; - Ok(()) - } + }; + join(tx_transfer, rx_transfer).await; - pub fn flush(&mut self) -> Result<(), Error> { - unsafe { + // if tx > rx we should clear any overflow of the FIFO SPI buffer + if tx_len > rx_len { let p = self.inner.regs(); while p.sr().read().bsy() {} + + // clear RX FIFO contents to prevent stale reads + while p.sr().read().rne() { + let _: u16 = p.dr().read().data(); + } + // clear RX overrun interrupt + p.icr().write(|w| w.set_roric(true)); } + Ok(()) } - - pub fn set_frequency(&mut self, freq: u32) { - let (presc, postdiv) = calc_prescs(freq); - let p = self.inner.regs(); - unsafe { - // disable - p.cr1().write(|w| w.set_sse(false)); - - // change stuff - p.cpsr().write(|w| w.set_cpsdvsr(presc)); - p.cr0().modify(|w| { - w.set_scr(postdiv); - }); - - // enable - p.cr1().write(|w| w.set_sse(true)); - } - } } mod sealed { use super::*; + pub trait Mode {} + pub trait Instance { + const TX_DREQ: u8; + const RX_DREQ: u8; + fn regs(&self) -> pac::spi::Spi; } } +pub trait Mode: sealed::Mode {} pub trait Instance: sealed::Instance {} macro_rules! impl_instance { - ($type:ident, $irq:ident) => { + ($type:ident, $irq:ident, $tx_dreq:expr, $rx_dreq:expr) => { impl sealed::Instance for peripherals::$type { + const TX_DREQ: u8 = $tx_dreq; + const RX_DREQ: u8 = $rx_dreq; + fn regs(&self) -> pac::spi::Spi { pac::$type } @@ -246,8 +451,8 @@ macro_rules! impl_instance { }; } -impl_instance!(SPI0, Spi0); -impl_instance!(SPI1, Spi1); +impl_instance!(SPI0, Spi0, 16, 17); +impl_instance!(SPI1, Spi1, 18, 19); pub trait ClkPin: GpioPin {} pub trait CsPin: GpioPin {} @@ -280,13 +485,36 @@ impl_pin!(PIN_16, SPI0, MisoPin); impl_pin!(PIN_17, SPI0, CsPin); impl_pin!(PIN_18, SPI0, ClkPin); impl_pin!(PIN_19, SPI0, MosiPin); +impl_pin!(PIN_20, SPI0, MisoPin); +impl_pin!(PIN_21, SPI0, CsPin); +impl_pin!(PIN_22, SPI0, ClkPin); +impl_pin!(PIN_23, SPI0, MosiPin); +impl_pin!(PIN_24, SPI1, MisoPin); +impl_pin!(PIN_25, SPI1, CsPin); +impl_pin!(PIN_26, SPI1, ClkPin); +impl_pin!(PIN_27, SPI1, MosiPin); +impl_pin!(PIN_28, SPI1, MisoPin); +impl_pin!(PIN_29, SPI1, CsPin); + +macro_rules! impl_mode { + ($name:ident) => { + impl sealed::Mode for $name {} + impl Mode for $name {} + }; +} + +pub struct Blocking; +pub struct Async; + +impl_mode!(Blocking); +impl_mode!(Async); // ==================== mod eh02 { use super::*; - impl<'d, T: Instance> embedded_hal_02::blocking::spi::Transfer for Spi<'d, T> { + impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::spi::Transfer for Spi<'d, T, M> { type Error = Error; fn transfer<'w>(&mut self, words: &'w mut [u8]) -> Result<&'w [u8], Self::Error> { self.blocking_transfer_in_place(words)?; @@ -294,7 +522,7 @@ mod eh02 { } } - impl<'d, T: Instance> embedded_hal_02::blocking::spi::Write for Spi<'d, T> { + impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::spi::Write for Spi<'d, T, M> { type Error = Error; fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { @@ -313,29 +541,23 @@ mod eh1 { } } - impl<'d, T: Instance> embedded_hal_1::spi::ErrorType for Spi<'d, T> { + impl<'d, T: Instance, M: Mode> embedded_hal_1::spi::ErrorType for Spi<'d, T, M> { type Error = Error; } - impl<'d, T: Instance> embedded_hal_1::spi::blocking::SpiBusFlush for Spi<'d, T> { + impl<'d, T: Instance, M: Mode> embedded_hal_1::spi::SpiBus for Spi<'d, T, M> { fn flush(&mut self) -> Result<(), Self::Error> { Ok(()) } - } - impl<'d, T: Instance> embedded_hal_1::spi::blocking::SpiBusRead for Spi<'d, T> { fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { self.blocking_transfer(words, &[]) } - } - impl<'d, T: Instance> embedded_hal_1::spi::blocking::SpiBusWrite for Spi<'d, T> { fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { self.blocking_write(words) } - } - impl<'d, T: Instance> embedded_hal_1::spi::blocking::SpiBus for Spi<'d, T> { fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { self.blocking_transfer(read, write) } @@ -346,19 +568,44 @@ mod eh1 { } } -impl<'d, T: Instance> SetConfig for Spi<'d, T> { +#[cfg(all(feature = "unstable-traits", feature = "nightly"))] +mod eha { + use super::*; + + impl<'d, T: Instance> embedded_hal_async::spi::SpiBus for Spi<'d, T, Async> { + async fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) + } + + async fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { + self.write(words).await + } + + async fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.read(words).await + } + + async fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { + self.transfer(read, write).await + } + + async fn transfer_in_place(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { + self.transfer_in_place(words).await + } + } +} + +impl<'d, T: Instance, M: Mode> SetConfig for Spi<'d, T, M> { type Config = Config; fn set_config(&mut self, config: &Self::Config) { let p = self.inner.regs(); let (presc, postdiv) = calc_prescs(config.frequency); - unsafe { - p.cpsr().write(|w| w.set_cpsdvsr(presc)); - p.cr0().write(|w| { - w.set_dss(0b0111); // 8bit - w.set_spo(config.polarity == Polarity::IdleHigh); - w.set_sph(config.phase == Phase::CaptureOnSecondTransition); - w.set_scr(postdiv); - }); - } + p.cpsr().write(|w| w.set_cpsdvsr(presc)); + p.cr0().write(|w| { + w.set_dss(0b0111); // 8bit + w.set_spo(config.polarity == Polarity::IdleHigh); + w.set_sph(config.phase == Phase::CaptureOnSecondTransition); + w.set_scr(postdiv); + }); } } diff --git a/embassy-rp/src/timer.rs b/embassy-rp/src/timer.rs index 5215c0c0f..faa8df037 100644 --- a/embassy-rp/src/timer.rs +++ b/embassy-rp/src/timer.rs @@ -6,7 +6,7 @@ use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::blocking_mutex::Mutex; use embassy_time::driver::{AlarmHandle, Driver}; -use crate::interrupt::{Interrupt, InterruptExt}; +use crate::interrupt::InterruptExt; use crate::{interrupt, pac}; struct AlarmState { @@ -34,13 +34,11 @@ embassy_time::time_driver_impl!(static DRIVER: TimerDriver = TimerDriver{ impl Driver for TimerDriver { fn now(&self) -> u64 { loop { - unsafe { - let hi = pac::TIMER.timerawh().read(); - let lo = pac::TIMER.timerawl().read(); - let hi2 = pac::TIMER.timerawh().read(); - if hi == hi2 { - return (hi as u64) << 32 | (lo as u64); - } + let hi = pac::TIMER.timerawh().read(); + let lo = pac::TIMER.timerawl().read(); + let hi2 = pac::TIMER.timerawh().read(); + if hi == hi2 { + return (hi as u64) << 32 | (lo as u64); } } } @@ -68,7 +66,7 @@ impl Driver for TimerDriver { }) } - fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) { + fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool { let n = alarm.id() as usize; critical_section::with(|cs| { let alarm = &self.alarms.borrow(cs)[n]; @@ -78,14 +76,19 @@ impl Driver for TimerDriver { // Note that we're not checking the high bits at all. This means the irq may fire early // if the alarm is more than 72 minutes (2^32 us) in the future. This is OK, since on irq fire // it is checked if the alarm time has passed. - unsafe { pac::TIMER.alarm(n).write_value(timestamp as u32) }; + pac::TIMER.alarm(n).write_value(timestamp as u32); let now = self.now(); - - // If alarm timestamp has passed, trigger it instantly. - // This disarms it. if timestamp <= now { - self.trigger_alarm(n, cs); + // If alarm timestamp has passed the alarm will not fire. + // Disarm the alarm and return `false` to indicate that. + pac::TIMER.armed().write(|w| w.set_armed(1 << n)); + + alarm.timestamp.set(u64::MAX); + + false + } else { + true } }) } @@ -101,17 +104,17 @@ impl TimerDriver { } else { // Not elapsed, arm it again. // This can happen if it was set more than 2^32 us in the future. - unsafe { pac::TIMER.alarm(n).write_value(timestamp as u32) }; + pac::TIMER.alarm(n).write_value(timestamp as u32); } }); // clear the irq - unsafe { pac::TIMER.intr().write(|w| w.set_alarm(n, true)) } + pac::TIMER.intr().write(|w| w.set_alarm(n, true)); } fn trigger_alarm(&self, n: usize, cs: CriticalSection) { // disarm - unsafe { pac::TIMER.armed().write(|w| w.set_armed(1 << n)) } + pac::TIMER.armed().write(|w| w.set_armed(1 << n)); let alarm = &self.alarms.borrow(cs)[n]; alarm.timestamp.set(u64::MAX); @@ -140,28 +143,32 @@ pub unsafe fn init() { w.set_alarm(2, true); w.set_alarm(3, true); }); - interrupt::TIMER_IRQ_0::steal().enable(); - interrupt::TIMER_IRQ_1::steal().enable(); - interrupt::TIMER_IRQ_2::steal().enable(); - interrupt::TIMER_IRQ_3::steal().enable(); + interrupt::TIMER_IRQ_0.enable(); + interrupt::TIMER_IRQ_1.enable(); + interrupt::TIMER_IRQ_2.enable(); + interrupt::TIMER_IRQ_3.enable(); } +#[cfg(feature = "rt")] #[interrupt] -unsafe fn TIMER_IRQ_0() { +fn TIMER_IRQ_0() { DRIVER.check_alarm(0) } +#[cfg(feature = "rt")] #[interrupt] -unsafe fn TIMER_IRQ_1() { +fn TIMER_IRQ_1() { DRIVER.check_alarm(1) } +#[cfg(feature = "rt")] #[interrupt] -unsafe fn TIMER_IRQ_2() { +fn TIMER_IRQ_2() { DRIVER.check_alarm(2) } +#[cfg(feature = "rt")] #[interrupt] -unsafe fn TIMER_IRQ_3() { +fn TIMER_IRQ_3() { DRIVER.check_alarm(3) } diff --git a/embassy-rp/src/uart.rs b/embassy-rp/src/uart.rs deleted file mode 100644 index 6c5ab3515..000000000 --- a/embassy-rp/src/uart.rs +++ /dev/null @@ -1,488 +0,0 @@ -use core::marker::PhantomData; - -use embassy_hal_common::{into_ref, PeripheralRef}; - -use crate::gpio::sealed::Pin; -use crate::gpio::AnyPin; -use crate::{pac, peripherals, Peripheral}; - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum DataBits { - DataBits5, - DataBits6, - DataBits7, - DataBits8, -} - -impl DataBits { - fn bits(&self) -> u8 { - match self { - Self::DataBits5 => 0b00, - Self::DataBits6 => 0b01, - Self::DataBits7 => 0b10, - Self::DataBits8 => 0b11, - } - } -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum Parity { - ParityNone, - ParityEven, - ParityOdd, -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum StopBits { - #[doc = "1 stop bit"] - STOP1, - #[doc = "2 stop bits"] - STOP2, -} - -#[non_exhaustive] -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub struct Config { - pub baudrate: u32, - pub data_bits: DataBits, - pub stop_bits: StopBits, - pub parity: Parity, -} - -impl Default for Config { - fn default() -> Self { - Self { - baudrate: 115200, - data_bits: DataBits::DataBits8, - stop_bits: StopBits::STOP1, - parity: Parity::ParityNone, - } - } -} - -/// Serial error -#[derive(Debug, Eq, PartialEq, Copy, Clone)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[non_exhaustive] -pub enum Error { - /// Triggered when the FIFO (or shift-register) is overflowed. - Overrun, - /// Triggered when a break is received - Break, - /// Triggered when there is a parity mismatch between what's received and - /// our settings. - Parity, - /// Triggered when the received character didn't have a valid stop bit. - Framing, -} - -pub struct Uart<'d, T: Instance> { - tx: UartTx<'d, T>, - rx: UartRx<'d, T>, -} - -pub struct UartTx<'d, T: Instance> { - phantom: PhantomData<&'d mut T>, -} - -pub struct UartRx<'d, T: Instance> { - phantom: PhantomData<&'d mut T>, -} - -impl<'d, T: Instance> UartTx<'d, T> { - fn new() -> Self { - Self { phantom: PhantomData } - } - - pub async fn write(&mut self, _buffer: &[u8]) -> Result<(), Error> { - todo!() - } - - pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { - let r = T::regs(); - unsafe { - for &b in buffer { - while r.uartfr().read().txff() {} - r.uartdr().write(|w| w.set_data(b)); - } - } - Ok(()) - } - - pub fn blocking_flush(&mut self) -> Result<(), Error> { - let r = T::regs(); - unsafe { while r.uartfr().read().txff() {} } - Ok(()) - } -} - -impl<'d, T: Instance> UartRx<'d, T> { - fn new() -> Self { - Self { phantom: PhantomData } - } - - pub async fn read(&mut self, _buffer: &mut [u8]) -> Result<(), Error> { - todo!(); - } - - pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { - let r = T::regs(); - unsafe { - for b in buffer { - *b = loop { - let dr = r.uartdr().read(); - - if dr.oe() { - return Err(Error::Overrun); - } else if dr.be() { - return Err(Error::Break); - } else if dr.pe() { - return Err(Error::Parity); - } else if dr.fe() { - return Err(Error::Framing); - } else if dr.fe() { - break dr.data(); - } - }; - } - } - Ok(()) - } -} - -impl<'d, T: Instance> Uart<'d, T> { - /// Create a new UART without hardware flow control - pub fn new( - uart: impl Peripheral

+ 'd, - tx: impl Peripheral

> + 'd, - rx: impl Peripheral

> + 'd, - config: Config, - ) -> Self { - into_ref!(tx, rx); - Self::new_inner(uart, rx.map_into(), tx.map_into(), None, None, config) - } - - /// Create a new UART with hardware flow control (RTS/CTS) - pub fn new_with_rtscts( - uart: impl Peripheral

+ 'd, - tx: impl Peripheral

> + 'd, - rx: impl Peripheral

> + 'd, - cts: impl Peripheral

> + 'd, - rts: impl Peripheral

> + 'd, - config: Config, - ) -> Self { - into_ref!(tx, rx, cts, rts); - Self::new_inner( - uart, - rx.map_into(), - tx.map_into(), - Some(cts.map_into()), - Some(rts.map_into()), - config, - ) - } - - fn new_inner( - _uart: impl Peripheral

+ 'd, - tx: PeripheralRef<'d, AnyPin>, - rx: PeripheralRef<'d, AnyPin>, - cts: Option>, - rts: Option>, - config: Config, - ) -> Self { - into_ref!(_uart); - - unsafe { - let r = T::regs(); - - let clk_base = crate::clocks::clk_peri_freq(); - - let baud_rate_div = (8 * clk_base) / config.baudrate; - let mut baud_ibrd = baud_rate_div >> 7; - let mut baud_fbrd = ((baud_rate_div & 0x7f) + 1) / 2; - - if baud_ibrd == 0 { - baud_ibrd = 1; - baud_fbrd = 0; - } else if baud_ibrd >= 65535 { - baud_ibrd = 65535; - baud_fbrd = 0; - } - - // Load PL011's baud divisor registers - r.uartibrd().write_value(pac::uart::regs::Uartibrd(baud_ibrd)); - r.uartfbrd().write_value(pac::uart::regs::Uartfbrd(baud_fbrd)); - - let (pen, eps) = match config.parity { - Parity::ParityNone => (false, false), - Parity::ParityEven => (true, true), - Parity::ParityOdd => (true, false), - }; - - r.uartlcr_h().write(|w| { - w.set_wlen(config.data_bits.bits()); - w.set_stp2(config.stop_bits == StopBits::STOP2); - w.set_pen(pen); - w.set_eps(eps); - w.set_fen(true); - }); - - r.uartcr().write(|w| { - w.set_uarten(true); - w.set_rxe(true); - w.set_txe(true); - w.set_ctsen(cts.is_some()); - w.set_rtsen(rts.is_some()); - }); - - tx.io().ctrl().write(|w| w.set_funcsel(2)); - rx.io().ctrl().write(|w| w.set_funcsel(2)); - if let Some(pin) = &cts { - pin.io().ctrl().write(|w| w.set_funcsel(2)); - } - if let Some(pin) = &rts { - pin.io().ctrl().write(|w| w.set_funcsel(2)); - } - } - - Self { - tx: UartTx::new(), - rx: UartRx::new(), - } - } - - pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { - self.tx.write(buffer).await - } - - pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { - self.tx.blocking_write(buffer) - } - - pub fn blocking_flush(&mut self) -> Result<(), Error> { - self.tx.blocking_flush() - } - - pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { - self.rx.read(buffer).await - } - - pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { - self.rx.blocking_read(buffer) - } - - /// Split the Uart into a transmitter and receiver, which is - /// particuarly useful when having two tasks correlating to - /// transmitting and receiving. - pub fn split(self) -> (UartTx<'d, T>, UartRx<'d, T>) { - (self.tx, self.rx) - } -} - -mod eh02 { - use super::*; - - impl<'d, T: Instance> embedded_hal_02::serial::Read for UartRx<'d, T> { - type Error = Error; - fn read(&mut self) -> Result> { - let r = T::regs(); - unsafe { - let dr = r.uartdr().read(); - - if dr.oe() { - Err(nb::Error::Other(Error::Overrun)) - } else if dr.be() { - Err(nb::Error::Other(Error::Break)) - } else if dr.pe() { - Err(nb::Error::Other(Error::Parity)) - } else if dr.fe() { - Err(nb::Error::Other(Error::Framing)) - } else if dr.fe() { - Ok(dr.data()) - } else { - Err(nb::Error::WouldBlock) - } - } - } - } - - impl<'d, T: Instance> embedded_hal_02::blocking::serial::Write for UartTx<'d, T> { - type Error = Error; - fn bwrite_all(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { - self.blocking_write(buffer) - } - fn bflush(&mut self) -> Result<(), Self::Error> { - self.blocking_flush() - } - } - - impl<'d, T: Instance> embedded_hal_02::serial::Read for Uart<'d, T> { - type Error = Error; - fn read(&mut self) -> Result> { - embedded_hal_02::serial::Read::read(&mut self.rx) - } - } - - impl<'d, T: Instance> embedded_hal_02::blocking::serial::Write for Uart<'d, T> { - type Error = Error; - fn bwrite_all(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { - self.blocking_write(buffer) - } - fn bflush(&mut self) -> Result<(), Self::Error> { - self.blocking_flush() - } - } -} - -#[cfg(feature = "unstable-traits")] -mod eh1 { - use super::*; - - impl embedded_hal_1::serial::Error for Error { - fn kind(&self) -> embedded_hal_1::serial::ErrorKind { - match *self { - Self::Framing => embedded_hal_1::serial::ErrorKind::FrameFormat, - Self::Break => embedded_hal_1::serial::ErrorKind::Other, - Self::Overrun => embedded_hal_1::serial::ErrorKind::Overrun, - Self::Parity => embedded_hal_1::serial::ErrorKind::Parity, - } - } - } - - impl<'d, T: Instance> embedded_hal_1::serial::ErrorType for Uart<'d, T> { - type Error = Error; - } - - impl<'d, T: Instance> embedded_hal_1::serial::ErrorType for UartTx<'d, T> { - type Error = Error; - } - - impl<'d, T: Instance> embedded_hal_1::serial::ErrorType for UartRx<'d, T> { - type Error = Error; - } -} - -cfg_if::cfg_if! { - if #[cfg(all(feature = "unstable-traits", feature = "nightly", feature = "_todo_embedded_hal_serial"))] { - use core::future::Future; - - impl<'d, T: Instance> embedded_hal_async::serial::Write for UartTx<'d, T> - { - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { - self.write(buf) - } - - type FlushFuture<'a> = impl Future> + 'a where Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - async move { Ok(()) } - } - } - - impl<'d, T: Instance> embedded_hal_async::serial::Read for UartRx<'d, T> - { - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - self.read(buf) - } - } - - impl<'d, T: Instance> embedded_hal_async::serial::Write for Uart<'d, T> - { - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { - self.write(buf) - } - - type FlushFuture<'a> = impl Future> + 'a where Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - async move { Ok(()) } - } - } - - impl<'d, T: Instance> embedded_hal_async::serial::Read for Uart<'d, T> - { - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - self.read(buf) - } - } - } -} - -mod sealed { - use super::*; - - pub trait Instance { - fn regs() -> pac::uart::Uart; - } - pub trait TxPin {} - pub trait RxPin {} - pub trait CtsPin {} - pub trait RtsPin {} -} - -pub trait Instance: sealed::Instance {} - -macro_rules! impl_instance { - ($inst:ident, $irq:ident) => { - impl sealed::Instance for peripherals::$inst { - fn regs() -> pac::uart::Uart { - pac::$inst - } - } - impl Instance for peripherals::$inst {} - }; -} - -impl_instance!(UART0, UART0); -impl_instance!(UART1, UART1); - -pub trait TxPin: sealed::TxPin + crate::gpio::Pin {} -pub trait RxPin: sealed::RxPin + crate::gpio::Pin {} -pub trait CtsPin: sealed::CtsPin + crate::gpio::Pin {} -pub trait RtsPin: sealed::RtsPin + crate::gpio::Pin {} - -macro_rules! impl_pin { - ($pin:ident, $instance:ident, $function:ident) => { - impl sealed::$function for peripherals::$pin {} - impl $function for peripherals::$pin {} - }; -} - -impl_pin!(PIN_0, UART0, TxPin); -impl_pin!(PIN_1, UART0, RxPin); -impl_pin!(PIN_2, UART0, CtsPin); -impl_pin!(PIN_3, UART0, RtsPin); -impl_pin!(PIN_4, UART1, TxPin); -impl_pin!(PIN_5, UART1, RxPin); -impl_pin!(PIN_6, UART1, CtsPin); -impl_pin!(PIN_7, UART1, RtsPin); -impl_pin!(PIN_8, UART1, TxPin); -impl_pin!(PIN_9, UART1, RxPin); -impl_pin!(PIN_10, UART1, CtsPin); -impl_pin!(PIN_11, UART1, RtsPin); -impl_pin!(PIN_12, UART0, TxPin); -impl_pin!(PIN_13, UART0, RxPin); -impl_pin!(PIN_14, UART0, CtsPin); -impl_pin!(PIN_15, UART0, RtsPin); -impl_pin!(PIN_16, UART0, TxPin); -impl_pin!(PIN_17, UART0, RxPin); -impl_pin!(PIN_18, UART0, CtsPin); -impl_pin!(PIN_19, UART0, RtsPin); -impl_pin!(PIN_20, UART1, TxPin); -impl_pin!(PIN_21, UART1, RxPin); -impl_pin!(PIN_22, UART1, CtsPin); -impl_pin!(PIN_23, UART1, RtsPin); -impl_pin!(PIN_24, UART1, TxPin); -impl_pin!(PIN_25, UART1, RxPin); -impl_pin!(PIN_26, UART1, CtsPin); -impl_pin!(PIN_27, UART1, RtsPin); -impl_pin!(PIN_28, UART0, TxPin); -impl_pin!(PIN_29, UART0, RxPin); diff --git a/embassy-rp/src/uart/buffered.rs b/embassy-rp/src/uart/buffered.rs new file mode 100644 index 000000000..30eeb5476 --- /dev/null +++ b/embassy-rp/src/uart/buffered.rs @@ -0,0 +1,815 @@ +use core::future::{poll_fn, Future}; +use core::slice; +use core::task::Poll; + +use atomic_polyfill::{AtomicU8, Ordering}; +use embassy_hal_common::atomic_ring_buffer::RingBuffer; +use embassy_sync::waitqueue::AtomicWaker; +use embassy_time::{Duration, Timer}; + +use super::*; +use crate::clocks::clk_peri_freq; +use crate::interrupt::typelevel::{Binding, Interrupt}; +use crate::{interrupt, RegExt}; + +pub struct State { + tx_waker: AtomicWaker, + tx_buf: RingBuffer, + rx_waker: AtomicWaker, + rx_buf: RingBuffer, + rx_error: AtomicU8, +} + +// these must match bits 8..11 in UARTDR +const RXE_OVERRUN: u8 = 8; +const RXE_BREAK: u8 = 4; +const RXE_PARITY: u8 = 2; +const RXE_FRAMING: u8 = 1; + +impl State { + pub const fn new() -> Self { + Self { + rx_buf: RingBuffer::new(), + tx_buf: RingBuffer::new(), + rx_waker: AtomicWaker::new(), + tx_waker: AtomicWaker::new(), + rx_error: AtomicU8::new(0), + } + } +} + +pub struct BufferedUart<'d, T: Instance> { + pub(crate) rx: BufferedUartRx<'d, T>, + pub(crate) tx: BufferedUartTx<'d, T>, +} + +pub struct BufferedUartRx<'d, T: Instance> { + pub(crate) phantom: PhantomData<&'d mut T>, +} + +pub struct BufferedUartTx<'d, T: Instance> { + pub(crate) phantom: PhantomData<&'d mut T>, +} + +pub(crate) fn init_buffers<'d, T: Instance + 'd>( + _irq: impl Binding>, + tx_buffer: &'d mut [u8], + rx_buffer: &'d mut [u8], +) { + let state = T::buffered_state(); + let len = tx_buffer.len(); + unsafe { state.tx_buf.init(tx_buffer.as_mut_ptr(), len) }; + let len = rx_buffer.len(); + unsafe { state.rx_buf.init(rx_buffer.as_mut_ptr(), len) }; + + // From the datasheet: + // "The transmit interrupt is based on a transition through a level, rather + // than on the level itself. When the interrupt and the UART is enabled + // before any data is written to the transmit FIFO the interrupt is not set. + // The interrupt is only set, after written data leaves the single location + // of the transmit FIFO and it becomes empty." + // + // This means we can leave the interrupt enabled the whole time as long as + // we clear it after it happens. The downside is that the we manually have + // to pend the ISR when we want data transmission to start. + let regs = T::regs(); + regs.uartimsc().write(|w| { + w.set_rxim(true); + w.set_rtim(true); + w.set_txim(true); + }); + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; +} + +impl<'d, T: Instance> BufferedUart<'d, T> { + pub fn new( + _uart: impl Peripheral

+ 'd, + irq: impl Binding>, + tx: impl Peripheral

> + 'd, + rx: impl Peripheral

> + 'd, + tx_buffer: &'d mut [u8], + rx_buffer: &'d mut [u8], + config: Config, + ) -> Self { + into_ref!(tx, rx); + + super::Uart::<'d, T, Async>::init(Some(tx.map_into()), Some(rx.map_into()), None, None, config); + init_buffers::(irq, tx_buffer, rx_buffer); + + Self { + rx: BufferedUartRx { phantom: PhantomData }, + tx: BufferedUartTx { phantom: PhantomData }, + } + } + + pub fn new_with_rtscts( + _uart: impl Peripheral

+ 'd, + irq: impl Binding>, + tx: impl Peripheral

> + 'd, + rx: impl Peripheral

> + 'd, + rts: impl Peripheral

> + 'd, + cts: impl Peripheral

> + 'd, + tx_buffer: &'d mut [u8], + rx_buffer: &'d mut [u8], + config: Config, + ) -> Self { + into_ref!(tx, rx, cts, rts); + + super::Uart::<'d, T, Async>::init( + Some(tx.map_into()), + Some(rx.map_into()), + Some(rts.map_into()), + Some(cts.map_into()), + config, + ); + init_buffers::(irq, tx_buffer, rx_buffer); + + Self { + rx: BufferedUartRx { phantom: PhantomData }, + tx: BufferedUartTx { phantom: PhantomData }, + } + } + + pub fn blocking_write(&mut self, buffer: &[u8]) -> Result { + self.tx.blocking_write(buffer) + } + + pub fn blocking_flush(&mut self) -> Result<(), Error> { + self.tx.blocking_flush() + } + + pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result { + self.rx.blocking_read(buffer) + } + + pub fn busy(&self) -> bool { + self.tx.busy() + } + + pub async fn send_break(&mut self, bits: u32) { + self.tx.send_break(bits).await + } + + pub fn split(self) -> (BufferedUartRx<'d, T>, BufferedUartTx<'d, T>) { + (self.rx, self.tx) + } +} + +impl<'d, T: Instance> BufferedUartRx<'d, T> { + pub fn new( + _uart: impl Peripheral

+ 'd, + irq: impl Binding>, + rx: impl Peripheral

> + 'd, + rx_buffer: &'d mut [u8], + config: Config, + ) -> Self { + into_ref!(rx); + + super::Uart::<'d, T, Async>::init(None, Some(rx.map_into()), None, None, config); + init_buffers::(irq, &mut [], rx_buffer); + + Self { phantom: PhantomData } + } + + pub fn new_with_rts( + _uart: impl Peripheral

+ 'd, + irq: impl Binding>, + rx: impl Peripheral

> + 'd, + rts: impl Peripheral

> + 'd, + rx_buffer: &'d mut [u8], + config: Config, + ) -> Self { + into_ref!(rx, rts); + + super::Uart::<'d, T, Async>::init(None, Some(rx.map_into()), Some(rts.map_into()), None, config); + init_buffers::(irq, &mut [], rx_buffer); + + Self { phantom: PhantomData } + } + + fn read<'a>(buf: &'a mut [u8]) -> impl Future> + 'a + where + T: 'd, + { + poll_fn(move |cx| { + if let Poll::Ready(r) = Self::try_read(buf) { + return Poll::Ready(r); + } + T::buffered_state().rx_waker.register(cx.waker()); + Poll::Pending + }) + } + + fn get_rx_error() -> Option { + let errs = T::buffered_state().rx_error.swap(0, Ordering::Relaxed); + if errs & RXE_OVERRUN != 0 { + Some(Error::Overrun) + } else if errs & RXE_BREAK != 0 { + Some(Error::Break) + } else if errs & RXE_PARITY != 0 { + Some(Error::Parity) + } else if errs & RXE_FRAMING != 0 { + Some(Error::Framing) + } else { + None + } + } + + fn try_read(buf: &mut [u8]) -> Poll> + where + T: 'd, + { + if buf.is_empty() { + return Poll::Ready(Ok(0)); + } + + let state = T::buffered_state(); + let mut rx_reader = unsafe { state.rx_buf.reader() }; + let n = rx_reader.pop(|data| { + let n = data.len().min(buf.len()); + buf[..n].copy_from_slice(&data[..n]); + n + }); + + let result = if n == 0 { + match Self::get_rx_error() { + None => return Poll::Pending, + Some(e) => Err(e), + } + } else { + Ok(n) + }; + + // (Re-)Enable the interrupt to receive more data in case it was + // disabled because the buffer was full or errors were detected. + let regs = T::regs(); + regs.uartimsc().write_set(|w| { + w.set_rxim(true); + w.set_rtim(true); + }); + + Poll::Ready(result) + } + + pub fn blocking_read(&mut self, buf: &mut [u8]) -> Result { + loop { + match Self::try_read(buf) { + Poll::Ready(res) => return res, + Poll::Pending => continue, + } + } + } + + fn fill_buf<'a>() -> impl Future> + where + T: 'd, + { + poll_fn(move |cx| { + let state = T::buffered_state(); + let mut rx_reader = unsafe { state.rx_buf.reader() }; + let (p, n) = rx_reader.pop_buf(); + let result = if n == 0 { + match Self::get_rx_error() { + None => { + state.rx_waker.register(cx.waker()); + return Poll::Pending; + } + Some(e) => Err(e), + } + } else { + let buf = unsafe { slice::from_raw_parts(p, n) }; + Ok(buf) + }; + + Poll::Ready(result) + }) + } + + fn consume(amt: usize) { + let state = T::buffered_state(); + let mut rx_reader = unsafe { state.rx_buf.reader() }; + rx_reader.pop_done(amt); + + // (Re-)Enable the interrupt to receive more data in case it was + // disabled because the buffer was full or errors were detected. + let regs = T::regs(); + regs.uartimsc().write_set(|w| { + w.set_rxim(true); + w.set_rtim(true); + }); + } +} + +impl<'d, T: Instance> BufferedUartTx<'d, T> { + pub fn new( + _uart: impl Peripheral

+ 'd, + irq: impl Binding>, + tx: impl Peripheral

> + 'd, + tx_buffer: &'d mut [u8], + config: Config, + ) -> Self { + into_ref!(tx); + + super::Uart::<'d, T, Async>::init(Some(tx.map_into()), None, None, None, config); + init_buffers::(irq, tx_buffer, &mut []); + + Self { phantom: PhantomData } + } + + pub fn new_with_cts( + _uart: impl Peripheral

+ 'd, + irq: impl Binding>, + tx: impl Peripheral

> + 'd, + cts: impl Peripheral

> + 'd, + tx_buffer: &'d mut [u8], + config: Config, + ) -> Self { + into_ref!(tx, cts); + + super::Uart::<'d, T, Async>::init(Some(tx.map_into()), None, None, Some(cts.map_into()), config); + init_buffers::(irq, tx_buffer, &mut []); + + Self { phantom: PhantomData } + } + + fn write<'a>(buf: &'a [u8]) -> impl Future> + 'a { + poll_fn(move |cx| { + if buf.is_empty() { + return Poll::Ready(Ok(0)); + } + + let state = T::buffered_state(); + let mut tx_writer = unsafe { state.tx_buf.writer() }; + let n = tx_writer.push(|data| { + let n = data.len().min(buf.len()); + data[..n].copy_from_slice(&buf[..n]); + n + }); + if n == 0 { + state.tx_waker.register(cx.waker()); + return Poll::Pending; + } + + // The TX interrupt only triggers when the there was data in the + // FIFO and the number of bytes drops below a threshold. When the + // FIFO was empty we have to manually pend the interrupt to shovel + // TX data from the buffer into the FIFO. + T::Interrupt::pend(); + Poll::Ready(Ok(n)) + }) + } + + fn flush() -> impl Future> { + poll_fn(move |cx| { + let state = T::buffered_state(); + if !state.tx_buf.is_empty() { + state.tx_waker.register(cx.waker()); + return Poll::Pending; + } + + Poll::Ready(Ok(())) + }) + } + + pub fn blocking_write(&mut self, buf: &[u8]) -> Result { + if buf.is_empty() { + return Ok(0); + } + + loop { + let state = T::buffered_state(); + let mut tx_writer = unsafe { state.tx_buf.writer() }; + let n = tx_writer.push(|data| { + let n = data.len().min(buf.len()); + data[..n].copy_from_slice(&buf[..n]); + n + }); + + if n != 0 { + // The TX interrupt only triggers when the there was data in the + // FIFO and the number of bytes drops below a threshold. When the + // FIFO was empty we have to manually pend the interrupt to shovel + // TX data from the buffer into the FIFO. + T::Interrupt::pend(); + return Ok(n); + } + } + } + + pub fn blocking_flush(&mut self) -> Result<(), Error> { + loop { + let state = T::buffered_state(); + if state.tx_buf.is_empty() { + return Ok(()); + } + } + } + + pub fn busy(&self) -> bool { + T::regs().uartfr().read().busy() + } + + /// Assert a break condition after waiting for the transmit buffers to empty, + /// for the specified number of bit times. This condition must be asserted + /// for at least two frame times to be effective, `bits` will adjusted + /// according to frame size, parity, and stop bit settings to ensure this. + /// + /// This method may block for a long amount of time since it has to wait + /// for the transmit fifo to empty, which may take a while on slow links. + pub async fn send_break(&mut self, bits: u32) { + let regs = T::regs(); + let bits = bits.max({ + let lcr = regs.uartlcr_h().read(); + let width = lcr.wlen() as u32 + 5; + let parity = lcr.pen() as u32; + let stops = 1 + lcr.stp2() as u32; + 2 * (1 + width + parity + stops) + }); + let divx64 = (((regs.uartibrd().read().baud_divint() as u32) << 6) + + regs.uartfbrd().read().baud_divfrac() as u32) as u64; + let div_clk = clk_peri_freq() as u64 * 64; + let wait_usecs = (1_000_000 * bits as u64 * divx64 * 16 + div_clk - 1) / div_clk; + + Self::flush().await.unwrap(); + while self.busy() {} + regs.uartlcr_h().write_set(|w| w.set_brk(true)); + Timer::after(Duration::from_micros(wait_usecs)).await; + regs.uartlcr_h().write_clear(|w| w.set_brk(true)); + } +} + +impl<'d, T: Instance> Drop for BufferedUartRx<'d, T> { + fn drop(&mut self) { + let state = T::buffered_state(); + unsafe { state.rx_buf.deinit() } + + // TX is inactive if the the buffer is not available. + // We can now unregister the interrupt handler + if state.tx_buf.len() == 0 { + T::Interrupt::disable(); + } + } +} + +impl<'d, T: Instance> Drop for BufferedUartTx<'d, T> { + fn drop(&mut self) { + let state = T::buffered_state(); + unsafe { state.tx_buf.deinit() } + + // RX is inactive if the the buffer is not available. + // We can now unregister the interrupt handler + if state.rx_buf.len() == 0 { + T::Interrupt::disable(); + } + } +} + +pub struct BufferedInterruptHandler { + _uart: PhantomData, +} + +impl interrupt::typelevel::Handler for BufferedInterruptHandler { + unsafe fn on_interrupt() { + let r = T::regs(); + if r.uartdmacr().read().rxdmae() { + return; + } + + let s = T::buffered_state(); + + // Clear TX and error interrupt flags + // RX interrupt flags are cleared by reading from the FIFO. + let ris = r.uartris().read(); + r.uarticr().write(|w| { + w.set_txic(ris.txris()); + w.set_feic(ris.feris()); + w.set_peic(ris.peris()); + w.set_beic(ris.beris()); + w.set_oeic(ris.oeris()); + }); + + trace!("on_interrupt ris={:#X}", ris.0); + + // Errors + if ris.feris() { + warn!("Framing error"); + } + if ris.peris() { + warn!("Parity error"); + } + if ris.beris() { + warn!("Break error"); + } + if ris.oeris() { + warn!("Overrun error"); + } + + // RX + let mut rx_writer = unsafe { s.rx_buf.writer() }; + let rx_buf = rx_writer.push_slice(); + let mut n_read = 0; + let mut error = false; + for rx_byte in rx_buf { + if r.uartfr().read().rxfe() { + break; + } + let dr = r.uartdr().read(); + if (dr.0 >> 8) != 0 { + s.rx_error.fetch_or((dr.0 >> 8) as u8, Ordering::Relaxed); + error = true; + // only fill the buffer with valid characters. the current character is fine + // if the error is an overrun, but if we add it to the buffer we'll report + // the overrun one character too late. drop it instead and pretend we were + // a bit slower at draining the rx fifo than we actually were. + // this is consistent with blocking uart error reporting. + break; + } + *rx_byte = dr.data(); + n_read += 1; + } + if n_read > 0 { + rx_writer.push_done(n_read); + s.rx_waker.wake(); + } else if error { + s.rx_waker.wake(); + } + // Disable any further RX interrupts when the buffer becomes full or + // errors have occurred. This lets us buffer additional errors in the + // fifo without needing more error storage locations, and most applications + // will want to do a full reset of their uart state anyway once an error + // has happened. + if s.rx_buf.is_full() || error { + r.uartimsc().write_clear(|w| { + w.set_rxim(true); + w.set_rtim(true); + }); + } + + // TX + let mut tx_reader = unsafe { s.tx_buf.reader() }; + let tx_buf = tx_reader.pop_slice(); + let mut n_written = 0; + for tx_byte in tx_buf.iter_mut() { + if r.uartfr().read().txff() { + break; + } + r.uartdr().write(|w| w.set_data(*tx_byte)); + n_written += 1; + } + if n_written > 0 { + tx_reader.pop_done(n_written); + s.tx_waker.wake(); + } + // The TX interrupt only triggers once when the FIFO threshold is + // crossed. No need to disable it when the buffer becomes empty + // as it does re-trigger anymore once we have cleared it. + } +} + +impl embedded_io::Error for Error { + fn kind(&self) -> embedded_io::ErrorKind { + embedded_io::ErrorKind::Other + } +} + +impl<'d, T: Instance> embedded_io::Io for BufferedUart<'d, T> { + type Error = Error; +} + +impl<'d, T: Instance> embedded_io::Io for BufferedUartRx<'d, T> { + type Error = Error; +} + +impl<'d, T: Instance> embedded_io::Io for BufferedUartTx<'d, T> { + type Error = Error; +} + +impl<'d, T: Instance + 'd> embedded_io::asynch::Read for BufferedUart<'d, T> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + BufferedUartRx::<'d, T>::read(buf).await + } +} + +impl<'d, T: Instance + 'd> embedded_io::asynch::Read for BufferedUartRx<'d, T> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + Self::read(buf).await + } +} + +impl<'d, T: Instance + 'd> embedded_io::asynch::BufRead for BufferedUart<'d, T> { + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + BufferedUartRx::<'d, T>::fill_buf().await + } + + fn consume(&mut self, amt: usize) { + BufferedUartRx::<'d, T>::consume(amt) + } +} + +impl<'d, T: Instance + 'd> embedded_io::asynch::BufRead for BufferedUartRx<'d, T> { + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + Self::fill_buf().await + } + + fn consume(&mut self, amt: usize) { + Self::consume(amt) + } +} + +impl<'d, T: Instance + 'd> embedded_io::asynch::Write for BufferedUart<'d, T> { + async fn write(&mut self, buf: &[u8]) -> Result { + BufferedUartTx::<'d, T>::write(buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + BufferedUartTx::<'d, T>::flush().await + } +} + +impl<'d, T: Instance + 'd> embedded_io::asynch::Write for BufferedUartTx<'d, T> { + async fn write(&mut self, buf: &[u8]) -> Result { + Self::write(buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + Self::flush().await + } +} + +impl<'d, T: Instance + 'd> embedded_io::blocking::Read for BufferedUart<'d, T> { + fn read(&mut self, buf: &mut [u8]) -> Result { + self.rx.blocking_read(buf) + } +} + +impl<'d, T: Instance + 'd> embedded_io::blocking::Read for BufferedUartRx<'d, T> { + fn read(&mut self, buf: &mut [u8]) -> Result { + self.blocking_read(buf) + } +} + +impl<'d, T: Instance + 'd> embedded_io::blocking::Write for BufferedUart<'d, T> { + fn write(&mut self, buf: &[u8]) -> Result { + self.tx.blocking_write(buf) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.tx.blocking_flush() + } +} + +impl<'d, T: Instance + 'd> embedded_io::blocking::Write for BufferedUartTx<'d, T> { + fn write(&mut self, buf: &[u8]) -> Result { + self.blocking_write(buf) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } +} + +mod eh02 { + use super::*; + + impl<'d, T: Instance> embedded_hal_02::serial::Read for BufferedUartRx<'d, T> { + type Error = Error; + + fn read(&mut self) -> Result> { + let r = T::regs(); + if r.uartfr().read().rxfe() { + return Err(nb::Error::WouldBlock); + } + + let dr = r.uartdr().read(); + + if dr.oe() { + Err(nb::Error::Other(Error::Overrun)) + } else if dr.be() { + Err(nb::Error::Other(Error::Break)) + } else if dr.pe() { + Err(nb::Error::Other(Error::Parity)) + } else if dr.fe() { + Err(nb::Error::Other(Error::Framing)) + } else { + Ok(dr.data()) + } + } + } + + impl<'d, T: Instance> embedded_hal_02::blocking::serial::Write for BufferedUartTx<'d, T> { + type Error = Error; + + fn bwrite_all(&mut self, mut buffer: &[u8]) -> Result<(), Self::Error> { + while !buffer.is_empty() { + match self.blocking_write(buffer) { + Ok(0) => panic!("zero-length write."), + Ok(n) => buffer = &buffer[n..], + Err(e) => return Err(e), + } + } + Ok(()) + } + + fn bflush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } + } + + impl<'d, T: Instance> embedded_hal_02::serial::Read for BufferedUart<'d, T> { + type Error = Error; + + fn read(&mut self) -> Result> { + embedded_hal_02::serial::Read::read(&mut self.rx) + } + } + + impl<'d, T: Instance> embedded_hal_02::blocking::serial::Write for BufferedUart<'d, T> { + type Error = Error; + + fn bwrite_all(&mut self, mut buffer: &[u8]) -> Result<(), Self::Error> { + while !buffer.is_empty() { + match self.blocking_write(buffer) { + Ok(0) => panic!("zero-length write."), + Ok(n) => buffer = &buffer[n..], + Err(e) => return Err(e), + } + } + Ok(()) + } + + fn bflush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } + } +} + +#[cfg(feature = "unstable-traits")] +mod eh1 { + use super::*; + + impl<'d, T: Instance> embedded_hal_1::serial::ErrorType for BufferedUart<'d, T> { + type Error = Error; + } + + impl<'d, T: Instance> embedded_hal_1::serial::ErrorType for BufferedUartTx<'d, T> { + type Error = Error; + } + + impl<'d, T: Instance> embedded_hal_1::serial::ErrorType for BufferedUartRx<'d, T> { + type Error = Error; + } + + impl<'d, T: Instance> embedded_hal_nb::serial::Read for BufferedUartRx<'d, T> { + fn read(&mut self) -> nb::Result { + embedded_hal_02::serial::Read::read(self) + } + } + + impl<'d, T: Instance> embedded_hal_1::serial::Write for BufferedUartTx<'d, T> { + fn write(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(buffer).map(drop) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } + } + + impl<'d, T: Instance> embedded_hal_nb::serial::Write for BufferedUartTx<'d, T> { + fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { + self.blocking_write(&[char]).map(drop).map_err(nb::Error::Other) + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + self.blocking_flush().map_err(nb::Error::Other) + } + } + + impl<'d, T: Instance> embedded_hal_nb::serial::Read for BufferedUart<'d, T> { + fn read(&mut self) -> Result> { + embedded_hal_02::serial::Read::read(&mut self.rx) + } + } + + impl<'d, T: Instance> embedded_hal_1::serial::Write for BufferedUart<'d, T> { + fn write(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(buffer).map(drop) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } + } + + impl<'d, T: Instance> embedded_hal_nb::serial::Write for BufferedUart<'d, T> { + fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { + self.blocking_write(&[char]).map(drop).map_err(nb::Error::Other) + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + self.blocking_flush().map_err(nb::Error::Other) + } + } +} diff --git a/embassy-rp/src/uart/mod.rs b/embassy-rp/src/uart/mod.rs new file mode 100644 index 000000000..7b94bce5e --- /dev/null +++ b/embassy-rp/src/uart/mod.rs @@ -0,0 +1,1016 @@ +use core::future::poll_fn; +use core::marker::PhantomData; +use core::task::Poll; + +use atomic_polyfill::{AtomicU16, Ordering}; +use embassy_futures::select::{select, Either}; +use embassy_hal_common::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; +use embassy_time::{Duration, Timer}; +use pac::uart::regs::Uartris; + +use crate::clocks::clk_peri_freq; +use crate::dma::{AnyChannel, Channel}; +use crate::gpio::sealed::Pin; +use crate::gpio::AnyPin; +use crate::interrupt::typelevel::{Binding, Interrupt}; +use crate::pac::io::vals::{Inover, Outover}; +use crate::{interrupt, pac, peripherals, Peripheral, RegExt}; + +#[cfg(feature = "nightly")] +mod buffered; +#[cfg(feature = "nightly")] +pub use buffered::{BufferedInterruptHandler, BufferedUart, BufferedUartRx, BufferedUartTx}; + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum DataBits { + DataBits5, + DataBits6, + DataBits7, + DataBits8, +} + +impl DataBits { + fn bits(&self) -> u8 { + match self { + Self::DataBits5 => 0b00, + Self::DataBits6 => 0b01, + Self::DataBits7 => 0b10, + Self::DataBits8 => 0b11, + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum Parity { + ParityNone, + ParityEven, + ParityOdd, +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum StopBits { + #[doc = "1 stop bit"] + STOP1, + #[doc = "2 stop bits"] + STOP2, +} + +#[non_exhaustive] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct Config { + pub baudrate: u32, + pub data_bits: DataBits, + pub stop_bits: StopBits, + pub parity: Parity, + /// Invert the tx pin output + pub invert_tx: bool, + /// Invert the rx pin input + pub invert_rx: bool, + // Invert the rts pin + pub invert_rts: bool, + // Invert the cts pin + pub invert_cts: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + baudrate: 115200, + data_bits: DataBits::DataBits8, + stop_bits: StopBits::STOP1, + parity: Parity::ParityNone, + invert_rx: false, + invert_tx: false, + invert_rts: false, + invert_cts: false, + } + } +} + +/// Serial error +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + /// Triggered when the FIFO (or shift-register) is overflowed. + Overrun, + /// Triggered when a break is received + Break, + /// Triggered when there is a parity mismatch between what's received and + /// our settings. + Parity, + /// Triggered when the received character didn't have a valid stop bit. + Framing, +} + +pub struct DmaState { + rx_err_waker: AtomicWaker, + rx_errs: AtomicU16, +} + +pub struct Uart<'d, T: Instance, M: Mode> { + tx: UartTx<'d, T, M>, + rx: UartRx<'d, T, M>, +} + +pub struct UartTx<'d, T: Instance, M: Mode> { + tx_dma: Option>, + phantom: PhantomData<(&'d mut T, M)>, +} + +pub struct UartRx<'d, T: Instance, M: Mode> { + rx_dma: Option>, + phantom: PhantomData<(&'d mut T, M)>, +} + +impl<'d, T: Instance, M: Mode> UartTx<'d, T, M> { + /// Create a new DMA-enabled UART which can only send data + pub fn new( + _uart: impl Peripheral

+ 'd, + tx: impl Peripheral

> + 'd, + tx_dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(tx, tx_dma); + Uart::::init(Some(tx.map_into()), None, None, None, config); + Self::new_inner(Some(tx_dma.map_into())) + } + + fn new_inner(tx_dma: Option>) -> Self { + Self { + tx_dma, + phantom: PhantomData, + } + } + + pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { + let r = T::regs(); + for &b in buffer { + while r.uartfr().read().txff() {} + r.uartdr().write(|w| w.set_data(b)); + } + Ok(()) + } + + pub fn blocking_flush(&mut self) -> Result<(), Error> { + let r = T::regs(); + while !r.uartfr().read().txfe() {} + Ok(()) + } + + pub fn busy(&self) -> bool { + T::regs().uartfr().read().busy() + } + + /// Assert a break condition after waiting for the transmit buffers to empty, + /// for the specified number of bit times. This condition must be asserted + /// for at least two frame times to be effective, `bits` will adjusted + /// according to frame size, parity, and stop bit settings to ensure this. + /// + /// This method may block for a long amount of time since it has to wait + /// for the transmit fifo to empty, which may take a while on slow links. + pub async fn send_break(&mut self, bits: u32) { + let regs = T::regs(); + let bits = bits.max({ + let lcr = regs.uartlcr_h().read(); + let width = lcr.wlen() as u32 + 5; + let parity = lcr.pen() as u32; + let stops = 1 + lcr.stp2() as u32; + 2 * (1 + width + parity + stops) + }); + let divx64 = (((regs.uartibrd().read().baud_divint() as u32) << 6) + + regs.uartfbrd().read().baud_divfrac() as u32) as u64; + let div_clk = clk_peri_freq() as u64 * 64; + let wait_usecs = (1_000_000 * bits as u64 * divx64 * 16 + div_clk - 1) / div_clk; + + self.blocking_flush().unwrap(); + while self.busy() {} + regs.uartlcr_h().write_set(|w| w.set_brk(true)); + Timer::after(Duration::from_micros(wait_usecs)).await; + regs.uartlcr_h().write_clear(|w| w.set_brk(true)); + } +} + +impl<'d, T: Instance> UartTx<'d, T, Blocking> { + #[cfg(feature = "nightly")] + pub fn into_buffered( + self, + irq: impl Binding>, + tx_buffer: &'d mut [u8], + ) -> BufferedUartTx<'d, T> { + buffered::init_buffers::(irq, tx_buffer, &mut []); + + BufferedUartTx { phantom: PhantomData } + } +} + +impl<'d, T: Instance> UartTx<'d, T, Async> { + pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { + let ch = self.tx_dma.as_mut().unwrap(); + let transfer = unsafe { + T::regs().uartdmacr().write_set(|reg| { + reg.set_txdmae(true); + }); + // If we don't assign future to a variable, the data register pointer + // is held across an await and makes the future non-Send. + crate::dma::write(ch, buffer, T::regs().uartdr().as_ptr() as *mut _, T::TX_DREQ) + }; + transfer.await; + Ok(()) + } +} + +impl<'d, T: Instance, M: Mode> UartRx<'d, T, M> { + /// Create a new DMA-enabled UART which can only receive data + pub fn new( + _uart: impl Peripheral

+ 'd, + rx: impl Peripheral

> + 'd, + _irq: impl Binding>, + rx_dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(rx, rx_dma); + Uart::::init(None, Some(rx.map_into()), None, None, config); + Self::new_inner(true, Some(rx_dma.map_into())) + } + + fn new_inner(has_irq: bool, rx_dma: Option>) -> Self { + debug_assert_eq!(has_irq, rx_dma.is_some()); + if has_irq { + // disable all error interrupts initially + T::regs().uartimsc().write(|w| w.0 = 0); + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + } + Self { + rx_dma, + phantom: PhantomData, + } + } + + pub fn blocking_read(&mut self, mut buffer: &mut [u8]) -> Result<(), Error> { + while buffer.len() > 0 { + let received = self.drain_fifo(buffer)?; + buffer = &mut buffer[received..]; + } + Ok(()) + } + + fn drain_fifo(&mut self, buffer: &mut [u8]) -> Result { + let r = T::regs(); + for (i, b) in buffer.iter_mut().enumerate() { + if r.uartfr().read().rxfe() { + return Ok(i); + } + + let dr = r.uartdr().read(); + + if dr.oe() { + return Err(Error::Overrun); + } else if dr.be() { + return Err(Error::Break); + } else if dr.pe() { + return Err(Error::Parity); + } else if dr.fe() { + return Err(Error::Framing); + } else { + *b = dr.data(); + } + } + Ok(buffer.len()) + } +} + +impl<'d, T: Instance, M: Mode> Drop for UartRx<'d, T, M> { + fn drop(&mut self) { + if let Some(_) = self.rx_dma { + T::Interrupt::disable(); + // clear dma flags. irq handlers use these to disambiguate among themselves. + T::regs().uartdmacr().write_clear(|reg| { + reg.set_rxdmae(true); + reg.set_txdmae(true); + reg.set_dmaonerr(true); + }); + } + } +} + +impl<'d, T: Instance> UartRx<'d, T, Blocking> { + pub fn new_blocking( + _uart: impl Peripheral

+ 'd, + rx: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + into_ref!(rx); + Uart::::init(None, Some(rx.map_into()), None, None, config); + Self::new_inner(false, None) + } + + #[cfg(feature = "nightly")] + pub fn into_buffered( + self, + irq: impl Binding>, + rx_buffer: &'d mut [u8], + ) -> BufferedUartRx<'d, T> { + buffered::init_buffers::(irq, &mut [], rx_buffer); + + BufferedUartRx { phantom: PhantomData } + } +} + +pub struct InterruptHandler { + _uart: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let uart = T::regs(); + if !uart.uartdmacr().read().rxdmae() { + return; + } + + let state = T::dma_state(); + let errs = uart.uartris().read(); + state.rx_errs.store(errs.0 as u16, Ordering::Relaxed); + state.rx_err_waker.wake(); + // disable the error interrupts instead of clearing the flags. clearing the + // flags would allow the dma transfer to continue, potentially signaling + // completion before we can check for errors that happened *during* the transfer. + uart.uartimsc().write_clear(|w| w.0 = errs.0); + } +} + +impl<'d, T: Instance> UartRx<'d, T, Async> { + pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + // clear error flags before we drain the fifo. errors that have accumulated + // in the flags will also be present in the fifo. + T::dma_state().rx_errs.store(0, Ordering::Relaxed); + T::regs().uarticr().write(|w| { + w.set_oeic(true); + w.set_beic(true); + w.set_peic(true); + w.set_feic(true); + }); + + // then drain the fifo. we need to read at most 32 bytes. errors that apply + // to fifo bytes will be reported directly. + let buffer = match { + let limit = buffer.len().min(32); + self.drain_fifo(&mut buffer[0..limit]) + } { + Ok(len) if len < buffer.len() => &mut buffer[len..], + Ok(_) => return Ok(()), + Err(e) => return Err(e), + }; + + // start a dma transfer. if errors have happened in the interim some error + // interrupt flags will have been raised, and those will be picked up immediately + // by the interrupt handler. + let ch = self.rx_dma.as_mut().unwrap(); + T::regs().uartimsc().write_set(|w| { + w.set_oeim(true); + w.set_beim(true); + w.set_peim(true); + w.set_feim(true); + }); + T::regs().uartdmacr().write_set(|reg| { + reg.set_rxdmae(true); + reg.set_dmaonerr(true); + }); + let transfer = unsafe { + // If we don't assign future to a variable, the data register pointer + // is held across an await and makes the future non-Send. + crate::dma::read(ch, T::regs().uartdr().as_ptr() as *const _, buffer, T::RX_DREQ) + }; + + // wait for either the transfer to complete or an error to happen. + let transfer_result = select( + transfer, + poll_fn(|cx| { + T::dma_state().rx_err_waker.register(cx.waker()); + match T::dma_state().rx_errs.swap(0, Ordering::Relaxed) { + 0 => Poll::Pending, + e => Poll::Ready(Uartris(e as u32)), + } + }), + ) + .await; + + let errors = match transfer_result { + Either::First(()) => return Ok(()), + Either::Second(e) => e, + }; + + if errors.0 == 0 { + return Ok(()); + } else if errors.oeris() { + return Err(Error::Overrun); + } else if errors.beris() { + return Err(Error::Break); + } else if errors.peris() { + return Err(Error::Parity); + } else if errors.feris() { + return Err(Error::Framing); + } + unreachable!("unrecognized rx error"); + } +} + +impl<'d, T: Instance> Uart<'d, T, Blocking> { + /// Create a new UART without hardware flow control + pub fn new_blocking( + uart: impl Peripheral

+ 'd, + tx: impl Peripheral

> + 'd, + rx: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + into_ref!(tx, rx); + Self::new_inner( + uart, + tx.map_into(), + rx.map_into(), + None, + None, + false, + None, + None, + config, + ) + } + + /// Create a new UART with hardware flow control (RTS/CTS) + pub fn new_with_rtscts_blocking( + uart: impl Peripheral

+ 'd, + tx: impl Peripheral

> + 'd, + rx: impl Peripheral

> + 'd, + rts: impl Peripheral

> + 'd, + cts: impl Peripheral

> + 'd, + config: Config, + ) -> Self { + into_ref!(tx, rx, cts, rts); + Self::new_inner( + uart, + tx.map_into(), + rx.map_into(), + Some(rts.map_into()), + Some(cts.map_into()), + false, + None, + None, + config, + ) + } + + #[cfg(feature = "nightly")] + pub fn into_buffered( + self, + irq: impl Binding>, + tx_buffer: &'d mut [u8], + rx_buffer: &'d mut [u8], + ) -> BufferedUart<'d, T> { + buffered::init_buffers::(irq, tx_buffer, rx_buffer); + + BufferedUart { + rx: BufferedUartRx { phantom: PhantomData }, + tx: BufferedUartTx { phantom: PhantomData }, + } + } +} + +impl<'d, T: Instance> Uart<'d, T, Async> { + /// Create a new DMA enabled UART without hardware flow control + pub fn new( + uart: impl Peripheral

+ 'd, + tx: impl Peripheral

> + 'd, + rx: impl Peripheral

> + 'd, + _irq: impl Binding>, + tx_dma: impl Peripheral

+ 'd, + rx_dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(tx, rx, tx_dma, rx_dma); + Self::new_inner( + uart, + tx.map_into(), + rx.map_into(), + None, + None, + true, + Some(tx_dma.map_into()), + Some(rx_dma.map_into()), + config, + ) + } + + /// Create a new DMA enabled UART with hardware flow control (RTS/CTS) + pub fn new_with_rtscts( + uart: impl Peripheral

+ 'd, + tx: impl Peripheral

> + 'd, + rx: impl Peripheral

> + 'd, + rts: impl Peripheral

> + 'd, + cts: impl Peripheral

> + 'd, + _irq: impl Binding>, + tx_dma: impl Peripheral

+ 'd, + rx_dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(tx, rx, cts, rts, tx_dma, rx_dma); + Self::new_inner( + uart, + tx.map_into(), + rx.map_into(), + Some(rts.map_into()), + Some(cts.map_into()), + true, + Some(tx_dma.map_into()), + Some(rx_dma.map_into()), + config, + ) + } +} + +impl<'d, T: Instance + 'd, M: Mode> Uart<'d, T, M> { + fn new_inner( + _uart: impl Peripheral

+ 'd, + mut tx: PeripheralRef<'d, AnyPin>, + mut rx: PeripheralRef<'d, AnyPin>, + mut rts: Option>, + mut cts: Option>, + has_irq: bool, + tx_dma: Option>, + rx_dma: Option>, + config: Config, + ) -> Self { + Self::init( + Some(tx.reborrow()), + Some(rx.reborrow()), + rts.as_mut().map(|x| x.reborrow()), + cts.as_mut().map(|x| x.reborrow()), + config, + ); + + Self { + tx: UartTx::new_inner(tx_dma), + rx: UartRx::new_inner(has_irq, rx_dma), + } + } + + fn init( + tx: Option>, + rx: Option>, + rts: Option>, + cts: Option>, + config: Config, + ) { + let r = T::regs(); + if let Some(pin) = &tx { + pin.io().ctrl().write(|w| { + w.set_funcsel(2); + w.set_outover(if config.invert_tx { + Outover::INVERT + } else { + Outover::NORMAL + }); + }); + pin.pad_ctrl().write(|w| w.set_ie(true)); + } + if let Some(pin) = &rx { + pin.io().ctrl().write(|w| { + w.set_funcsel(2); + w.set_inover(if config.invert_rx { + Inover::INVERT + } else { + Inover::NORMAL + }); + }); + pin.pad_ctrl().write(|w| w.set_ie(true)); + } + if let Some(pin) = &cts { + pin.io().ctrl().write(|w| { + w.set_funcsel(2); + w.set_inover(if config.invert_cts { + Inover::INVERT + } else { + Inover::NORMAL + }); + }); + pin.pad_ctrl().write(|w| w.set_ie(true)); + } + if let Some(pin) = &rts { + pin.io().ctrl().write(|w| { + w.set_funcsel(2); + w.set_outover(if config.invert_rts { + Outover::INVERT + } else { + Outover::NORMAL + }); + }); + pin.pad_ctrl().write(|w| w.set_ie(true)); + } + + Self::set_baudrate_inner(config.baudrate); + + let (pen, eps) = match config.parity { + Parity::ParityNone => (false, false), + Parity::ParityOdd => (true, false), + Parity::ParityEven => (true, true), + }; + + r.uartlcr_h().write(|w| { + w.set_wlen(config.data_bits.bits()); + w.set_stp2(config.stop_bits == StopBits::STOP2); + w.set_pen(pen); + w.set_eps(eps); + w.set_fen(true); + }); + + r.uartifls().write(|w| { + w.set_rxiflsel(0b000); + w.set_txiflsel(0b000); + }); + + r.uartcr().write(|w| { + w.set_uarten(true); + w.set_rxe(true); + w.set_txe(true); + w.set_ctsen(cts.is_some()); + w.set_rtsen(rts.is_some()); + }); + } + + /// sets baudrate on runtime + pub fn set_baudrate(&mut self, baudrate: u32) { + Self::set_baudrate_inner(baudrate); + } + + fn set_baudrate_inner(baudrate: u32) { + let r = T::regs(); + + let clk_base = crate::clocks::clk_peri_freq(); + + let baud_rate_div = (8 * clk_base) / baudrate; + let mut baud_ibrd = baud_rate_div >> 7; + let mut baud_fbrd = ((baud_rate_div & 0x7f) + 1) / 2; + + if baud_ibrd == 0 { + baud_ibrd = 1; + baud_fbrd = 0; + } else if baud_ibrd >= 65535 { + baud_ibrd = 65535; + baud_fbrd = 0; + } + + // Load PL011's baud divisor registers + r.uartibrd().write_value(pac::uart::regs::Uartibrd(baud_ibrd)); + r.uartfbrd().write_value(pac::uart::regs::Uartfbrd(baud_fbrd)); + + // PL011 needs a (dummy) line control register write to latch in the + // divisors. We don't want to actually change LCR contents here. + r.uartlcr_h().modify(|_| {}); + } +} + +impl<'d, T: Instance, M: Mode> Uart<'d, T, M> { + pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { + self.tx.blocking_write(buffer) + } + + pub fn blocking_flush(&mut self) -> Result<(), Error> { + self.tx.blocking_flush() + } + + pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + self.rx.blocking_read(buffer) + } + + pub fn busy(&self) -> bool { + self.tx.busy() + } + + pub async fn send_break(&mut self, bits: u32) { + self.tx.send_break(bits).await + } + + /// Split the Uart into a transmitter and receiver, which is particularly + /// useful when having two tasks correlating to transmitting and receiving. + pub fn split(self) -> (UartTx<'d, T, M>, UartRx<'d, T, M>) { + (self.tx, self.rx) + } +} + +impl<'d, T: Instance> Uart<'d, T, Async> { + pub async fn write(&mut self, buffer: &[u8]) -> Result<(), Error> { + self.tx.write(buffer).await + } + + pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + self.rx.read(buffer).await + } +} + +mod eh02 { + use super::*; + + impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Read for UartRx<'d, T, M> { + type Error = Error; + fn read(&mut self) -> Result> { + let r = T::regs(); + if r.uartfr().read().rxfe() { + return Err(nb::Error::WouldBlock); + } + + let dr = r.uartdr().read(); + + if dr.oe() { + Err(nb::Error::Other(Error::Overrun)) + } else if dr.be() { + Err(nb::Error::Other(Error::Break)) + } else if dr.pe() { + Err(nb::Error::Other(Error::Parity)) + } else if dr.fe() { + Err(nb::Error::Other(Error::Framing)) + } else { + Ok(dr.data()) + } + } + } + + impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Write for UartTx<'d, T, M> { + type Error = Error; + + fn write(&mut self, word: u8) -> Result<(), nb::Error> { + let r = T::regs(); + if r.uartfr().read().txff() { + return Err(nb::Error::WouldBlock); + } + + r.uartdr().write(|w| w.set_data(word)); + Ok(()) + } + + fn flush(&mut self) -> Result<(), nb::Error> { + let r = T::regs(); + if !r.uartfr().read().txfe() { + return Err(nb::Error::WouldBlock); + } + Ok(()) + } + } + + impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::serial::Write for UartTx<'d, T, M> { + type Error = Error; + + fn bwrite_all(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(buffer) + } + + fn bflush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } + } + + impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Read for Uart<'d, T, M> { + type Error = Error; + + fn read(&mut self) -> Result> { + embedded_hal_02::serial::Read::read(&mut self.rx) + } + } + + impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Write for Uart<'d, T, M> { + type Error = Error; + + fn write(&mut self, word: u8) -> Result<(), nb::Error> { + embedded_hal_02::serial::Write::write(&mut self.tx, word) + } + + fn flush(&mut self) -> Result<(), nb::Error> { + embedded_hal_02::serial::Write::flush(&mut self.tx) + } + } + + impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::serial::Write for Uart<'d, T, M> { + type Error = Error; + + fn bwrite_all(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(buffer) + } + + fn bflush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } + } +} + +#[cfg(feature = "unstable-traits")] +mod eh1 { + use super::*; + + impl embedded_hal_1::serial::Error for Error { + fn kind(&self) -> embedded_hal_1::serial::ErrorKind { + match *self { + Self::Framing => embedded_hal_1::serial::ErrorKind::FrameFormat, + Self::Break => embedded_hal_1::serial::ErrorKind::Other, + Self::Overrun => embedded_hal_1::serial::ErrorKind::Overrun, + Self::Parity => embedded_hal_1::serial::ErrorKind::Parity, + } + } + } + + impl<'d, T: Instance, M: Mode> embedded_hal_1::serial::ErrorType for Uart<'d, T, M> { + type Error = Error; + } + + impl<'d, T: Instance, M: Mode> embedded_hal_1::serial::ErrorType for UartTx<'d, T, M> { + type Error = Error; + } + + impl<'d, T: Instance, M: Mode> embedded_hal_1::serial::ErrorType for UartRx<'d, T, M> { + type Error = Error; + } + + impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::Read for UartRx<'d, T, M> { + fn read(&mut self) -> nb::Result { + let r = T::regs(); + let dr = r.uartdr().read(); + + if dr.oe() { + Err(nb::Error::Other(Error::Overrun)) + } else if dr.be() { + Err(nb::Error::Other(Error::Break)) + } else if dr.pe() { + Err(nb::Error::Other(Error::Parity)) + } else if dr.fe() { + Err(nb::Error::Other(Error::Framing)) + } else if dr.fe() { + Ok(dr.data()) + } else { + Err(nb::Error::WouldBlock) + } + } + } + + impl<'d, T: Instance, M: Mode> embedded_hal_1::serial::Write for UartTx<'d, T, M> { + fn write(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(buffer) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } + } + + impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::Write for UartTx<'d, T, M> { + fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { + self.blocking_write(&[char]).map_err(nb::Error::Other) + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + self.blocking_flush().map_err(nb::Error::Other) + } + } + + impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::Read for Uart<'d, T, M> { + fn read(&mut self) -> Result> { + embedded_hal_02::serial::Read::read(&mut self.rx) + } + } + + impl<'d, T: Instance, M: Mode> embedded_hal_1::serial::Write for Uart<'d, T, M> { + fn write(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(buffer) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } + } + + impl<'d, T: Instance, M: Mode> embedded_hal_nb::serial::Write for Uart<'d, T, M> { + fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { + self.blocking_write(&[char]).map_err(nb::Error::Other) + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + self.blocking_flush().map_err(nb::Error::Other) + } + } +} + +mod sealed { + use super::*; + + pub trait Mode {} + + pub trait Instance { + const TX_DREQ: u8; + const RX_DREQ: u8; + + type Interrupt: interrupt::typelevel::Interrupt; + + fn regs() -> pac::uart::Uart; + + #[cfg(feature = "nightly")] + fn buffered_state() -> &'static buffered::State; + + fn dma_state() -> &'static DmaState; + } + pub trait TxPin {} + pub trait RxPin {} + pub trait CtsPin {} + pub trait RtsPin {} +} + +pub trait Mode: sealed::Mode {} + +macro_rules! impl_mode { + ($name:ident) => { + impl sealed::Mode for $name {} + impl Mode for $name {} + }; +} + +pub struct Blocking; +pub struct Async; + +impl_mode!(Blocking); +impl_mode!(Async); + +pub trait Instance: sealed::Instance {} + +macro_rules! impl_instance { + ($inst:ident, $irq:ident, $tx_dreq:expr, $rx_dreq:expr) => { + impl sealed::Instance for peripherals::$inst { + const TX_DREQ: u8 = $tx_dreq; + const RX_DREQ: u8 = $rx_dreq; + + type Interrupt = crate::interrupt::typelevel::$irq; + + fn regs() -> pac::uart::Uart { + pac::$inst + } + + #[cfg(feature = "nightly")] + fn buffered_state() -> &'static buffered::State { + static STATE: buffered::State = buffered::State::new(); + &STATE + } + + fn dma_state() -> &'static DmaState { + static STATE: DmaState = DmaState { + rx_err_waker: AtomicWaker::new(), + rx_errs: AtomicU16::new(0), + }; + &STATE + } + } + impl Instance for peripherals::$inst {} + }; +} + +impl_instance!(UART0, UART0_IRQ, 20, 21); +impl_instance!(UART1, UART1_IRQ, 22, 23); + +pub trait TxPin: sealed::TxPin + crate::gpio::Pin {} +pub trait RxPin: sealed::RxPin + crate::gpio::Pin {} +pub trait CtsPin: sealed::CtsPin + crate::gpio::Pin {} +pub trait RtsPin: sealed::RtsPin + crate::gpio::Pin {} + +macro_rules! impl_pin { + ($pin:ident, $instance:ident, $function:ident) => { + impl sealed::$function for peripherals::$pin {} + impl $function for peripherals::$pin {} + }; +} + +impl_pin!(PIN_0, UART0, TxPin); +impl_pin!(PIN_1, UART0, RxPin); +impl_pin!(PIN_2, UART0, CtsPin); +impl_pin!(PIN_3, UART0, RtsPin); +impl_pin!(PIN_4, UART1, TxPin); +impl_pin!(PIN_5, UART1, RxPin); +impl_pin!(PIN_6, UART1, CtsPin); +impl_pin!(PIN_7, UART1, RtsPin); +impl_pin!(PIN_8, UART1, TxPin); +impl_pin!(PIN_9, UART1, RxPin); +impl_pin!(PIN_10, UART1, CtsPin); +impl_pin!(PIN_11, UART1, RtsPin); +impl_pin!(PIN_12, UART0, TxPin); +impl_pin!(PIN_13, UART0, RxPin); +impl_pin!(PIN_14, UART0, CtsPin); +impl_pin!(PIN_15, UART0, RtsPin); +impl_pin!(PIN_16, UART0, TxPin); +impl_pin!(PIN_17, UART0, RxPin); +impl_pin!(PIN_18, UART0, CtsPin); +impl_pin!(PIN_19, UART0, RtsPin); +impl_pin!(PIN_20, UART1, TxPin); +impl_pin!(PIN_21, UART1, RxPin); +impl_pin!(PIN_22, UART1, CtsPin); +impl_pin!(PIN_23, UART1, RtsPin); +impl_pin!(PIN_24, UART1, TxPin); +impl_pin!(PIN_25, UART1, RxPin); +impl_pin!(PIN_26, UART1, CtsPin); +impl_pin!(PIN_27, UART1, RtsPin); +impl_pin!(PIN_28, UART0, TxPin); +impl_pin!(PIN_29, UART0, RxPin); diff --git a/embassy-rp/src/usb.rs b/embassy-rp/src/usb.rs new file mode 100644 index 000000000..4ab881f6e --- /dev/null +++ b/embassy-rp/src/usb.rs @@ -0,0 +1,800 @@ +use core::future::poll_fn; +use core::marker::PhantomData; +use core::slice; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; + +use embassy_sync::waitqueue::AtomicWaker; +use embassy_usb_driver as driver; +use embassy_usb_driver::{ + Direction, EndpointAddress, EndpointAllocError, EndpointError, EndpointInfo, EndpointType, Event, Unsupported, +}; + +use crate::interrupt::typelevel::{Binding, Interrupt}; +use crate::{interrupt, pac, peripherals, Peripheral, RegExt}; + +pub(crate) mod sealed { + pub trait Instance { + fn regs() -> crate::pac::usb::Usb; + fn dpram() -> crate::pac::usb_dpram::UsbDpram; + } +} + +pub trait Instance: sealed::Instance + 'static { + type Interrupt: interrupt::typelevel::Interrupt; +} + +impl crate::usb::sealed::Instance for peripherals::USB { + fn regs() -> pac::usb::Usb { + pac::USBCTRL_REGS + } + fn dpram() -> crate::pac::usb_dpram::UsbDpram { + pac::USBCTRL_DPRAM + } +} + +impl crate::usb::Instance for peripherals::USB { + type Interrupt = crate::interrupt::typelevel::USBCTRL_IRQ; +} + +const EP_COUNT: usize = 16; +const EP_MEMORY_SIZE: usize = 4096; +const EP_MEMORY: *mut u8 = pac::USBCTRL_DPRAM.as_ptr() as *mut u8; + +const NEW_AW: AtomicWaker = AtomicWaker::new(); +static BUS_WAKER: AtomicWaker = NEW_AW; +static EP_IN_WAKERS: [AtomicWaker; EP_COUNT] = [NEW_AW; EP_COUNT]; +static EP_OUT_WAKERS: [AtomicWaker; EP_COUNT] = [NEW_AW; EP_COUNT]; + +struct EndpointBuffer { + addr: u16, + len: u16, + _phantom: PhantomData, +} + +impl EndpointBuffer { + const fn new(addr: u16, len: u16) -> Self { + Self { + addr, + len, + _phantom: PhantomData, + } + } + + fn read(&mut self, buf: &mut [u8]) { + assert!(buf.len() <= self.len as usize); + compiler_fence(Ordering::SeqCst); + let mem = unsafe { slice::from_raw_parts(EP_MEMORY.add(self.addr as _), buf.len()) }; + buf.copy_from_slice(mem); + compiler_fence(Ordering::SeqCst); + } + + fn write(&mut self, buf: &[u8]) { + assert!(buf.len() <= self.len as usize); + compiler_fence(Ordering::SeqCst); + let mem = unsafe { slice::from_raw_parts_mut(EP_MEMORY.add(self.addr as _), buf.len()) }; + mem.copy_from_slice(buf); + compiler_fence(Ordering::SeqCst); + } +} + +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +struct EndpointData { + ep_type: EndpointType, // only valid if used + max_packet_size: u16, + used: bool, +} + +impl EndpointData { + const fn new() -> Self { + Self { + ep_type: EndpointType::Bulk, + max_packet_size: 0, + used: false, + } + } +} + +pub struct Driver<'d, T: Instance> { + phantom: PhantomData<&'d mut T>, + ep_in: [EndpointData; EP_COUNT], + ep_out: [EndpointData; EP_COUNT], + ep_mem_free: u16, // first free address in EP mem, in bytes. +} + +impl<'d, T: Instance> Driver<'d, T> { + pub fn new(_usb: impl Peripheral

+ 'd, _irq: impl Binding>) -> Self { + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + let regs = T::regs(); + unsafe { + // zero fill regs + let p = regs.as_ptr() as *mut u32; + for i in 0..0x9c / 4 { + p.add(i).write_volatile(0) + } + + // zero fill epmem + let p = EP_MEMORY as *mut u32; + for i in 0..0x100 / 4 { + p.add(i).write_volatile(0) + } + } + + regs.usb_muxing().write(|w| { + w.set_to_phy(true); + w.set_softcon(true); + }); + regs.usb_pwr().write(|w| { + w.set_vbus_detect(true); + w.set_vbus_detect_override_en(true); + }); + regs.main_ctrl().write(|w| { + w.set_controller_en(true); + }); + + // Initialize the bus so that it signals that power is available + BUS_WAKER.wake(); + + Self { + phantom: PhantomData, + ep_in: [EndpointData::new(); EP_COUNT], + ep_out: [EndpointData::new(); EP_COUNT], + ep_mem_free: 0x180, // data buffer region + } + } + + fn alloc_endpoint( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + ) -> Result, driver::EndpointAllocError> { + trace!( + "allocating type={:?} mps={:?} interval_ms={}, dir={:?}", + ep_type, + max_packet_size, + interval_ms, + D::dir() + ); + + let alloc = match D::dir() { + Direction::Out => &mut self.ep_out, + Direction::In => &mut self.ep_in, + }; + + let index = alloc.iter_mut().enumerate().find(|(i, ep)| { + if *i == 0 { + return false; // reserved for control pipe + } + !ep.used + }); + + let (index, ep) = index.ok_or(EndpointAllocError)?; + assert!(!ep.used); + + // as per datasheet, the maximum buffer size is 64, except for isochronous + // endpoints, which are allowed to be up to 1023 bytes. + if (ep_type != EndpointType::Isochronous && max_packet_size > 64) || max_packet_size > 1023 { + warn!("max_packet_size too high: {}", max_packet_size); + return Err(EndpointAllocError); + } + + // ep mem addrs must be 64-byte aligned, so there's no point in trying + // to allocate smaller chunks to save memory. + let len = (max_packet_size + 63) / 64 * 64; + + let addr = self.ep_mem_free; + if addr + len > EP_MEMORY_SIZE as u16 { + warn!("Endpoint memory full"); + return Err(EndpointAllocError); + } + self.ep_mem_free += len; + + let buf = EndpointBuffer { + addr, + len, + _phantom: PhantomData, + }; + + trace!(" index={} addr={} len={}", index, buf.addr, buf.len); + + ep.ep_type = ep_type; + ep.used = true; + ep.max_packet_size = max_packet_size; + + let ep_type_reg = match ep_type { + EndpointType::Bulk => pac::usb_dpram::vals::EpControlEndpointType::BULK, + EndpointType::Control => pac::usb_dpram::vals::EpControlEndpointType::CONTROL, + EndpointType::Interrupt => pac::usb_dpram::vals::EpControlEndpointType::INTERRUPT, + EndpointType::Isochronous => pac::usb_dpram::vals::EpControlEndpointType::ISOCHRONOUS, + }; + + match D::dir() { + Direction::Out => T::dpram().ep_out_control(index - 1).write(|w| { + w.set_enable(false); + w.set_buffer_address(addr); + w.set_interrupt_per_buff(true); + w.set_endpoint_type(ep_type_reg); + }), + Direction::In => T::dpram().ep_in_control(index - 1).write(|w| { + w.set_enable(false); + w.set_buffer_address(addr); + w.set_interrupt_per_buff(true); + w.set_endpoint_type(ep_type_reg); + }), + } + + Ok(Endpoint { + _phantom: PhantomData, + info: EndpointInfo { + addr: EndpointAddress::from_parts(index, D::dir()), + ep_type, + max_packet_size, + interval_ms, + }, + buf, + }) + } +} + +pub struct InterruptHandler { + _uart: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let regs = T::regs(); + //let x = regs.istr().read().0; + //trace!("USB IRQ: {:08x}", x); + + let ints = regs.ints().read(); + + if ints.bus_reset() { + regs.inte().write_clear(|w| w.set_bus_reset(true)); + BUS_WAKER.wake(); + } + if ints.dev_resume_from_host() { + regs.inte().write_clear(|w| w.set_dev_resume_from_host(true)); + BUS_WAKER.wake(); + } + if ints.dev_suspend() { + regs.inte().write_clear(|w| w.set_dev_suspend(true)); + BUS_WAKER.wake(); + } + if ints.setup_req() { + regs.inte().write_clear(|w| w.set_setup_req(true)); + EP_OUT_WAKERS[0].wake(); + } + + if ints.buff_status() { + let s = regs.buff_status().read(); + regs.buff_status().write_value(s); + + for i in 0..EP_COUNT { + if s.ep_in(i) { + EP_IN_WAKERS[i].wake(); + } + if s.ep_out(i) { + EP_OUT_WAKERS[i].wake(); + } + } + } + } +} + +impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> { + type EndpointOut = Endpoint<'d, T, Out>; + type EndpointIn = Endpoint<'d, T, In>; + type ControlPipe = ControlPipe<'d, T>; + type Bus = Bus<'d, T>; + + fn alloc_endpoint_in( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + ) -> Result { + self.alloc_endpoint(ep_type, max_packet_size, interval_ms) + } + + fn alloc_endpoint_out( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + ) -> Result { + self.alloc_endpoint(ep_type, max_packet_size, interval_ms) + } + + fn start(self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) { + let regs = T::regs(); + regs.inte().write(|w| { + w.set_bus_reset(true); + w.set_buff_status(true); + w.set_dev_resume_from_host(true); + w.set_dev_suspend(true); + w.set_setup_req(true); + }); + regs.int_ep_ctrl().write(|w| { + w.set_int_ep_active(0xFFFE); // all EPs + }); + regs.sie_ctrl().write(|w| { + w.set_ep0_int_1buf(true); + w.set_pullup_en(true); + }); + + trace!("enabled"); + + ( + Bus { + phantom: PhantomData, + inited: false, + ep_out: self.ep_out, + }, + ControlPipe { + _phantom: PhantomData, + max_packet_size: control_max_packet_size, + }, + ) + } +} + +pub struct Bus<'d, T: Instance> { + phantom: PhantomData<&'d mut T>, + ep_out: [EndpointData; EP_COUNT], + inited: bool, +} + +impl<'d, T: Instance> driver::Bus for Bus<'d, T> { + async fn poll(&mut self) -> Event { + poll_fn(move |cx| { + BUS_WAKER.register(cx.waker()); + + // TODO: implement VBUS detection. + if !self.inited { + self.inited = true; + return Poll::Ready(Event::PowerDetected); + } + + let regs = T::regs(); + let siestatus = regs.sie_status().read(); + let intrstatus = regs.intr().read(); + + if siestatus.resume() { + regs.sie_status().write(|w| w.set_resume(true)); + return Poll::Ready(Event::Resume); + } + + if siestatus.bus_reset() { + regs.sie_status().write(|w| { + w.set_bus_reset(true); + w.set_setup_rec(true); + }); + regs.buff_status().write(|w| w.0 = 0xFFFF_FFFF); + regs.addr_endp().write(|w| w.set_address(0)); + + for i in 1..EP_COUNT { + T::dpram().ep_in_control(i - 1).modify(|w| w.set_enable(false)); + T::dpram().ep_out_control(i - 1).modify(|w| w.set_enable(false)); + } + + for w in &EP_IN_WAKERS { + w.wake() + } + for w in &EP_OUT_WAKERS { + w.wake() + } + return Poll::Ready(Event::Reset); + } + + if siestatus.suspended() && intrstatus.dev_suspend() { + regs.sie_status().write(|w| w.set_suspended(true)); + return Poll::Ready(Event::Suspend); + } + + // no pending event. Reenable all irqs. + regs.inte().write_set(|w| { + w.set_bus_reset(true); + w.set_dev_resume_from_host(true); + w.set_dev_suspend(true); + }); + Poll::Pending + }) + .await + } + + fn endpoint_set_stalled(&mut self, _ep_addr: EndpointAddress, _stalled: bool) { + todo!(); + } + + fn endpoint_is_stalled(&mut self, _ep_addr: EndpointAddress) -> bool { + todo!(); + } + + fn endpoint_set_enabled(&mut self, ep_addr: EndpointAddress, enabled: bool) { + trace!("set_enabled {:?} {}", ep_addr, enabled); + if ep_addr.index() == 0 { + return; + } + + let n = ep_addr.index(); + match ep_addr.direction() { + Direction::In => { + T::dpram().ep_in_control(n - 1).modify(|w| w.set_enable(enabled)); + T::dpram().ep_in_buffer_control(ep_addr.index()).write(|w| { + w.set_pid(0, true); // first packet is DATA0, but PID is flipped before + }); + EP_IN_WAKERS[n].wake(); + } + Direction::Out => { + T::dpram().ep_out_control(n - 1).modify(|w| w.set_enable(enabled)); + + T::dpram().ep_out_buffer_control(ep_addr.index()).write(|w| { + w.set_pid(0, false); + w.set_length(0, self.ep_out[n].max_packet_size); + }); + cortex_m::asm::delay(12); + T::dpram().ep_out_buffer_control(ep_addr.index()).write(|w| { + w.set_pid(0, false); + w.set_length(0, self.ep_out[n].max_packet_size); + w.set_available(0, true); + }); + EP_OUT_WAKERS[n].wake(); + } + } + } + + async fn enable(&mut self) {} + + async fn disable(&mut self) {} + + async fn remote_wakeup(&mut self) -> Result<(), Unsupported> { + Err(Unsupported) + } +} + +trait Dir { + fn dir() -> Direction; + fn waker(i: usize) -> &'static AtomicWaker; +} + +pub enum In {} +impl Dir for In { + fn dir() -> Direction { + Direction::In + } + + #[inline] + fn waker(i: usize) -> &'static AtomicWaker { + &EP_IN_WAKERS[i] + } +} + +pub enum Out {} +impl Dir for Out { + fn dir() -> Direction { + Direction::Out + } + + #[inline] + fn waker(i: usize) -> &'static AtomicWaker { + &EP_OUT_WAKERS[i] + } +} + +pub struct Endpoint<'d, T: Instance, D> { + _phantom: PhantomData<(&'d mut T, D)>, + info: EndpointInfo, + buf: EndpointBuffer, +} + +impl<'d, T: Instance> driver::Endpoint for Endpoint<'d, T, In> { + fn info(&self) -> &EndpointInfo { + &self.info + } + + async fn wait_enabled(&mut self) { + trace!("wait_enabled IN WAITING"); + let index = self.info.addr.index(); + poll_fn(|cx| { + EP_IN_WAKERS[index].register(cx.waker()); + let val = T::dpram().ep_in_control(self.info.addr.index() - 1).read(); + if val.enable() { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + trace!("wait_enabled IN OK"); + } +} + +impl<'d, T: Instance> driver::Endpoint for Endpoint<'d, T, Out> { + fn info(&self) -> &EndpointInfo { + &self.info + } + + async fn wait_enabled(&mut self) { + trace!("wait_enabled OUT WAITING"); + let index = self.info.addr.index(); + poll_fn(|cx| { + EP_OUT_WAKERS[index].register(cx.waker()); + let val = T::dpram().ep_out_control(self.info.addr.index() - 1).read(); + if val.enable() { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + trace!("wait_enabled OUT OK"); + } +} + +impl<'d, T: Instance> driver::EndpointOut for Endpoint<'d, T, Out> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + trace!("READ WAITING, buf.len() = {}", buf.len()); + let index = self.info.addr.index(); + let val = poll_fn(|cx| { + EP_OUT_WAKERS[index].register(cx.waker()); + let val = T::dpram().ep_out_buffer_control(index).read(); + if val.available(0) { + Poll::Pending + } else { + Poll::Ready(val) + } + }) + .await; + + let rx_len = val.length(0) as usize; + if rx_len > buf.len() { + return Err(EndpointError::BufferOverflow); + } + self.buf.read(&mut buf[..rx_len]); + + trace!("READ OK, rx_len = {}", rx_len); + + let pid = !val.pid(0); + T::dpram().ep_out_buffer_control(index).write(|w| { + w.set_pid(0, pid); + w.set_length(0, self.info.max_packet_size); + }); + cortex_m::asm::delay(12); + T::dpram().ep_out_buffer_control(index).write(|w| { + w.set_pid(0, pid); + w.set_length(0, self.info.max_packet_size); + w.set_available(0, true); + }); + + Ok(rx_len) + } +} + +impl<'d, T: Instance> driver::EndpointIn for Endpoint<'d, T, In> { + async fn write(&mut self, buf: &[u8]) -> Result<(), EndpointError> { + if buf.len() > self.info.max_packet_size as usize { + return Err(EndpointError::BufferOverflow); + } + + trace!("WRITE WAITING"); + + let index = self.info.addr.index(); + let val = poll_fn(|cx| { + EP_IN_WAKERS[index].register(cx.waker()); + let val = T::dpram().ep_in_buffer_control(index).read(); + if val.available(0) { + Poll::Pending + } else { + Poll::Ready(val) + } + }) + .await; + + self.buf.write(buf); + + let pid = !val.pid(0); + T::dpram().ep_in_buffer_control(index).write(|w| { + w.set_pid(0, pid); + w.set_length(0, buf.len() as _); + w.set_full(0, true); + }); + cortex_m::asm::delay(12); + T::dpram().ep_in_buffer_control(index).write(|w| { + w.set_pid(0, pid); + w.set_length(0, buf.len() as _); + w.set_full(0, true); + w.set_available(0, true); + }); + + trace!("WRITE OK"); + + Ok(()) + } +} + +pub struct ControlPipe<'d, T: Instance> { + _phantom: PhantomData<&'d mut T>, + max_packet_size: u16, +} + +impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { + fn max_packet_size(&self) -> usize { + 64 + } + + async fn setup(&mut self) -> [u8; 8] { + loop { + trace!("SETUP read waiting"); + let regs = T::regs(); + regs.inte().write_set(|w| w.set_setup_req(true)); + + poll_fn(|cx| { + EP_OUT_WAKERS[0].register(cx.waker()); + let regs = T::regs(); + if regs.sie_status().read().setup_rec() { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + + let mut buf = [0; 8]; + EndpointBuffer::::new(0, 8).read(&mut buf); + + let regs = T::regs(); + regs.sie_status().write(|w| w.set_setup_rec(true)); + + // set PID to 0, so (after toggling) first DATA is PID 1 + T::dpram().ep_in_buffer_control(0).write(|w| w.set_pid(0, false)); + T::dpram().ep_out_buffer_control(0).write(|w| w.set_pid(0, false)); + + trace!("SETUP read ok"); + return buf; + } + } + + async fn data_out(&mut self, buf: &mut [u8], first: bool, last: bool) -> Result { + let bufcontrol = T::dpram().ep_out_buffer_control(0); + let pid = !bufcontrol.read().pid(0); + bufcontrol.write(|w| { + w.set_length(0, self.max_packet_size); + w.set_pid(0, pid); + }); + cortex_m::asm::delay(12); + bufcontrol.write(|w| { + w.set_length(0, self.max_packet_size); + w.set_pid(0, pid); + w.set_available(0, true); + }); + + trace!("control: data_out len={} first={} last={}", buf.len(), first, last); + let val = poll_fn(|cx| { + EP_OUT_WAKERS[0].register(cx.waker()); + let val = T::dpram().ep_out_buffer_control(0).read(); + if val.available(0) { + Poll::Pending + } else { + Poll::Ready(val) + } + }) + .await; + + let rx_len = val.length(0) as _; + trace!("control data_out DONE, rx_len = {}", rx_len); + + if rx_len > buf.len() { + return Err(EndpointError::BufferOverflow); + } + EndpointBuffer::::new(0x100, 64).read(&mut buf[..rx_len]); + + Ok(rx_len) + } + + async fn data_in(&mut self, data: &[u8], first: bool, last: bool) -> Result<(), EndpointError> { + trace!("control: data_in len={} first={} last={}", data.len(), first, last); + + if data.len() > 64 { + return Err(EndpointError::BufferOverflow); + } + EndpointBuffer::::new(0x100, 64).write(data); + + let bufcontrol = T::dpram().ep_in_buffer_control(0); + let pid = !bufcontrol.read().pid(0); + bufcontrol.write(|w| { + w.set_length(0, data.len() as _); + w.set_pid(0, pid); + w.set_full(0, true); + }); + cortex_m::asm::delay(12); + bufcontrol.write(|w| { + w.set_length(0, data.len() as _); + w.set_pid(0, pid); + w.set_full(0, true); + w.set_available(0, true); + }); + + poll_fn(|cx| { + EP_IN_WAKERS[0].register(cx.waker()); + let bufcontrol = T::dpram().ep_in_buffer_control(0); + if bufcontrol.read().available(0) { + Poll::Pending + } else { + Poll::Ready(()) + } + }) + .await; + trace!("control: data_in DONE"); + + if last { + // prepare status phase right away. + let bufcontrol = T::dpram().ep_out_buffer_control(0); + bufcontrol.write(|w| { + w.set_length(0, 0); + w.set_pid(0, true); + }); + cortex_m::asm::delay(12); + bufcontrol.write(|w| { + w.set_length(0, 0); + w.set_pid(0, true); + w.set_available(0, true); + }); + } + + Ok(()) + } + + async fn accept(&mut self) { + trace!("control: accept"); + + let bufcontrol = T::dpram().ep_in_buffer_control(0); + bufcontrol.write(|w| { + w.set_length(0, 0); + w.set_pid(0, true); + w.set_full(0, true); + }); + cortex_m::asm::delay(12); + bufcontrol.write(|w| { + w.set_length(0, 0); + w.set_pid(0, true); + w.set_full(0, true); + w.set_available(0, true); + }); + + // wait for completion before returning, needed so + // set_address() doesn't happen early. + poll_fn(|cx| { + EP_IN_WAKERS[0].register(cx.waker()); + if bufcontrol.read().available(0) { + Poll::Pending + } else { + Poll::Ready(()) + } + }) + .await; + } + + async fn reject(&mut self) { + trace!("control: reject"); + + let regs = T::regs(); + regs.ep_stall_arm().write_set(|w| { + w.set_ep0_in(true); + w.set_ep0_out(true); + }); + T::dpram().ep_out_buffer_control(0).write(|w| w.set_stall(true)); + T::dpram().ep_in_buffer_control(0).write(|w| w.set_stall(true)); + } + + async fn accept_set_address(&mut self, addr: u8) { + self.accept().await; + + let regs = T::regs(); + trace!("setting addr: {}", addr); + regs.addr_endp().write(|w| w.set_address(addr)) + } +} diff --git a/embassy-rp/src/watchdog.rs b/embassy-rp/src/watchdog.rs new file mode 100644 index 000000000..f1e986ec7 --- /dev/null +++ b/embassy-rp/src/watchdog.rs @@ -0,0 +1,142 @@ +//! Watchdog +//! +//! The watchdog is a countdown timer that can restart parts of the chip if it reaches zero. This can be used to restart the +//! processor if software gets stuck in an infinite loop. The programmer must periodically write a value to the watchdog to +//! stop it from reaching zero. +//! +//! Credit: based on `rp-hal` implementation (also licensed Apache+MIT) + +use core::marker::PhantomData; + +use embassy_time::Duration; + +use crate::pac; +use crate::peripherals::WATCHDOG; + +/// Watchdog peripheral +pub struct Watchdog { + phantom: PhantomData, + load_value: u32, // decremented by 2 per tick (µs) +} + +impl Watchdog { + /// Create a new `Watchdog` + pub fn new(_watchdog: WATCHDOG) -> Self { + Self { + phantom: PhantomData, + load_value: 0, + } + } + + /// Start tick generation on clk_tick which is driven from clk_ref. + /// + /// # Arguments + /// + /// * `cycles` - Total number of tick cycles before the next tick is generated. + /// It is expected to be the frequency in MHz of clk_ref. + pub fn enable_tick_generation(&mut self, cycles: u8) { + let watchdog = pac::WATCHDOG; + watchdog.tick().write(|w| { + w.set_enable(true); + w.set_cycles(cycles.into()) + }); + } + + /// Defines whether or not the watchdog timer should be paused when processor(s) are in debug mode + /// or when JTAG is accessing bus fabric + pub fn pause_on_debug(&mut self, pause: bool) { + let watchdog = pac::WATCHDOG; + watchdog.ctrl().write(|w| { + w.set_pause_dbg0(pause); + w.set_pause_dbg1(pause); + w.set_pause_jtag(pause); + }) + } + + fn load_counter(&self, counter: u32) { + let watchdog = pac::WATCHDOG; + watchdog.load().write_value(pac::watchdog::regs::Load(counter)); + } + + fn enable(&self, bit: bool) { + let watchdog = pac::WATCHDOG; + watchdog.ctrl().write(|w| w.set_enable(bit)) + } + + // Configure which hardware will be reset by the watchdog + // (everything except ROSC, XOSC) + fn configure_wdog_reset_triggers(&self) { + let psm = pac::PSM; + psm.wdsel().write_value(pac::psm::regs::Wdsel( + 0x0001ffff & !(0x01 << 0usize) & !(0x01 << 1usize), + )); + } + + /// Feed the watchdog timer + pub fn feed(&mut self) { + self.load_counter(self.load_value) + } + + /// Start the watchdog timer + pub fn start(&mut self, period: Duration) { + const MAX_PERIOD: u32 = 0xFFFFFF; + + let delay_us = period.as_micros(); + if delay_us > (MAX_PERIOD / 2) as u64 { + panic!("Period cannot exceed {} microseconds", MAX_PERIOD / 2); + } + let delay_us = delay_us as u32; + + // Due to a logic error, the watchdog decrements by 2 and + // the load value must be compensated; see RP2040-E1 + self.load_value = delay_us * 2; + + self.enable(false); + self.configure_wdog_reset_triggers(); + self.load_counter(self.load_value); + self.enable(true); + } + + /// Trigger a system reset + pub fn trigger_reset(&mut self) { + self.configure_wdog_reset_triggers(); + self.pause_on_debug(false); + self.enable(true); + let watchdog = pac::WATCHDOG; + watchdog.ctrl().write(|w| { + w.set_trigger(true); + }) + } + + /// Store data in scratch register + pub fn set_scratch(&mut self, index: usize, value: u32) { + let watchdog = pac::WATCHDOG; + match index { + 0 => watchdog.scratch0().write(|w| *w = value), + 1 => watchdog.scratch1().write(|w| *w = value), + 2 => watchdog.scratch2().write(|w| *w = value), + 3 => watchdog.scratch3().write(|w| *w = value), + 4 => watchdog.scratch4().write(|w| *w = value), + 5 => watchdog.scratch5().write(|w| *w = value), + 6 => watchdog.scratch6().write(|w| *w = value), + 7 => watchdog.scratch7().write(|w| *w = value), + _ => panic!("Invalid watchdog scratch index"), + } + } + + /// Read data from scratch register + pub fn get_scratch(&mut self, index: usize) -> u32 { + let watchdog = pac::WATCHDOG; + match index { + 0 => watchdog.scratch0().read(), + 1 => watchdog.scratch1().read(), + 2 => watchdog.scratch2().read(), + 3 => watchdog.scratch3().read(), + 4 => watchdog.scratch4().read(), + 5 => watchdog.scratch5().read(), + 6 => watchdog.scratch6().read(), + 7 => watchdog.scratch7().read(), + _ => panic!("Invalid watchdog scratch index"), + } + } +} diff --git a/embassy-stm32-wpan/Cargo.toml b/embassy-stm32-wpan/Cargo.toml new file mode 100644 index 000000000..868bffe74 --- /dev/null +++ b/embassy-stm32-wpan/Cargo.toml @@ -0,0 +1,52 @@ +[package] +name = "embassy-stm32-wpan" +version = "0.1.0" +edition = "2021" +license = "MIT OR Apache-2.0" + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-stm32-wpan-v$VERSION/embassy-stm32-wpan/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-stm32-wpan/src/" +target = "thumbv7em-none-eabihf" +features = ["stm32wb55rg"] + +[dependencies] +embassy-stm32 = { version = "0.1.0", path = "../embassy-stm32" } +embassy-sync = { version = "0.2.0", path = "../embassy-sync" } +embassy-time = { version = "0.1.2", path = "../embassy-time", optional = true } +embassy-futures = { version = "0.1.0", path = "../embassy-futures" } +embassy-hal-common = { version = "0.1.0", path = "../embassy-hal-common" } +embassy-embedded-hal = { version = "0.1.0", path = "../embassy-embedded-hal" } + +defmt = { version = "0.3", optional = true } +cortex-m = "0.7.6" +heapless = "0.7.16" +aligned = "0.4.1" + +bit_field = "0.10.2" +stm32-device-signature = { version = "0.3.3", features = ["stm32wb5x"] } +stm32wb-hci = { version = "0.1.3", optional = true } +bitflags = { version = "2.3.3", optional = true } + +[features] +defmt = ["dep:defmt", "embassy-sync/defmt", "embassy-embedded-hal/defmt", "embassy-hal-common/defmt", "stm32wb-hci?/defmt"] + +ble = ["dep:stm32wb-hci"] +mac = ["dep:bitflags"] + +stm32wb10cc = [ "embassy-stm32/stm32wb10cc" ] +stm32wb15cc = [ "embassy-stm32/stm32wb15cc" ] +stm32wb30ce = [ "embassy-stm32/stm32wb30ce" ] +stm32wb35cc = [ "embassy-stm32/stm32wb35cc" ] +stm32wb35ce = [ "embassy-stm32/stm32wb35ce" ] +stm32wb50cg = [ "embassy-stm32/stm32wb50cg" ] +stm32wb55cc = [ "embassy-stm32/stm32wb55cc" ] +stm32wb55ce = [ "embassy-stm32/stm32wb55ce" ] +stm32wb55cg = [ "embassy-stm32/stm32wb55cg" ] +stm32wb55rc = [ "embassy-stm32/stm32wb55rc" ] +stm32wb55re = [ "embassy-stm32/stm32wb55re" ] +stm32wb55rg = [ "embassy-stm32/stm32wb55rg" ] +stm32wb55vc = [ "embassy-stm32/stm32wb55vc" ] +stm32wb55ve = [ "embassy-stm32/stm32wb55ve" ] +stm32wb55vg = [ "embassy-stm32/stm32wb55vg" ] +stm32wb55vy = [ "embassy-stm32/stm32wb55vy" ] diff --git a/embassy-stm32-wpan/build.rs b/embassy-stm32-wpan/build.rs new file mode 100644 index 000000000..94aac070d --- /dev/null +++ b/embassy-stm32-wpan/build.rs @@ -0,0 +1,45 @@ +use std::path::PathBuf; +use std::{env, fs}; + +fn main() { + match env::vars() + .map(|(a, _)| a) + .filter(|x| x.starts_with("CARGO_FEATURE_STM32")) + .get_one() + { + Ok(_) => {} + Err(GetOneError::None) => panic!("No stm32xx Cargo feature enabled"), + Err(GetOneError::Multiple) => panic!("Multiple stm32xx Cargo features enabled"), + } + + let out_dir = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + + // ======== + // stm32wb tl_mbox link sections + + let out_file = out_dir.join("tl_mbox.x").to_string_lossy().to_string(); + fs::write(out_file, fs::read_to_string("tl_mbox.x.in").unwrap()).unwrap(); + println!("cargo:rustc-link-search={}", out_dir.display()); + println!("cargo:rerun-if-changed=tl_mbox.x.in"); +} + +enum GetOneError { + None, + Multiple, +} + +trait IteratorExt: Iterator { + fn get_one(self) -> Result; +} + +impl IteratorExt for T { + fn get_one(mut self) -> Result { + match self.next() { + None => Err(GetOneError::None), + Some(res) => match self.next() { + Some(_) => Err(GetOneError::Multiple), + None => Ok(res), + }, + } + } +} diff --git a/embassy-stm32-wpan/src/channels.rs b/embassy-stm32-wpan/src/channels.rs new file mode 100644 index 000000000..9a2be1cfa --- /dev/null +++ b/embassy-stm32-wpan/src/channels.rs @@ -0,0 +1,96 @@ +//! CPU1 CPU2 +//! | (SYSTEM) | +//! |----HW_IPCC_SYSTEM_CMD_RSP_CHANNEL-------------->| +//! | | +//! |<---HW_IPCC_SYSTEM_EVENT_CHANNEL-----------------| +//! | | +//! | (ZIGBEE) | +//! |----HW_IPCC_ZIGBEE_CMD_APPLI_CHANNEL------------>| +//! | | +//! |----HW_IPCC_ZIGBEE_CMD_CLI_CHANNEL-------------->| +//! | | +//! |<---HW_IPCC_ZIGBEE_APPLI_NOTIF_ACK_CHANNEL-------| +//! | | +//! |<---HW_IPCC_ZIGBEE_CLI_NOTIF_ACK_CHANNEL---------| +//! | | +//! | (THREAD) | +//! |----HW_IPCC_THREAD_OT_CMD_RSP_CHANNEL----------->| +//! | | +//! |----HW_IPCC_THREAD_CLI_CMD_CHANNEL-------------->| +//! | | +//! |<---HW_IPCC_THREAD_NOTIFICATION_ACK_CHANNEL------| +//! | | +//! |<---HW_IPCC_THREAD_CLI_NOTIFICATION_ACK_CHANNEL--| +//! | | +//! | (BLE) | +//! |----HW_IPCC_BLE_CMD_CHANNEL--------------------->| +//! | | +//! |----HW_IPCC_HCI_ACL_DATA_CHANNEL---------------->| +//! | | +//! |<---HW_IPCC_BLE_EVENT_CHANNEL--------------------| +//! | | +//! | (BLE LLD) | +//! |----HW_IPCC_BLE_LLD_CMD_CHANNEL----------------->| +//! | | +//! |<---HW_IPCC_BLE_LLD_RSP_CHANNEL------------------| +//! | | +//! |<---HW_IPCC_BLE_LLD_M0_CMD_CHANNEL---------------| +//! | | +//! | (MAC) | +//! |----HW_IPCC_MAC_802_15_4_CMD_RSP_CHANNEL-------->| +//! | | +//! |<---HW_IPCC_MAC_802_15_4_NOTIFICATION_ACK_CHANNEL| +//! | | +//! | (BUFFER) | +//! |----HW_IPCC_MM_RELEASE_BUFFER_CHANNE------------>| +//! | | +//! | (TRACE) | +//! |<----HW_IPCC_TRACES_CHANNEL----------------------| +//! | | +//! + +pub mod cpu1 { + use embassy_stm32::ipcc::IpccChannel; + + pub const IPCC_BLE_CMD_CHANNEL: IpccChannel = IpccChannel::Channel1; + pub const IPCC_SYSTEM_CMD_RSP_CHANNEL: IpccChannel = IpccChannel::Channel2; + pub const IPCC_THREAD_OT_CMD_RSP_CHANNEL: IpccChannel = IpccChannel::Channel3; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_ZIGBEE_CMD_APPLI_CHANNEL: IpccChannel = IpccChannel::Channel3; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_MAC_802_15_4_CMD_RSP_CHANNEL: IpccChannel = IpccChannel::Channel3; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_MM_RELEASE_BUFFER_CHANNEL: IpccChannel = IpccChannel::Channel4; + pub const IPCC_THREAD_CLI_CMD_CHANNEL: IpccChannel = IpccChannel::Channel5; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_LLDTESTS_CLI_CMD_CHANNEL: IpccChannel = IpccChannel::Channel5; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_BLE_LLD_CMD_CHANNEL: IpccChannel = IpccChannel::Channel5; + pub const IPCC_HCI_ACL_DATA_CHANNEL: IpccChannel = IpccChannel::Channel6; +} + +pub mod cpu2 { + use embassy_stm32::ipcc::IpccChannel; + + pub const IPCC_BLE_EVENT_CHANNEL: IpccChannel = IpccChannel::Channel1; + pub const IPCC_SYSTEM_EVENT_CHANNEL: IpccChannel = IpccChannel::Channel2; + pub const IPCC_THREAD_NOTIFICATION_ACK_CHANNEL: IpccChannel = IpccChannel::Channel3; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_ZIGBEE_APPLI_NOTIF_ACK_CHANNEL: IpccChannel = IpccChannel::Channel3; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_MAC_802_15_4_NOTIFICATION_ACK_CHANNEL: IpccChannel = IpccChannel::Channel3; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_LDDTESTS_M0_CMD_CHANNEL: IpccChannel = IpccChannel::Channel3; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_BLE_LLDÇM0_CMD_CHANNEL: IpccChannel = IpccChannel::Channel3; + pub const IPCC_TRACES_CHANNEL: IpccChannel = IpccChannel::Channel4; + pub const IPCC_THREAD_CLI_NOTIFICATION_ACK_CHANNEL: IpccChannel = IpccChannel::Channel5; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_LLDTESTS_CLI_RSP_CHANNEL: IpccChannel = IpccChannel::Channel5; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_BLE_LLD_CLI_RSP_CHANNEL: IpccChannel = IpccChannel::Channel5; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_BLE_LLD_RSP_CHANNEL: IpccChannel = IpccChannel::Channel5; + #[allow(dead_code)] // Not used currently but reserved + pub const IPCC_ZIGBEE_M0_REQUEST_CHANNEL: IpccChannel = IpccChannel::Channel5; +} diff --git a/embassy-stm32-wpan/src/cmd.rs b/embassy-stm32-wpan/src/cmd.rs new file mode 100644 index 000000000..928357384 --- /dev/null +++ b/embassy-stm32-wpan/src/cmd.rs @@ -0,0 +1,104 @@ +use core::ptr; + +use crate::consts::TlPacketType; +use crate::PacketHeader; + +#[derive(Copy, Clone)] +#[repr(C, packed)] +pub struct Cmd { + pub cmd_code: u16, + pub payload_len: u8, + pub payload: [u8; 255], +} + +impl Default for Cmd { + fn default() -> Self { + Self { + cmd_code: 0, + payload_len: 0, + payload: [0u8; 255], + } + } +} + +#[derive(Copy, Clone, Default)] +#[repr(C, packed)] +pub struct CmdSerial { + pub ty: u8, + pub cmd: Cmd, +} + +#[derive(Copy, Clone, Default)] +#[repr(C, packed)] +pub struct CmdSerialStub { + pub ty: u8, + pub cmd_code: u16, + pub payload_len: u8, +} + +#[derive(Copy, Clone, Default)] +#[repr(C, packed)] +pub struct CmdPacket { + pub header: PacketHeader, + pub cmdserial: CmdSerial, +} + +impl CmdPacket { + pub unsafe fn write_into(cmd_buf: *mut CmdPacket, packet_type: TlPacketType, cmd_code: u16, payload: &[u8]) { + let p_cmd_serial = &mut (*cmd_buf).cmdserial as *mut _ as *mut CmdSerialStub; + let p_payload = &mut (*cmd_buf).cmdserial.cmd.payload as *mut _; + + ptr::write_volatile( + p_cmd_serial, + CmdSerialStub { + ty: packet_type as u8, + cmd_code, + payload_len: payload.len() as u8, + }, + ); + + ptr::copy_nonoverlapping(payload as *const _ as *const u8, p_payload, payload.len()); + } +} + +#[derive(Copy, Clone)] +#[repr(C, packed)] +pub struct AclDataSerial { + pub ty: u8, + pub handle: u16, + pub length: u16, + pub acl_data: [u8; 1], +} + +#[derive(Copy, Clone)] +#[repr(C, packed)] +pub struct AclDataSerialStub { + pub ty: u8, + pub handle: u16, + pub length: u16, +} + +#[derive(Copy, Clone)] +#[repr(C, packed)] +pub struct AclDataPacket { + pub header: PacketHeader, + pub acl_data_serial: AclDataSerial, +} + +impl AclDataPacket { + pub unsafe fn write_into(cmd_buf: *mut AclDataPacket, packet_type: TlPacketType, handle: u16, payload: &[u8]) { + let p_cmd_serial = &mut (*cmd_buf).acl_data_serial as *mut _ as *mut AclDataSerialStub; + let p_payload = &mut (*cmd_buf).acl_data_serial.acl_data as *mut _; + + ptr::write_volatile( + p_cmd_serial, + AclDataSerialStub { + ty: packet_type as u8, + handle: handle, + length: payload.len() as u16, + }, + ); + + ptr::copy_nonoverlapping(payload as *const _ as *const u8, p_payload, payload.len()); + } +} diff --git a/embassy-stm32-wpan/src/consts.rs b/embassy-stm32-wpan/src/consts.rs new file mode 100644 index 000000000..bd70851ea --- /dev/null +++ b/embassy-stm32-wpan/src/consts.rs @@ -0,0 +1,96 @@ +use core::convert::TryFrom; + +use crate::evt::CsEvt; +use crate::PacketHeader; + +#[derive(Debug)] +#[repr(C)] +pub enum TlPacketType { + MacCmd = 0x00, + + BleCmd = 0x01, + AclData = 0x02, + BleEvt = 0x04, + + OtCmd = 0x08, + OtRsp = 0x09, + CliCmd = 0x0A, + OtNot = 0x0C, + OtAck = 0x0D, + CliNot = 0x0E, + CliAck = 0x0F, + + SysCmd = 0x10, + SysRsp = 0x11, + SysEvt = 0x12, + + LocCmd = 0x20, + LocRsp = 0x21, + + TracesApp = 0x40, + TracesWl = 0x41, +} + +impl TryFrom for TlPacketType { + type Error = (); + + fn try_from(value: u8) -> Result { + match value { + 0x01 => Ok(TlPacketType::BleCmd), + 0x02 => Ok(TlPacketType::AclData), + 0x04 => Ok(TlPacketType::BleEvt), + 0x08 => Ok(TlPacketType::OtCmd), + 0x09 => Ok(TlPacketType::OtRsp), + 0x0A => Ok(TlPacketType::CliCmd), + 0x0C => Ok(TlPacketType::OtNot), + 0x0D => Ok(TlPacketType::OtAck), + 0x0E => Ok(TlPacketType::CliNot), + 0x0F => Ok(TlPacketType::CliAck), + 0x10 => Ok(TlPacketType::SysCmd), + 0x11 => Ok(TlPacketType::SysRsp), + 0x12 => Ok(TlPacketType::SysEvt), + 0x20 => Ok(TlPacketType::LocCmd), + 0x21 => Ok(TlPacketType::LocRsp), + 0x40 => Ok(TlPacketType::TracesApp), + 0x41 => Ok(TlPacketType::TracesWl), + + _ => Err(()), + } + } +} + +pub const TL_PACKET_HEADER_SIZE: usize = core::mem::size_of::(); +pub const TL_EVT_HEADER_SIZE: usize = 3; +pub const TL_CS_EVT_SIZE: usize = core::mem::size_of::(); + +/** + * Queue length of BLE Event + * This parameter defines the number of asynchronous events that can be stored in the HCI layer before + * being reported to the application. When a command is sent to the BLE core coprocessor, the HCI layer + * is waiting for the event with the Num_HCI_Command_Packets set to 1. The receive queue shall be large + * enough to store all asynchronous events received in between. + * When CFG_TLBLE_MOST_EVENT_PAYLOAD_SIZE is set to 27, this allow to store three 255 bytes long asynchronous events + * between the HCI command and its event. + * This parameter depends on the value given to CFG_TLBLE_MOST_EVENT_PAYLOAD_SIZE. When the queue size is to small, + * the system may hang if the queue is full with asynchronous events and the HCI layer is still waiting + * for a CC/CS event, In that case, the notification TL_BLE_HCI_ToNot() is called to indicate + * to the application a HCI command did not receive its command event within 30s (Default HCI Timeout). + */ +pub const CFG_TL_BLE_EVT_QUEUE_LENGTH: usize = 5; +pub const CFG_TL_BLE_MOST_EVENT_PAYLOAD_SIZE: usize = 255; +pub const TL_BLE_EVENT_FRAME_SIZE: usize = TL_EVT_HEADER_SIZE + CFG_TL_BLE_MOST_EVENT_PAYLOAD_SIZE; + +pub const POOL_SIZE: usize = CFG_TL_BLE_EVT_QUEUE_LENGTH * 4 * divc(TL_PACKET_HEADER_SIZE + TL_BLE_EVENT_FRAME_SIZE, 4); +pub const C_SIZE_CMD_STRING: usize = 256; + +pub const fn divc(x: usize, y: usize) -> usize { + (x + y - 1) / y +} + +pub const TL_BLE_EVT_CS_PACKET_SIZE: usize = TL_EVT_HEADER_SIZE + TL_CS_EVT_SIZE; +#[allow(dead_code)] +pub const TL_BLE_EVT_CS_BUFFER_SIZE: usize = TL_PACKET_HEADER_SIZE + TL_BLE_EVT_CS_PACKET_SIZE; + +pub const TL_BLEEVT_CC_OPCODE: u8 = 0x0E; +pub const TL_BLEEVT_CS_OPCODE: u8 = 0x0F; +pub const TL_BLEEVT_VS_OPCODE: u8 = 0xFF; diff --git a/embassy-stm32-wpan/src/evt.rs b/embassy-stm32-wpan/src/evt.rs new file mode 100644 index 000000000..c6528413d --- /dev/null +++ b/embassy-stm32-wpan/src/evt.rs @@ -0,0 +1,151 @@ +use core::marker::PhantomData; +use core::{ptr, slice}; + +use super::PacketHeader; +use crate::consts::TL_EVT_HEADER_SIZE; + +/** + * The payload of `Evt` for a command status event + */ +#[derive(Copy, Clone)] +#[repr(C, packed)] +pub struct CsEvt { + pub status: u8, + pub num_cmd: u8, + pub cmd_code: u16, +} + +/** + * The payload of `Evt` for a command complete event + */ +#[derive(Copy, Clone, Default)] +#[repr(C, packed)] +pub struct CcEvt { + pub num_cmd: u8, + pub cmd_code: u16, + pub payload: [u8; 1], +} + +impl CcEvt { + pub fn write(&self, buf: &mut [u8]) { + unsafe { + let len = core::mem::size_of::(); + assert!(buf.len() >= len); + + let self_ptr: *const CcEvt = self; + let self_buf_ptr: *const u8 = self_ptr.cast(); + + core::ptr::copy(self_buf_ptr, buf.as_mut_ptr(), len); + } + } +} + +#[derive(Copy, Clone, Default)] +#[repr(C, packed)] +pub struct AsynchEvt { + sub_evt_code: u16, + payload: [u8; 1], +} + +#[derive(Copy, Clone)] +#[repr(C, packed)] +pub struct Evt { + pub evt_code: u8, + pub payload_len: u8, + pub payload: [u8; 255], +} + +#[derive(Copy, Clone)] +#[repr(C, packed)] +pub struct EvtSerial { + pub kind: u8, + pub evt: Evt, +} + +#[derive(Copy, Clone, Default)] +#[repr(C, packed)] +pub struct EvtStub { + pub kind: u8, + pub evt_code: u8, +} + +/// This format shall be used for all events (asynchronous and command response) reported +/// by the CPU2 except for the command response of a system command where the header is not there +/// and the format to be used shall be `EvtSerial`. +/// +/// ### Note: +/// Be careful that the asynchronous events reported by the CPU2 on the system channel do +/// include the header and shall use `EvtPacket` format. Only the command response format on the +/// system channel is different. +#[derive(Copy, Clone)] +#[repr(C, packed)] +pub struct EvtPacket { + pub header: PacketHeader, + pub evt_serial: EvtSerial, +} + +impl EvtPacket { + pub fn kind(&self) -> u8 { + self.evt_serial.kind + } + + pub fn evt(&self) -> &Evt { + &self.evt_serial.evt + } +} + +pub trait MemoryManager { + unsafe fn drop_event_packet(evt: *mut EvtPacket); +} + +/// smart pointer to the [`EvtPacket`] that will dispose of [`EvtPacket`] buffer automatically +/// on [`Drop`] +#[derive(Debug)] +pub struct EvtBox { + ptr: *mut EvtPacket, + mm: PhantomData, +} + +unsafe impl Send for EvtBox {} +impl EvtBox { + pub(super) fn new(ptr: *mut EvtPacket) -> Self { + Self { ptr, mm: PhantomData } + } + + /// Returns information about the event + pub fn stub(&self) -> EvtStub { + unsafe { + let p_evt_stub = &(*self.ptr).evt_serial as *const _ as *const EvtStub; + + ptr::read_volatile(p_evt_stub) + } + } + + pub fn payload<'a>(&'a self) -> &'a [u8] { + unsafe { + let p_payload_len = &(*self.ptr).evt_serial.evt.payload_len as *const u8; + let p_payload = &(*self.ptr).evt_serial.evt.payload as *const u8; + + let payload_len = ptr::read_volatile(p_payload_len); + + slice::from_raw_parts(p_payload, payload_len as usize) + } + } + + pub fn serial<'a>(&'a self) -> &'a [u8] { + unsafe { + let evt_serial: *const EvtSerial = &(*self.ptr).evt_serial; + let evt_serial_buf: *const u8 = evt_serial.cast(); + + let len = (*evt_serial).evt.payload_len as usize + TL_EVT_HEADER_SIZE; + + slice::from_raw_parts(evt_serial_buf, len) + } + } +} + +impl Drop for EvtBox { + fn drop(&mut self) { + unsafe { T::drop_event_packet(self.ptr) }; + } +} diff --git a/embassy-usb-serial/src/fmt.rs b/embassy-stm32-wpan/src/fmt.rs similarity index 100% rename from embassy-usb-serial/src/fmt.rs rename to embassy-stm32-wpan/src/fmt.rs diff --git a/embassy-stm32-wpan/src/lhci.rs b/embassy-stm32-wpan/src/lhci.rs new file mode 100644 index 000000000..89f204f99 --- /dev/null +++ b/embassy-stm32-wpan/src/lhci.rs @@ -0,0 +1,112 @@ +use core::ptr; + +use crate::cmd::CmdPacket; +use crate::consts::{TlPacketType, TL_EVT_HEADER_SIZE}; +use crate::evt::{CcEvt, EvtPacket, EvtSerial}; +use crate::tables::{DeviceInfoTable, RssInfoTable, SafeBootInfoTable, WirelessFwInfoTable, TL_DEVICE_INFO_TABLE}; + +const TL_BLEEVT_CC_OPCODE: u8 = 0x0e; +const LHCI_OPCODE_C1_DEVICE_INF: u16 = 0xfd62; + +const PACKAGE_DATA_PTR: *const u8 = 0x1FFF_7500 as _; +const UID64_PTR: *const u32 = 0x1FFF_7580 as _; + +#[derive(Debug, Copy, Clone)] +#[repr(C, packed)] +pub struct LhciC1DeviceInformationCcrp { + pub status: u8, + pub rev_id: u16, + pub dev_code_id: u16, + pub package_type: u8, + pub device_type_id: u8, + pub st_company_id: u32, + pub uid64: u32, + + pub uid96_0: u32, + pub uid96_1: u32, + pub uid96_2: u32, + + pub safe_boot_info_table: SafeBootInfoTable, + pub rss_info_table: RssInfoTable, + pub wireless_fw_info_table: WirelessFwInfoTable, + + pub app_fw_inf: u32, +} + +impl Default for LhciC1DeviceInformationCcrp { + fn default() -> Self { + let DeviceInfoTable { + safe_boot_info_table, + rss_info_table, + wireless_fw_info_table, + } = unsafe { ptr::read_volatile(TL_DEVICE_INFO_TABLE.as_ptr()) }; + + let device_id = stm32_device_signature::device_id(); + let uid96_0 = (device_id[3] as u32) << 24 + | (device_id[2] as u32) << 16 + | (device_id[1] as u32) << 8 + | device_id[0] as u32; + let uid96_1 = (device_id[7] as u32) << 24 + | (device_id[6] as u32) << 16 + | (device_id[5] as u32) << 8 + | device_id[4] as u32; + let uid96_2 = (device_id[11] as u32) << 24 + | (device_id[10] as u32) << 16 + | (device_id[9] as u32) << 8 + | device_id[8] as u32; + + let package_type = unsafe { *PACKAGE_DATA_PTR }; + let uid64 = unsafe { *UID64_PTR }; + let st_company_id = unsafe { *UID64_PTR.offset(1) } >> 8 & 0x00FF_FFFF; + let device_type_id = (unsafe { *UID64_PTR.offset(1) } & 0x000000FF) as u8; + + LhciC1DeviceInformationCcrp { + status: 0, + rev_id: 0, + dev_code_id: 0, + package_type, + device_type_id, + st_company_id, + uid64, + uid96_0, + uid96_1, + uid96_2, + safe_boot_info_table, + rss_info_table, + wireless_fw_info_table, + app_fw_inf: (1 << 8), // 0.0.1 + } + } +} + +impl LhciC1DeviceInformationCcrp { + pub fn new() -> Self { + Self::default() + } + + pub fn write(&self, cmd_packet: &mut CmdPacket) { + let self_size = core::mem::size_of::(); + + unsafe { + let cmd_packet_ptr: *mut CmdPacket = cmd_packet; + let evet_packet_ptr: *mut EvtPacket = cmd_packet_ptr.cast(); + + let evt_serial: *mut EvtSerial = &mut (*evet_packet_ptr).evt_serial; + let evt_payload = (*evt_serial).evt.payload.as_mut_ptr(); + let evt_cc: *mut CcEvt = evt_payload.cast(); + let evt_cc_payload_buf = (*evt_cc).payload.as_mut_ptr(); + + (*evt_serial).kind = TlPacketType::LocRsp as u8; + (*evt_serial).evt.evt_code = TL_BLEEVT_CC_OPCODE; + (*evt_serial).evt.payload_len = TL_EVT_HEADER_SIZE as u8 + self_size as u8; + + (*evt_cc).cmd_code = LHCI_OPCODE_C1_DEVICE_INF; + (*evt_cc).num_cmd = 1; + + let self_ptr: *const LhciC1DeviceInformationCcrp = self; + let self_buf = self_ptr.cast(); + + ptr::copy(self_buf, evt_cc_payload_buf, self_size); + } + } +} diff --git a/embassy-stm32-wpan/src/lib.rs b/embassy-stm32-wpan/src/lib.rs new file mode 100644 index 000000000..57f0dc4fa --- /dev/null +++ b/embassy-stm32-wpan/src/lib.rs @@ -0,0 +1,152 @@ +#![no_std] +#![cfg_attr(feature = "ble", feature(async_fn_in_trait))] + +// This must go FIRST so that all the other modules see its macros. +pub mod fmt; + +use core::mem::MaybeUninit; +use core::sync::atomic::{compiler_fence, Ordering}; + +use embassy_hal_common::{into_ref, Peripheral, PeripheralRef}; +use embassy_stm32::interrupt; +use embassy_stm32::ipcc::{Config, Ipcc, ReceiveInterruptHandler, TransmitInterruptHandler}; +use embassy_stm32::peripherals::IPCC; +use sub::mm::MemoryManager; +use sub::sys::Sys; +use tables::*; +use unsafe_linked_list::LinkedListNode; + +pub mod channels; +pub mod cmd; +pub mod consts; +pub mod evt; +pub mod lhci; +pub mod shci; +pub mod sub; +pub mod tables; +pub mod unsafe_linked_list; + +#[cfg(feature = "mac")] +pub mod mac; + +#[cfg(feature = "ble")] +pub use crate::sub::ble::hci; + +type PacketHeader = LinkedListNode; + +pub struct TlMbox<'d> { + _ipcc: PeripheralRef<'d, IPCC>, + + pub sys_subsystem: Sys, + pub mm_subsystem: MemoryManager, + #[cfg(feature = "ble")] + pub ble_subsystem: sub::ble::Ble, + #[cfg(feature = "mac")] + pub mac_subsystem: sub::mac::Mac, +} + +impl<'d> TlMbox<'d> { + pub fn init( + ipcc: impl Peripheral

+ 'd, + _irqs: impl interrupt::typelevel::Binding + + interrupt::typelevel::Binding, + config: Config, + ) -> Self { + into_ref!(ipcc); + + unsafe { + TL_REF_TABLE.as_mut_ptr().write_volatile(RefTable { + device_info_table: TL_DEVICE_INFO_TABLE.as_ptr(), + ble_table: TL_BLE_TABLE.as_ptr(), + thread_table: TL_THREAD_TABLE.as_ptr(), + sys_table: TL_SYS_TABLE.as_ptr(), + mem_manager_table: TL_MEM_MANAGER_TABLE.as_ptr(), + traces_table: TL_TRACES_TABLE.as_ptr(), + mac_802_15_4_table: TL_MAC_802_15_4_TABLE.as_ptr(), + zigbee_table: TL_ZIGBEE_TABLE.as_ptr(), + lld_tests_table: TL_LLD_TESTS_TABLE.as_ptr(), + ble_lld_table: TL_BLE_LLD_TABLE.as_ptr(), + }); + + TL_SYS_TABLE + .as_mut_ptr() + .write_volatile(MaybeUninit::zeroed().assume_init()); + TL_DEVICE_INFO_TABLE + .as_mut_ptr() + .write_volatile(MaybeUninit::zeroed().assume_init()); + TL_BLE_TABLE + .as_mut_ptr() + .write_volatile(MaybeUninit::zeroed().assume_init()); + TL_THREAD_TABLE + .as_mut_ptr() + .write_volatile(MaybeUninit::zeroed().assume_init()); + TL_MEM_MANAGER_TABLE + .as_mut_ptr() + .write_volatile(MaybeUninit::zeroed().assume_init()); + + TL_TRACES_TABLE + .as_mut_ptr() + .write_volatile(MaybeUninit::zeroed().assume_init()); + TL_MAC_802_15_4_TABLE + .as_mut_ptr() + .write_volatile(MaybeUninit::zeroed().assume_init()); + TL_ZIGBEE_TABLE + .as_mut_ptr() + .write_volatile(MaybeUninit::zeroed().assume_init()); + TL_LLD_TESTS_TABLE + .as_mut_ptr() + .write_volatile(MaybeUninit::zeroed().assume_init()); + TL_BLE_LLD_TABLE + .as_mut_ptr() + .write_volatile(MaybeUninit::zeroed().assume_init()); + + EVT_POOL + .as_mut_ptr() + .write_volatile(MaybeUninit::zeroed().assume_init()); + SYS_SPARE_EVT_BUF + .as_mut_ptr() + .write_volatile(MaybeUninit::zeroed().assume_init()); + CS_BUFFER + .as_mut_ptr() + .write_volatile(MaybeUninit::zeroed().assume_init()); + + #[cfg(feature = "ble")] + { + BLE_SPARE_EVT_BUF + .as_mut_ptr() + .write_volatile(MaybeUninit::zeroed().assume_init()); + + BLE_CMD_BUFFER + .as_mut_ptr() + .write_volatile(MaybeUninit::zeroed().assume_init()); + HCI_ACL_DATA_BUFFER + .as_mut_ptr() + .write_volatile(MaybeUninit::zeroed().assume_init()); + } + + #[cfg(feature = "mac")] + { + MAC_802_15_4_CMD_BUFFER + .as_mut_ptr() + .write_volatile(MaybeUninit::zeroed().assume_init()); + MAC_802_15_4_NOTIF_RSP_EVT_BUFFER + .as_mut_ptr() + .write_volatile(MaybeUninit::zeroed().assume_init()); + } + } + + compiler_fence(Ordering::SeqCst); + + Ipcc::enable(config); + + Self { + _ipcc: ipcc, + sys_subsystem: sub::sys::Sys::new(), + #[cfg(feature = "ble")] + ble_subsystem: sub::ble::Ble::new(), + #[cfg(feature = "mac")] + mac_subsystem: sub::mac::Mac::new(), + mm_subsystem: sub::mm::MemoryManager::new(), + } + } +} diff --git a/embassy-stm32-wpan/src/mac/commands.rs b/embassy-stm32-wpan/src/mac/commands.rs new file mode 100644 index 000000000..8f6dcbbbc --- /dev/null +++ b/embassy-stm32-wpan/src/mac/commands.rs @@ -0,0 +1,476 @@ +use core::{mem, slice}; + +use super::opcodes::OpcodeM4ToM0; +use super::typedefs::{ + AddressMode, Capabilities, DisassociationReason, GtsCharacteristics, KeyIdMode, MacAddress, MacChannel, MacStatus, + PanId, PibId, ScanType, SecurityLevel, +}; + +pub trait MacCommand: Sized { + const OPCODE: OpcodeM4ToM0; + + fn payload<'a>(&'a self) -> &'a [u8] { + unsafe { slice::from_raw_parts(self as *const _ as *const u8, mem::size_of::()) } + } +} + +/// MLME ASSOCIATE Request used to request an association +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AssociateRequest { + /// the logical channel on which to attempt association + pub channel_number: MacChannel, + /// the channel page on which to attempt association + pub channel_page: u8, + /// coordinator addressing mode + pub coord_addr_mode: AddressMode, + /// operational capabilities of the associating device + pub capability_information: Capabilities, + /// the identifier of the PAN with which to associate + pub coord_pan_id: PanId, + /// the security level to be used + pub security_level: SecurityLevel, + /// the mode used to identify the key to be used + pub key_id_mode: KeyIdMode, + /// the originator of the key to be used + pub key_source: [u8; 8], + /// Coordinator address + pub coord_address: MacAddress, + /// the index of the key to be used + pub key_index: u8, +} + +impl MacCommand for AssociateRequest { + const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeAssociateReq; +} + +/// MLME DISASSOCIATE Request sed to request a disassociation +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DisassociateRequest { + /// device addressing mode used + pub device_addr_mode: AddressMode, + /// the identifier of the PAN of the device + pub device_pan_id: PanId, + /// the reason for the disassociation + pub disassociation_reason: DisassociationReason, + /// device address + pub device_address: MacAddress, + /// `true` if the disassociation notification command is to be sent indirectly + pub tx_indirect: bool, + /// the security level to be used + pub security_level: SecurityLevel, + /// the mode to be used to indetify the key to be used + pub key_id_mode: KeyIdMode, + /// the index of the key to be used + pub key_index: u8, + /// the originator of the key to be used + pub key_source: [u8; 8], +} + +impl MacCommand for DisassociateRequest { + const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeDisassociateReq; +} + +/// MLME GET Request used to request a PIB value +#[repr(C)] +#[derive(Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct GetRequest { + /// the name of the PIB attribute to read + pub pib_attribute: PibId, + + /// byte stuffing to keep 32 bit alignment + pub a_stuffing: [u8; 3], +} + +impl MacCommand for GetRequest { + const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeGetReq; +} + +/// MLME GTS Request used to request and maintain GTSs +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct GtsRequest { + /// the characteristics of the GTS + pub characteristics: GtsCharacteristics, + /// the security level to be used + pub security_level: SecurityLevel, + /// the mode used to identify the key to be used + pub key_id_mode: KeyIdMode, + /// the index of the key to be used + pub key_index: u8, + /// the originator of the key to be used + pub key_source: [u8; 8], +} + +impl MacCommand for GtsRequest { + const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeGetReq; +} + +#[repr(C)] +#[derive(Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ResetRequest { + /// MAC PIB attributes are set to their default values or not during reset + pub set_default_pib: bool, + /// byte stuffing to keep 32 bit alignment + pub a_stuffing: [u8; 3], +} + +impl MacCommand for ResetRequest { + const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeResetReq; +} + +/// MLME RX ENABLE Request used to request that the receiver is either enabled +/// for a finite period of time or disabled +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct RxEnableRequest { + /// the request operation can be deferred or not + pub defer_permit: bool, + /// configure the transceiver to RX with ranging for a value of + /// RANGING_ON or to not enable ranging for RANGING_OFF + pub ranging_rx_control: u8, + /// byte stuffing to keep 32 bit alignment + pub a_stuffing: [u8; 2], + /// number of symbols measured before the receiver is to be enabled or disabled + pub rx_on_time: [u8; 4], + /// number of symbols for which the receiver is to be enabled + pub rx_on_duration: [u8; 4], +} + +impl MacCommand for RxEnableRequest { + const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeRxEnableReq; +} + +/// MLME SCAN Request used to initiate a channel scan over a given list of channels +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ScanRequest { + /// the type of scan to be performed + pub scan_type: ScanType, + /// the time spent on scanning each channel + pub scan_duration: u8, + /// channel page on which to perform the scan + pub channel_page: u8, + /// security level to be used + pub security_level: SecurityLevel, + /// indicate which channels are to be scanned + pub scan_channels: [u8; 4], + /// originator the key to be used + pub key_source: [u8; 8], + /// mode used to identify the key to be used + pub key_id_mode: KeyIdMode, + /// index of the key to be used + pub key_index: u8, + /// byte stuffing to keep 32 bit alignment + pub a_stuffing: [u8; 2], +} + +impl MacCommand for ScanRequest { + const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeScanReq; +} + +/// MLME SET Request used to attempt to write the given value to the indicated PIB attribute +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SetRequest { + /// the pointer to the value of the PIB attribute to set + pub pib_attribute_ptr: *const u8, + /// the name of the PIB attribute to set + pub pib_attribute: PibId, +} + +impl MacCommand for SetRequest { + const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeSetReq; +} + +/// MLME START Request used by the FFDs to intiate a new PAN or to begin using a new superframe +/// configuration +#[repr(C)] +#[derive(Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct StartRequest { + /// PAN indentifier to used by the device + pub pan_id: PanId, + /// logical channel on which to begin + pub channel_number: MacChannel, + /// channel page on which to begin + pub channel_page: u8, + /// time at which to begin transmitting beacons + pub start_time: [u8; 4], + /// indicated how often the beacon is to be transmitted + pub beacon_order: u8, + /// length of the active portion of the superframe + pub superframe_order: u8, + /// indicated wheter the device is a PAN coordinator or not + pub pan_coordinator: bool, + /// indicates if the receiver of the beaconing device is disabled or not + pub battery_life_extension: bool, + /// indicated if the coordinator realignment command is to be trasmitted + pub coord_realignment: u8, + /// indicated if the coordinator realignment command is to be trasmitted + pub coord_realign_security_level: SecurityLevel, + /// index of the key to be used + pub coord_realign_key_id_index: u8, + /// originator of the key to be used + pub coord_realign_key_source: [u8; 8], + /// security level to be used for beacon frames + pub beacon_security_level: SecurityLevel, + /// mode used to identify the key to be used + pub beacon_key_id_mode: KeyIdMode, + /// index of the key to be used + pub beacon_key_index: u8, + /// originator of the key to be used + pub beacon_key_source: [u8; 8], +} + +impl MacCommand for StartRequest { + const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeStartReq; +} + +/// MLME SYNC Request used to synchronize with the coordinator by acquiring and, if +/// specified, tracking its beacons +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SyncRequest { + /// the channel number on which to attempt coordinator synchronization + pub channel_number: MacChannel, + /// the channel page on which to attempt coordinator synchronization + pub channel_page: u8, + /// `true` if the MLME is to synchronize with the next beacon and attempts + /// to track all future beacons. + /// + /// `false` if the MLME is to synchronize with only the next beacon + pub track_beacon: bool, + /// byte stuffing to keep 32 bit alignment + pub a_stuffing: [u8; 1], +} + +impl MacCommand for SyncRequest { + const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeSyncReq; +} + +/// MLME POLL Request propmts the device to request data from the coordinator +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PollRequest { + /// addressing mode of the coordinator + pub coord_addr_mode: AddressMode, + /// security level to be used + pub security_level: SecurityLevel, + /// mode used to identify the key to be used + pub key_id_mode: KeyIdMode, + /// index of the key to be used + pub key_index: u8, + /// coordinator address + pub coord_address: MacAddress, + /// originator of the key to be used + pub key_source: [u8; 8], + /// PAN identifier of the coordinator + pub coord_pan_id: PanId, + /// byte stuffing to keep 32 bit alignment + pub a_stuffing: [u8; 2], +} + +impl MacCommand for PollRequest { + const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmePollReq; +} + +/// MLME DPS Request allows the next higher layer to request that the PHY utilize a +/// given pair of preamble codes for a single use pending expiration of the DPSIndexDuration +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DpsRequest { + /// the index value for the transmitter + tx_dps_index: u8, + /// the index value of the receiver + rx_dps_index: u8, + /// the number of symbols for which the transmitter and receiver will utilize the + /// respective DPS indices + dps_index_duration: u8, + /// byte stuffing to keep 32 bit alignment + pub a_stuffing: [u8; 1], +} + +impl MacCommand for DpsRequest { + const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeDpsReq; +} + +/// MLME SOUNDING request primitive which is used by the next higher layer to request that +/// the PHY respond with channel sounding information +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SoundingRequest { + /// byte stuffing to keep 32 bit alignment + pub a_stuffing: [u8; 4], +} + +impl MacCommand for SoundingRequest { + const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeSoundingReq; +} + +/// MLME CALIBRATE request primitive which used to obtain the results of a ranging +/// calibration request from an RDEV +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CalibrateRequest { + /// byte stuffing to keep 32 bit alignment + pub a_stuffing: [u8; 4], +} + +impl MacCommand for CalibrateRequest { + const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeCalibrateReq; +} + +/// MCPS DATA Request used for MAC data related requests from the application +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DataRequest { + /// the handle assocated with the MSDU to be transmitted + pub msdu_ptr: *const u8, + /// source addressing mode used + pub src_addr_mode: AddressMode, + /// destination addressing mode used + pub dst_addr_mode: AddressMode, + /// destination PAN Id + pub dst_pan_id: PanId, + /// destination address + pub dst_address: MacAddress, + /// the number of octets contained in the MSDU + pub msdu_length: u8, + /// the handle assocated with the MSDU to be transmitted + pub msdu_handle: u8, + /// the ACK transmittion options for the MSDU + pub ack_tx: u8, + /// `true` if a GTS is to be used for transmission + /// + /// `false` indicates that the CAP will be used + pub gts_tx: bool, + /// the pending bit transmission options for the MSDU + pub indirect_tx: u8, + /// the security level to be used + pub security_level: SecurityLevel, + /// the mode used to indentify the key to be used + pub key_id_mode: KeyIdMode, + /// the index of the key to be used + pub key_index: u8, + /// the originator of the key to be used + pub key_source: [u8; 8], + /// 2011 - the pulse repitition value + pub uwbprf: u8, + /// 2011 - the ranging configuration + pub ranging: u8, + /// 2011 - the preamble symbol repititions + pub uwb_preamble_symbol_repetitions: u8, + /// 2011 - indicates the data rate + pub datrate: u8, +} + +impl DataRequest { + pub fn set_buffer<'a>(&'a mut self, buf: &'a [u8]) -> &mut Self { + self.msdu_ptr = buf as *const _ as *const u8; + self.msdu_length = buf.len() as u8; + + self + } +} + +impl Default for DataRequest { + fn default() -> Self { + Self { + msdu_ptr: 0 as *const u8, + src_addr_mode: AddressMode::NoAddress, + dst_addr_mode: AddressMode::NoAddress, + dst_pan_id: PanId([0, 0]), + dst_address: MacAddress { short: [0, 0] }, + msdu_length: 0, + msdu_handle: 0, + ack_tx: 0, + gts_tx: false, + indirect_tx: 0, + security_level: SecurityLevel::Unsecure, + key_id_mode: KeyIdMode::Implicite, + key_index: 0, + key_source: [0u8; 8], + uwbprf: 0, + ranging: 0, + uwb_preamble_symbol_repetitions: 0, + datrate: 0, + } + } +} + +impl MacCommand for DataRequest { + const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::McpsDataReq; +} + +/// for MCPS PURGE Request used to purge an MSDU from the transaction queue +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PurgeRequest { + /// the handle associated with the MSDU to be purged from the transaction + /// queue + pub msdu_handle: u8, + /// byte stuffing to keep 32 bit alignment + pub a_stuffing: [u8; 3], +} + +impl MacCommand for PurgeRequest { + const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::McpsPurgeReq; +} + +/// MLME ASSOCIATE Response used to initiate a response to an MLME-ASSOCIATE.indication +#[repr(C)] +#[derive(Default)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AssociateResponse { + /// extended address of the device requesting association + pub device_address: [u8; 8], + /// 16-bitshort device address allocated by the coordinator on successful + /// association + pub assoc_short_address: [u8; 2], + /// status of the association attempt + pub status: MacStatus, + /// security level to be used + pub security_level: SecurityLevel, + /// the originator of the key to be used + pub key_source: [u8; 8], + /// the mode used to identify the key to be used + pub key_id_mode: KeyIdMode, + /// the index of the key to be used + pub key_index: u8, + /// byte stuffing to keep 32 bit alignment + pub a_stuffing: [u8; 2], +} + +impl MacCommand for AssociateResponse { + const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeAssociateRes; +} + +/// MLME ORPHAN Response used to respond to the MLME ORPHAN Indication +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct OrphanResponse { + /// extended address of the orphaned device + pub orphan_address: [u8; 8], + /// short address allocated to the orphaned device + pub short_address: [u8; 2], + /// if the orphaned device is associated with coordinator or not + pub associated_member: bool, + /// security level to be used + pub security_level: SecurityLevel, + /// the originator of the key to be used + pub key_source: [u8; 8], + /// the mode used to identify the key to be used + pub key_id_mode: KeyIdMode, + /// the index of the key to be used + pub key_index: u8, + /// byte stuffing to keep 32 bit alignment + pub a_stuffing: [u8; 2], +} + +impl MacCommand for OrphanResponse { + const OPCODE: OpcodeM4ToM0 = OpcodeM4ToM0::MlmeOrphanRes; +} diff --git a/embassy-stm32-wpan/src/mac/consts.rs b/embassy-stm32-wpan/src/mac/consts.rs new file mode 100644 index 000000000..56903d980 --- /dev/null +++ b/embassy-stm32-wpan/src/mac/consts.rs @@ -0,0 +1,4 @@ +pub const MAX_PAN_DESC_SUPPORTED: usize = 6; +pub const MAX_SOUNDING_LIST_SUPPORTED: usize = 6; +pub const MAX_PENDING_ADDRESS: usize = 7; +pub const MAX_ED_SCAN_RESULTS_SUPPORTED: usize = 16; diff --git a/embassy-stm32-wpan/src/mac/event.rs b/embassy-stm32-wpan/src/mac/event.rs new file mode 100644 index 000000000..a2bb79222 --- /dev/null +++ b/embassy-stm32-wpan/src/mac/event.rs @@ -0,0 +1,104 @@ +use core::mem; + +use super::indications::{ + AssociateIndication, BeaconNotifyIndication, CommStatusIndication, DataIndication, DisassociateIndication, + DpsIndication, GtsIndication, OrphanIndication, PollIndication, SyncLossIndication, +}; +use super::responses::{ + AssociateConfirm, CalibrateConfirm, DataConfirm, DisassociateConfirm, DpsConfirm, GetConfirm, GtsConfirm, + PollConfirm, PurgeConfirm, ResetConfirm, RxEnableConfirm, ScanConfirm, SetConfirm, SoundingConfirm, StartConfirm, +}; +use crate::evt::EvtBox; +use crate::mac::opcodes::OpcodeM0ToM4; +use crate::sub::mac::Mac; + +pub(crate) trait ParseableMacEvent: Sized { + fn from_buffer<'a>(buf: &'a [u8]) -> Result<&'a Self, ()> { + if buf.len() < mem::size_of::() { + Err(()) + } else { + Ok(unsafe { &*(buf as *const _ as *const Self) }) + } + } +} + +pub struct Event { + event_box: EvtBox, +} + +impl Event { + pub(crate) fn new(event_box: EvtBox) -> Self { + Self { event_box } + } + + pub fn mac_event<'a>(&'a self) -> Result, ()> { + let payload = self.event_box.payload(); + let opcode = u16::from_le_bytes(payload[0..2].try_into().unwrap()); + + let opcode = OpcodeM0ToM4::try_from(opcode)?; + let buf = &payload[2..]; + + match opcode { + OpcodeM0ToM4::MlmeAssociateCnf => Ok(MacEvent::MlmeAssociateCnf(AssociateConfirm::from_buffer(buf)?)), + OpcodeM0ToM4::MlmeDisassociateCnf => { + Ok(MacEvent::MlmeDisassociateCnf(DisassociateConfirm::from_buffer(buf)?)) + } + OpcodeM0ToM4::MlmeGetCnf => Ok(MacEvent::MlmeGetCnf(GetConfirm::from_buffer(buf)?)), + OpcodeM0ToM4::MlmeGtsCnf => Ok(MacEvent::MlmeGtsCnf(GtsConfirm::from_buffer(buf)?)), + OpcodeM0ToM4::MlmeResetCnf => Ok(MacEvent::MlmeResetCnf(ResetConfirm::from_buffer(buf)?)), + OpcodeM0ToM4::MlmeRxEnableCnf => Ok(MacEvent::MlmeRxEnableCnf(RxEnableConfirm::from_buffer(buf)?)), + OpcodeM0ToM4::MlmeScanCnf => Ok(MacEvent::MlmeScanCnf(ScanConfirm::from_buffer(buf)?)), + OpcodeM0ToM4::MlmeSetCnf => Ok(MacEvent::MlmeSetCnf(SetConfirm::from_buffer(buf)?)), + OpcodeM0ToM4::MlmeStartCnf => Ok(MacEvent::MlmeStartCnf(StartConfirm::from_buffer(buf)?)), + OpcodeM0ToM4::MlmePollCnf => Ok(MacEvent::MlmePollCnf(PollConfirm::from_buffer(buf)?)), + OpcodeM0ToM4::MlmeDpsCnf => Ok(MacEvent::MlmeDpsCnf(DpsConfirm::from_buffer(buf)?)), + OpcodeM0ToM4::MlmeSoundingCnf => Ok(MacEvent::MlmeSoundingCnf(SoundingConfirm::from_buffer(buf)?)), + OpcodeM0ToM4::MlmeCalibrateCnf => Ok(MacEvent::MlmeCalibrateCnf(CalibrateConfirm::from_buffer(buf)?)), + OpcodeM0ToM4::McpsDataCnf => Ok(MacEvent::McpsDataCnf(DataConfirm::from_buffer(buf)?)), + OpcodeM0ToM4::McpsPurgeCnf => Ok(MacEvent::McpsPurgeCnf(PurgeConfirm::from_buffer(buf)?)), + OpcodeM0ToM4::MlmeAssociateInd => Ok(MacEvent::MlmeAssociateInd(AssociateIndication::from_buffer(buf)?)), + OpcodeM0ToM4::MlmeDisassociateInd => { + Ok(MacEvent::MlmeDisassociateInd(DisassociateIndication::from_buffer(buf)?)) + } + OpcodeM0ToM4::MlmeBeaconNotifyInd => { + Ok(MacEvent::MlmeBeaconNotifyInd(BeaconNotifyIndication::from_buffer(buf)?)) + } + OpcodeM0ToM4::MlmeCommStatusInd => Ok(MacEvent::MlmeCommStatusInd(CommStatusIndication::from_buffer(buf)?)), + OpcodeM0ToM4::MlmeGtsInd => Ok(MacEvent::MlmeGtsInd(GtsIndication::from_buffer(buf)?)), + OpcodeM0ToM4::MlmeOrphanInd => Ok(MacEvent::MlmeOrphanInd(OrphanIndication::from_buffer(buf)?)), + OpcodeM0ToM4::MlmeSyncLossInd => Ok(MacEvent::MlmeSyncLossInd(SyncLossIndication::from_buffer(buf)?)), + OpcodeM0ToM4::MlmeDpsInd => Ok(MacEvent::MlmeDpsInd(DpsIndication::from_buffer(buf)?)), + OpcodeM0ToM4::McpsDataInd => Ok(MacEvent::McpsDataInd(DataIndication::from_buffer(buf)?)), + OpcodeM0ToM4::MlmePollInd => Ok(MacEvent::MlmePollInd(PollIndication::from_buffer(buf)?)), + } + } +} + +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum MacEvent<'a> { + MlmeAssociateCnf(&'a AssociateConfirm), + MlmeDisassociateCnf(&'a DisassociateConfirm), + MlmeGetCnf(&'a GetConfirm), + MlmeGtsCnf(&'a GtsConfirm), + MlmeResetCnf(&'a ResetConfirm), + MlmeRxEnableCnf(&'a RxEnableConfirm), + MlmeScanCnf(&'a ScanConfirm), + MlmeSetCnf(&'a SetConfirm), + MlmeStartCnf(&'a StartConfirm), + MlmePollCnf(&'a PollConfirm), + MlmeDpsCnf(&'a DpsConfirm), + MlmeSoundingCnf(&'a SoundingConfirm), + MlmeCalibrateCnf(&'a CalibrateConfirm), + McpsDataCnf(&'a DataConfirm), + McpsPurgeCnf(&'a PurgeConfirm), + MlmeAssociateInd(&'a AssociateIndication), + MlmeDisassociateInd(&'a DisassociateIndication), + MlmeBeaconNotifyInd(&'a BeaconNotifyIndication), + MlmeCommStatusInd(&'a CommStatusIndication), + MlmeGtsInd(&'a GtsIndication), + MlmeOrphanInd(&'a OrphanIndication), + MlmeSyncLossInd(&'a SyncLossIndication), + MlmeDpsInd(&'a DpsIndication), + McpsDataInd(&'a DataIndication), + MlmePollInd(&'a PollIndication), +} diff --git a/embassy-stm32-wpan/src/mac/indications.rs b/embassy-stm32-wpan/src/mac/indications.rs new file mode 100644 index 000000000..5445fb4af --- /dev/null +++ b/embassy-stm32-wpan/src/mac/indications.rs @@ -0,0 +1,255 @@ +use core::slice; + +use super::consts::MAX_PENDING_ADDRESS; +use super::event::ParseableMacEvent; +use super::typedefs::{ + AddressMode, Capabilities, DisassociationReason, KeyIdMode, MacAddress, MacChannel, MacStatus, PanDescriptor, + PanId, SecurityLevel, +}; + +/// MLME ASSOCIATE Indication which will be used by the MAC +/// to indicate the reception of an association request command +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AssociateIndication { + /// Extended address of the device requesting association + pub device_address: [u8; 8], + /// Operational capabilities of the device requesting association + pub capability_information: Capabilities, + /// Security level purportedly used by the received MAC command frame + pub security_level: SecurityLevel, + /// The mode used to identify the key used by the originator of frame + pub key_id_mode: KeyIdMode, + /// Index of the key used by the originator of the received frame + pub key_index: u8, + /// The originator of the key used by the originator of the received frame + pub key_source: [u8; 8], +} + +impl ParseableMacEvent for AssociateIndication {} + +/// MLME DISASSOCIATE indication which will be used to send +/// disassociation indication to the application. +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DisassociateIndication { + /// Extended address of the device requesting association + pub device_address: [u8; 8], + /// The reason for the disassociation + pub disassociation_reason: DisassociationReason, + /// The security level to be used + pub security_level: SecurityLevel, + /// The mode used to identify the key to be used + pub key_id_mode: KeyIdMode, + /// The index of the key to be used + pub key_index: u8, + /// The originator of the key to be used + pub key_source: [u8; 8], +} + +impl ParseableMacEvent for DisassociateIndication {} + +/// MLME BEACON NOTIIFY Indication which is used to send parameters contained +/// within a beacon frame received by the MAC to the application +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct BeaconNotifyIndication { + /// he set of octets comprising the beacon payload to be transferred + /// from the MAC sublayer entity to the next higher layer + pub sdu_ptr: *const u8, + /// The PAN Descriptor for the received beacon + pub pan_descriptor: PanDescriptor, + /// The list of addresses of the devices + pub addr_list: [MacAddress; MAX_PENDING_ADDRESS], + /// Beacon Sequence Number + pub bsn: u8, + /// The beacon pending address specification + pub pend_addr_spec: u8, + /// Number of octets contained in the beacon payload of the beacon frame + pub sdu_length: u8, +} + +impl ParseableMacEvent for BeaconNotifyIndication {} + +/// MLME COMM STATUS Indication which is used by the MAC to indicate a communications status +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CommStatusIndication { + /// The 16-bit PAN identifier of the device from which the frame + /// was received or to which the frame was being sent + pub pan_id: PanId, + /// Source addressing mode + pub src_addr_mode: AddressMode, + /// Destination addressing mode + pub dst_addr_mode: AddressMode, + /// Source address + pub src_address: MacAddress, + /// Destination address + pub dst_address: MacAddress, + /// The communications status + pub status: MacStatus, + /// Security level to be used + pub security_level: SecurityLevel, + /// Mode used to identify the key to be used + pub key_id_mode: KeyIdMode, + /// Index of the key to be used + pub key_index: u8, + /// Originator of the key to be used + pub key_source: [u8; 8], +} + +impl ParseableMacEvent for CommStatusIndication {} + +/// MLME GTS Indication indicates that a GTS has been allocated or that a +/// previously allocated GTS has been deallocated +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct GtsIndication { + /// The short address of the device that has been allocated or deallocated a GTS + pub device_address: [u8; 2], + /// The characteristics of the GTS + pub gts_characteristics: u8, + /// Security level to be used + pub security_level: SecurityLevel, + /// Mode used to identify the key to be used + pub key_id_mode: KeyIdMode, + /// Index of the key to be used + pub key_index: u8, + /// byte stuffing to keep 32 bit alignment + a_stuffing: [u8; 2], + /// Originator of the key to be used + pub key_source: [u8; 8], +} + +impl ParseableMacEvent for GtsIndication {} + +/// MLME ORPHAN Indication which is used by the coordinator to notify the +/// application of the presence of an orphaned device +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct OrphanIndication { + /// Extended address of the orphaned device + pub orphan_address: [u8; 8], + /// Originator of the key used by the originator of the received frame + pub key_source: [u8; 8], + /// Security level purportedly used by the received MAC command frame + pub security_level: SecurityLevel, + /// Mode used to identify the key used by originator of received frame + pub key_id_mode: KeyIdMode, + /// Index of the key used by the originator of the received frame + pub key_index: u8, + /// byte stuffing to keep 32 bit alignment + a_stuffing: [u8; 1], +} + +impl ParseableMacEvent for OrphanIndication {} + +/// MLME SYNC LOSS Indication which is used by the MAC to indicate the loss +/// of synchronization with the coordinator +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SyncLossIndication { + /// The PAN identifier with which the device lost synchronization or to which it was realigned + pub pan_id: PanId, + /// The reason that synchronization was lost + pub loss_reason: u8, + /// The logical channel on which the device lost synchronization or to whi + pub channel_number: MacChannel, + /// The channel page on which the device lost synchronization or to which + pub channel_page: u8, + /// The security level used by the received MAC frame + pub security_level: SecurityLevel, + /// Mode used to identify the key used by originator of received frame + pub key_id_mode: KeyIdMode, + /// Index of the key used by the originator of the received frame + pub key_index: u8, + /// Originator of the key used by the originator of the received frame + pub key_source: [u8; 8], +} + +impl ParseableMacEvent for SyncLossIndication {} + +/// MLME DPS Indication which indicates the expiration of the DPSIndexDuration +/// and the resetting of the DPS values in the PHY +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DpsIndication { + /// byte stuffing to keep 32 bit alignment + a_stuffing: [u8; 4], +} + +impl ParseableMacEvent for DpsIndication {} + +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DataIndication { + /// Pointer to the set of octets forming the MSDU being indicated + pub msdu_ptr: *const u8, + /// Source addressing mode used + pub src_addr_mode: AddressMode, + /// Source PAN ID + pub src_pan_id: PanId, + /// Source address + pub src_address: MacAddress, + /// Destination addressing mode used + pub dst_addr_mode: AddressMode, + /// Destination PAN ID + pub dst_pan_id: PanId, + /// Destination address + pub dst_address: MacAddress, + /// The number of octets contained in the MSDU being indicated + pub msdu_length: u8, + /// QI value measured during reception of the MPDU + pub mpdu_link_quality: u8, + /// The data sequence number of the received data frame + pub dsn: u8, + /// The time, in symbols, at which the data were received + pub time_stamp: [u8; 4], + /// The security level purportedly used by the received data frame + security_level: SecurityLevel, + /// Mode used to identify the key used by originator of received frame + key_id_mode: KeyIdMode, + /// The originator of the key + pub key_source: [u8; 8], + /// The index of the key + pub key_index: u8, + /// he pulse repetition value of the received PPDU + pub uwbprf: u8, + /// The preamble symbol repetitions of the UWB PHY frame + pub uwn_preamble_symbol_repetitions: u8, + /// Indicates the data rate + pub datrate: u8, + /// time units corresponding to an RMARKER at the antenna at the end of a ranging exchange, + pub ranging_received: u8, + pub ranging_counter_start: u32, + pub ranging_counter_stop: u32, + /// ime units in a message exchange over which the tracking offset was measured + pub ranging_tracking_interval: u32, + /// time units slipped or advanced by the radio tracking system + pub ranging_offset: u32, + /// The FoM characterizing the ranging measurement + pub ranging_fom: u8, + /// The Received Signal Strength Indicator measured + pub rssi: u8, +} + +impl ParseableMacEvent for DataIndication {} + +impl DataIndication { + pub fn payload<'a>(&'a self) -> &'a [u8] { + unsafe { slice::from_raw_parts(self.msdu_ptr, self.msdu_length as usize) } + } +} + +/// MLME POLL Indication which will be used for indicating the Data Request +/// reception to upper layer as defined in Zigbee r22 - D.8.2 +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PollIndication { + /// addressing mode used + pub addr_mode: AddressMode, + /// Poll requester address + pub request_address: MacAddress, +} + +impl ParseableMacEvent for PollIndication {} diff --git a/embassy-stm32-wpan/src/mac/macros.rs b/embassy-stm32-wpan/src/mac/macros.rs new file mode 100644 index 000000000..1a988a779 --- /dev/null +++ b/embassy-stm32-wpan/src/mac/macros.rs @@ -0,0 +1,32 @@ +#[macro_export] +macro_rules! numeric_enum { + (#[repr($repr:ident)] + $(#$attrs:tt)* $vis:vis enum $name:ident { + $($(#$enum_attrs:tt)* $enum:ident = $constant:expr),* $(,)? + } ) => { + #[repr($repr)] + $(#$attrs)* + $vis enum $name { + $($(#$enum_attrs)* $enum = $constant),* + } + + impl ::core::convert::TryFrom<$repr> for $name { + type Error = (); + + fn try_from(value: $repr) -> ::core::result::Result { + match value { + $($constant => Ok( $name :: $enum ),)* + _ => Err(()) + } + } + } + + impl ::core::convert::From<$name> for $repr { + fn from(value: $name) -> $repr { + match value { + $($name :: $enum => $constant,)* + } + } + } + } +} diff --git a/embassy-stm32-wpan/src/mac/mod.rs b/embassy-stm32-wpan/src/mac/mod.rs new file mode 100644 index 000000000..8d5edad6b --- /dev/null +++ b/embassy-stm32-wpan/src/mac/mod.rs @@ -0,0 +1,8 @@ +pub mod commands; +mod consts; +pub mod event; +pub mod indications; +mod macros; +mod opcodes; +pub mod responses; +pub mod typedefs; diff --git a/embassy-stm32-wpan/src/mac/opcodes.rs b/embassy-stm32-wpan/src/mac/opcodes.rs new file mode 100644 index 000000000..fd7011873 --- /dev/null +++ b/embassy-stm32-wpan/src/mac/opcodes.rs @@ -0,0 +1,92 @@ +const ST_VENDOR_OGF: u16 = 0x3F; +const MAC_802_15_4_CMD_OPCODE_OFFSET: u16 = 0x280; + +const fn opcode(ocf: u16) -> isize { + ((ST_VENDOR_OGF << 9) | (MAC_802_15_4_CMD_OPCODE_OFFSET + ocf)) as isize +} + +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum OpcodeM4ToM0 { + MlmeAssociateReq = opcode(0x00), + MlmeAssociateRes = opcode(0x01), + MlmeDisassociateReq = opcode(0x02), + MlmeGetReq = opcode(0x03), + MlmeGtsReq = opcode(0x04), + MlmeOrphanRes = opcode(0x05), + MlmeResetReq = opcode(0x06), + MlmeRxEnableReq = opcode(0x07), + MlmeScanReq = opcode(0x08), + MlmeSetReq = opcode(0x09), + MlmeStartReq = opcode(0x0A), + MlmeSyncReq = opcode(0x0B), + MlmePollReq = opcode(0x0C), + MlmeDpsReq = opcode(0x0D), + MlmeSoundingReq = opcode(0x0E), + MlmeCalibrateReq = opcode(0x0F), + McpsDataReq = opcode(0x10), + McpsPurgeReq = opcode(0x11), +} + +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum OpcodeM0ToM4 { + MlmeAssociateCnf = 0x00, + MlmeDisassociateCnf, + MlmeGetCnf, + MlmeGtsCnf, + MlmeResetCnf, + MlmeRxEnableCnf, + MlmeScanCnf, + MlmeSetCnf, + MlmeStartCnf, + MlmePollCnf, + MlmeDpsCnf, + MlmeSoundingCnf, + MlmeCalibrateCnf, + McpsDataCnf, + McpsPurgeCnf, + MlmeAssociateInd, + MlmeDisassociateInd, + MlmeBeaconNotifyInd, + MlmeCommStatusInd, + MlmeGtsInd, + MlmeOrphanInd, + MlmeSyncLossInd, + MlmeDpsInd, + McpsDataInd, + MlmePollInd, +} + +impl TryFrom for OpcodeM0ToM4 { + type Error = (); + + fn try_from(value: u16) -> Result { + match value { + 0 => Ok(Self::MlmeAssociateCnf), + 1 => Ok(Self::MlmeDisassociateCnf), + 2 => Ok(Self::MlmeGetCnf), + 3 => Ok(Self::MlmeGtsCnf), + 4 => Ok(Self::MlmeResetCnf), + 5 => Ok(Self::MlmeRxEnableCnf), + 6 => Ok(Self::MlmeScanCnf), + 7 => Ok(Self::MlmeSetCnf), + 8 => Ok(Self::MlmeStartCnf), + 9 => Ok(Self::MlmePollCnf), + 10 => Ok(Self::MlmeDpsCnf), + 11 => Ok(Self::MlmeSoundingCnf), + 12 => Ok(Self::MlmeCalibrateCnf), + 13 => Ok(Self::McpsDataCnf), + 14 => Ok(Self::McpsPurgeCnf), + 15 => Ok(Self::MlmeAssociateInd), + 16 => Ok(Self::MlmeDisassociateInd), + 17 => Ok(Self::MlmeBeaconNotifyInd), + 18 => Ok(Self::MlmeCommStatusInd), + 19 => Ok(Self::MlmeGtsInd), + 20 => Ok(Self::MlmeOrphanInd), + 21 => Ok(Self::MlmeSyncLossInd), + 22 => Ok(Self::MlmeDpsInd), + 23 => Ok(Self::McpsDataInd), + 24 => Ok(Self::MlmePollInd), + _ => Err(()), + } + } +} diff --git a/embassy-stm32-wpan/src/mac/responses.rs b/embassy-stm32-wpan/src/mac/responses.rs new file mode 100644 index 000000000..5d203084c --- /dev/null +++ b/embassy-stm32-wpan/src/mac/responses.rs @@ -0,0 +1,258 @@ +use super::consts::{MAX_ED_SCAN_RESULTS_SUPPORTED, MAX_PAN_DESC_SUPPORTED, MAX_SOUNDING_LIST_SUPPORTED}; +use super::event::ParseableMacEvent; +use super::typedefs::{ + AddressMode, AssociationStatus, KeyIdMode, MacAddress, MacStatus, PanDescriptor, PanId, PibId, ScanType, + SecurityLevel, +}; + +/// MLME ASSOCIATE Confirm used to inform of the initiating device whether +/// its request to associate was successful or unsuccessful +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct AssociateConfirm { + /// short address allocated by the coordinator on successful association + pub assoc_short_address: [u8; 2], + /// status of the association request + pub status: AssociationStatus, + /// security level to be used + pub security_level: SecurityLevel, + /// the originator of the key to be used + pub key_source: [u8; 8], + /// the mode used to identify the key to be used + pub key_id_mode: KeyIdMode, + /// the index of the key to be used + pub key_index: u8, + /// byte stuffing to keep 32 bit alignment + a_stuffing: [u8; 2], +} + +impl ParseableMacEvent for AssociateConfirm {} + +/// MLME DISASSOCIATE Confirm used to send disassociation Confirmation to the application. +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DisassociateConfirm { + /// status of the disassociation attempt + pub status: MacStatus, + /// device addressing mode used + pub device_addr_mode: AddressMode, + /// the identifier of the PAN of the device + pub device_pan_id: PanId, + /// device address + pub device_address: MacAddress, +} + +impl ParseableMacEvent for DisassociateConfirm {} + +/// MLME GET Confirm which requests information about a given PIB attribute +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct GetConfirm { + /// The pointer to the value of the PIB attribute attempted to read + pub pib_attribute_value_ptr: *const u8, + /// Status of the GET attempt + pub status: MacStatus, + /// The name of the PIB attribute attempted to read + pub pib_attribute: PibId, + /// The lenght of the PIB attribute Value return + pub pib_attribute_value_len: u8, + /// byte stuffing to keep 32 bit alignment + a_stuffing: [u8; 1], +} + +impl ParseableMacEvent for GetConfirm {} + +/// MLME GTS Confirm which eports the results of a request to allocate a new GTS +/// or to deallocate an existing GTS +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct GtsConfirm { + /// The characteristics of the GTS + pub gts_characteristics: u8, + /// The status of the GTS reques + pub status: MacStatus, + /// byte stuffing to keep 32 bit alignment + a_stuffing: [u8; 2], +} + +impl ParseableMacEvent for GtsConfirm {} + +/// MLME RESET Confirm which is used to report the results of the reset operation +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ResetConfirm { + /// The result of the reset operation + status: MacStatus, + /// byte stuffing to keep 32 bit alignment + a_stuffing: [u8; 3], +} + +impl ParseableMacEvent for ResetConfirm {} + +/// MLME RX ENABLE Confirm which is used to report the results of the attempt +/// to enable or disable the receiver +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct RxEnableConfirm { + /// Result of the request to enable or disable the receiver + status: MacStatus, + /// byte stuffing to keep 32 bit alignment + a_stuffing: [u8; 3], +} + +impl ParseableMacEvent for RxEnableConfirm {} + +/// MLME SCAN Confirm which is used to report the result of the channel scan request +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ScanConfirm { + /// Status of the scan request + pub status: MacStatus, + /// The type of scan performed + pub scan_type: ScanType, + /// Channel page on which the scan was performed + pub channel_page: u8, + /// Channels given in the request which were not scanned + pub unscanned_channels: [u8; 4], + /// Number of elements returned in the appropriate result lists + pub result_list_size: u8, + /// List of energy measurements + pub energy_detect_list: [u8; MAX_ED_SCAN_RESULTS_SUPPORTED], + /// List of PAN descriptors + pub pan_descriptor_list: [PanDescriptor; MAX_PAN_DESC_SUPPORTED], + /// Categorization of energy detected in channel + pub detected_category: u8, + /// For UWB PHYs, the list of energy measurements taken + pub uwb_energy_detect_list: [u8; MAX_ED_SCAN_RESULTS_SUPPORTED], +} + +impl ParseableMacEvent for ScanConfirm {} + +/// MLME SET Confirm which reports the result of an attempt to write a value to a PIB attribute +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SetConfirm { + /// The result of the set operation + pub status: MacStatus, + /// The name of the PIB attribute that was written + pub pin_attribute: PibId, + /// byte stuffing to keep 32 bit alignment + a_stuffing: [u8; 2], +} + +impl ParseableMacEvent for SetConfirm {} + +/// MLME START Confirm which is used to report the results of the attempt to +/// start using a new superframe configuration +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct StartConfirm { + /// Result of the attempt to start using an updated superframe configuration + pub status: MacStatus, + /// byte stuffing to keep 32 bit alignment + a_stuffing: [u8; 3], +} + +impl ParseableMacEvent for StartConfirm {} + +/// MLME POLL Confirm which is used to report the result of a request to poll the coordinator for data +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PollConfirm { + /// The status of the data request + pub status: MacStatus, + /// byte stuffing to keep 32 bit alignment + a_stuffing: [u8; 3], +} + +impl ParseableMacEvent for PollConfirm {} + +/// MLME DPS Confirm which reports the results of the attempt to enable or disable the DPS +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DpsConfirm { + /// The status of the DPS request + pub status: MacStatus, + /// byte stuffing to keep 32 bit alignment + a_stuffing: [u8; 3], +} + +impl ParseableMacEvent for DpsConfirm {} + +/// MLME SOUNDING Confirm which reports the result of a request to the PHY to provide +/// channel sounding information +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct SoundingConfirm { + /// Results of the sounding measurement + sounding_list: [u8; MAX_SOUNDING_LIST_SUPPORTED], + + status: u8, +} + +impl ParseableMacEvent for SoundingConfirm {} + +/// MLME CALIBRATE Confirm which reports the result of a request to the PHY +/// to provide internal propagation path information +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct CalibrateConfirm { + /// The status of the attempt to return sounding data + pub status: MacStatus, + /// byte stuffing to keep 32 bit alignment + a_stuffing: [u8; 3], + /// A count of the propagation time from the ranging counter + /// to the transmit antenna + pub cal_tx_rmaker_offset: u32, + /// A count of the propagation time from the receive antenna + /// to the ranging counter + pub cal_rx_rmaker_offset: u32, +} + +impl ParseableMacEvent for CalibrateConfirm {} + +/// MCPS DATA Confirm which will be used for reporting the results of +/// MAC data related requests from the application +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DataConfirm { + /// The handle associated with the MSDU being confirmed + pub msdu_handle: u8, + /// The time, in symbols, at which the data were transmitted + pub time_stamp: [u8; 4], + /// ranging status + pub ranging_received: u8, + /// The status of the last MSDU transmission + pub status: MacStatus, + /// time units corresponding to an RMARKER at the antenna at + /// the beginning of a ranging exchange + pub ranging_counter_start: u32, + /// time units corresponding to an RMARKER at the antenna + /// at the end of a ranging exchange + pub ranging_counter_stop: u32, + /// time units in a message exchange over which the tracking offset was measured + pub ranging_tracking_interval: u32, + /// time units slipped or advanced by the radio tracking system + pub ranging_offset: u32, + /// The FoM characterizing the ranging measurement + pub ranging_fom: u8, + /// byte stuffing to keep 32 bit alignment + a_stuffing: [u8; 3], +} + +impl ParseableMacEvent for DataConfirm {} + +/// MCPS PURGE Confirm which will be used by the MAC to notify the application of +/// the status of its request to purge an MSDU from the transaction queue +#[repr(C)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PurgeConfirm { + /// Handle associated with the MSDU requested to be purged from the transaction queue + pub msdu_handle: u8, + /// The status of the request + pub status: MacStatus, + /// byte stuffing to keep 32 bit alignment + a_stuffing: [u8; 2], +} + +impl ParseableMacEvent for PurgeConfirm {} diff --git a/embassy-stm32-wpan/src/mac/typedefs.rs b/embassy-stm32-wpan/src/mac/typedefs.rs new file mode 100644 index 000000000..98c67c86b --- /dev/null +++ b/embassy-stm32-wpan/src/mac/typedefs.rs @@ -0,0 +1,365 @@ +use crate::numeric_enum; + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum MacError { + Error = 0x01, + NotImplemented = 0x02, + NotSupported = 0x03, + HardwareNotSupported = 0x04, + Undefined = 0x05, +} + +impl From for MacError { + fn from(value: u8) -> Self { + match value { + 0x01 => Self::Error, + 0x02 => Self::NotImplemented, + 0x03 => Self::NotSupported, + 0x04 => Self::HardwareNotSupported, + 0x05 => Self::Undefined, + _ => Self::Undefined, + } + } +} + +numeric_enum! { + #[repr(u8)] + #[derive(Debug, Default)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum MacStatus { + #[default] + Success = 0x00, + Failure = 0xFF + } +} + +numeric_enum! { + #[repr(u8)] + /// this enum contains all the MAC PIB Ids + #[derive(Default)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum PibId { + // PHY + #[default] + CurrentChannel = 0x00, + ChannelsSupported = 0x01, + TransmitPower = 0x02, + CCAMode = 0x03, + CurrentPage = 0x04, + MaxFrameDuration = 0x05, + SHRDuration = 0x06, + SymbolsPerOctet = 0x07, + + // MAC + AckWaitDuration = 0x40, + AssociationPermit = 0x41, + AutoRequest = 0x42, + BeaconPayload = 0x45, + BeaconPayloadLength = 0x46, + BeaconOrder = 0x47, + Bsn = 0x49, + CoordExtendedAdddress = 0x4A, + CoordShortAddress = 0x4B, + Dsn = 0x4C, + MaxFrameTotalWaitTime = 0x58, + MaxFrameRetries = 0x59, + PanId = 0x50, + ResponseWaitTime = 0x5A, + RxOnWhenIdle = 0x52, + SecurityEnabled = 0x5D, + ShortAddress = 0x53, + SuperframeOrder = 0x54, + TimestampSupported = 0x5C, + TransactionPersistenceTime = 0x55, + MaxBe = 0x57, + LifsPeriod = 0x5E, + SifsPeriod = 0x5F, + MaxCsmaBackoffs = 0x4E, + MinBe = 0x4F, + PanCoordinator = 0x10, + AssocPanCoordinator = 0x11, + ExtendedAddress = 0x6F, + AclEntryDescriptor = 0x70, + AclEntryDescriptorSize = 0x71, + DefaultSecurity = 0x72, + DefaultSecurityMaterialLength = 0x73, + DefaultSecurityMaterial = 0x74, + DefaultSecuritySuite = 0x75, + SecurityMode = 0x76, + CurrentAclEntries = 0x80, + DefaultSecurityExtendedAddress = 0x81, + AssociatedPanCoordinator = 0x56, + PromiscuousMode = 0x51, + } +} + +numeric_enum! { + #[repr(u8)] + #[derive(Default, Clone, Copy)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum AddressMode { + #[default] + NoAddress = 0x00, + Reserved = 0x01, + Short = 0x02, + Extended = 0x03, +} +} + +#[derive(Clone, Copy)] +pub union MacAddress { + pub short: [u8; 2], + pub extended: [u8; 8], +} + +#[cfg(feature = "defmt")] +impl defmt::Format for MacAddress { + fn format(&self, fmt: defmt::Formatter) { + unsafe { + defmt::write!( + fmt, + "MacAddress {{ short: {}, extended: {} }}", + self.short, + self.extended + ) + } + } +} + +impl Default for MacAddress { + fn default() -> Self { + Self { short: [0, 0] } + } +} + +impl MacAddress { + pub const BROADCAST: Self = Self { short: [0xFF, 0xFF] }; +} + +impl TryFrom<&[u8]> for MacAddress { + type Error = (); + + fn try_from(buf: &[u8]) -> Result { + const SIZE: usize = 8; + if buf.len() < SIZE { + return Err(()); + } + + Ok(Self { + extended: [buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7]], + }) + } +} + +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct GtsCharacteristics { + pub fields: u8, +} + +/// MAC PAN Descriptor which contains the network details of the device from +/// which the beacon is received +#[derive(Default, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PanDescriptor { + /// PAN identifier of the coordinator + pub coord_pan_id: PanId, + /// Coordinator addressing mode + pub coord_addr_mode: AddressMode, + /// The current logical channel occupied by the network + pub logical_channel: MacChannel, + /// Coordinator address + pub coord_addr: MacAddress, + /// The current channel page occupied by the network + pub channel_page: u8, + /// PAN coordinator is accepting GTS requests or not + pub gts_permit: bool, + /// Superframe specification as specified in the received beacon frame + pub superframe_spec: [u8; 2], + /// The time at which the beacon frame was received, in symbols + pub time_stamp: [u8; 4], + /// The LQI at which the network beacon was received + pub link_quality: u8, + /// Security level purportedly used by the received beacon frame + pub security_level: u8, +} + +impl TryFrom<&[u8]> for PanDescriptor { + type Error = (); + + fn try_from(buf: &[u8]) -> Result { + const SIZE: usize = 22; + if buf.len() < SIZE { + return Err(()); + } + + let coord_addr_mode = AddressMode::try_from(buf[2])?; + let coord_addr = match coord_addr_mode { + AddressMode::NoAddress => MacAddress { short: [0, 0] }, + AddressMode::Reserved => MacAddress { short: [0, 0] }, + AddressMode::Short => MacAddress { + short: [buf[4], buf[5]], + }, + AddressMode::Extended => MacAddress { + extended: [buf[4], buf[5], buf[6], buf[7], buf[8], buf[9], buf[10], buf[11]], + }, + }; + + Ok(Self { + coord_pan_id: PanId([buf[0], buf[1]]), + coord_addr_mode, + logical_channel: MacChannel::try_from(buf[3])?, + coord_addr, + channel_page: buf[12], + gts_permit: buf[13] != 0, + superframe_spec: [buf[14], buf[15]], + time_stamp: [buf[16], buf[17], buf[18], buf[19]], + link_quality: buf[20], + security_level: buf[21], + // 2 byte stuffing + }) + } +} + +numeric_enum! { + #[repr(u8)] + #[derive(Default, Clone, Copy)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + /// Building wireless applications with STM32WB series MCUs - Application note 13.10.3 + pub enum MacChannel { + Channel11 = 0x0B, + Channel12 = 0x0C, + Channel13 = 0x0D, + Channel14 = 0x0E, + Channel15 = 0x0F, + #[default] + Channel16 = 0x10, + Channel17 = 0x11, + Channel18 = 0x12, + Channel19 = 0x13, + Channel20 = 0x14, + Channel21 = 0x15, + Channel22 = 0x16, + Channel23 = 0x17, + Channel24 = 0x18, + Channel25 = 0x19, + Channel26 = 0x1A, + } +} + +#[cfg(not(feature = "defmt"))] +bitflags::bitflags! { + pub struct Capabilities: u8 { + /// 1 if the device is capabaleof becoming a PAN coordinator + const IS_COORDINATOR_CAPABLE = 0b00000001; + /// 1 if the device is an FFD, 0 if it is an RFD + const IS_FFD = 0b00000010; + /// 1 if the device is receiving power from mains, 0 if it is battery-powered + const IS_MAINS_POWERED = 0b00000100; + /// 1 if the device does not disable its receiver to conserver power during idle periods + const RECEIVER_ON_WHEN_IDLE = 0b00001000; + // 0b00010000 reserved + // 0b00100000 reserved + /// 1 if the device is capable of sending and receiving secured MAC frames + const IS_SECURE = 0b01000000; + /// 1 if the device wishes the coordinator to allocate a short address as a result of the association + const ALLOCATE_ADDRESS = 0b10000000; + } +} + +#[cfg(feature = "defmt")] +defmt::bitflags! { + pub struct Capabilities: u8 { + /// 1 if the device is capabaleof becoming a PAN coordinator + const IS_COORDINATOR_CAPABLE = 0b00000001; + /// 1 if the device is an FFD, 0 if it is an RFD + const IS_FFD = 0b00000010; + /// 1 if the device is receiving power from mains, 0 if it is battery-powered + const IS_MAINS_POWERED = 0b00000100; + /// 1 if the device does not disable its receiver to conserver power during idle periods + const RECEIVER_ON_WHEN_IDLE = 0b00001000; + // 0b00010000 reserved + // 0b00100000 reserved + /// 1 if the device is capable of sending and receiving secured MAC frames + const IS_SECURE = 0b01000000; + /// 1 if the device wishes the coordinator to allocate a short address as a result of the association + const ALLOCATE_ADDRESS = 0b10000000; + } +} + +numeric_enum! { + #[repr(u8)] + #[derive(Default, Clone, Copy)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum KeyIdMode { + #[default] + /// the key is determined implicitly from the originator and recipient(s) of the frame + Implicite = 0x00, + /// the key is determined explicitly using a 1 bytes key source and a 1 byte key index + Explicite1Byte = 0x01, + /// the key is determined explicitly using a 4 bytes key source and a 1 byte key index + Explicite4Byte = 0x02, + /// the key is determined explicitly using a 8 bytes key source and a 1 byte key index + Explicite8Byte = 0x03, + } +} + +numeric_enum! { + #[repr(u8)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum AssociationStatus { + /// Association successful + Success = 0x00, + /// PAN at capacity + PanAtCapacity = 0x01, + /// PAN access denied + PanAccessDenied = 0x02 + } +} + +numeric_enum! { + #[repr(u8)] + #[derive(Clone, Copy)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum DisassociationReason { + /// The coordinator wishes the device to leave the PAN. + CoordRequested = 0x01, + /// The device wishes to leave the PAN. + DeviceRequested = 0x02, + } +} + +numeric_enum! { + #[repr(u8)] + #[derive(Default, Clone, Copy)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum SecurityLevel { + /// MAC Unsecured Mode Security + #[default] + Unsecure = 0x00, + /// MAC ACL Mode Security + AclMode = 0x01, + /// MAC Secured Mode Security + Secured = 0x02, + } +} + +numeric_enum! { + #[repr(u8)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum ScanType { + EdScan = 0x00, + Active = 0x01, + Passive = 0x02, + Orphan = 0x03 + } +} + +/// newtype for Pan Id +#[derive(Default, Clone, Copy)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct PanId(pub [u8; 2]); + +impl PanId { + pub const BROADCAST: Self = Self([0xFF, 0xFF]); +} diff --git a/embassy-stm32-wpan/src/shci.rs b/embassy-stm32-wpan/src/shci.rs new file mode 100644 index 000000000..30d689716 --- /dev/null +++ b/embassy-stm32-wpan/src/shci.rs @@ -0,0 +1,375 @@ +use core::{mem, slice}; + +use crate::consts::{TL_CS_EVT_SIZE, TL_EVT_HEADER_SIZE, TL_PACKET_HEADER_SIZE}; + +const SHCI_OGF: u16 = 0x3F; + +const fn opcode(ogf: u16, ocf: u16) -> isize { + ((ogf << 10) + ocf) as isize +} + +#[allow(dead_code)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum SchiCommandStatus { + ShciSuccess = 0x00, + ShciUnknownCmd = 0x01, + ShciMemoryCapacityExceededErrCode = 0x07, + ShciErrUnsupportedFeature = 0x11, + ShciErrInvalidHciCmdParams = 0x12, + ShciErrInvalidParams = 0x42, /* only used for release < v1.13.0 */ + ShciErrInvalidParamsV2 = 0x92, /* available for release >= v1.13.0 */ + ShciFusCmdNotSupported = 0xFF, +} + +impl TryFrom for SchiCommandStatus { + type Error = (); + + fn try_from(v: u8) -> Result { + match v { + x if x == SchiCommandStatus::ShciSuccess as u8 => Ok(SchiCommandStatus::ShciSuccess), + x if x == SchiCommandStatus::ShciUnknownCmd as u8 => Ok(SchiCommandStatus::ShciUnknownCmd), + x if x == SchiCommandStatus::ShciMemoryCapacityExceededErrCode as u8 => { + Ok(SchiCommandStatus::ShciMemoryCapacityExceededErrCode) + } + x if x == SchiCommandStatus::ShciErrUnsupportedFeature as u8 => { + Ok(SchiCommandStatus::ShciErrUnsupportedFeature) + } + x if x == SchiCommandStatus::ShciErrInvalidHciCmdParams as u8 => { + Ok(SchiCommandStatus::ShciErrInvalidHciCmdParams) + } + x if x == SchiCommandStatus::ShciErrInvalidParams as u8 => Ok(SchiCommandStatus::ShciErrInvalidParams), /* only used for release < v1.13.0 */ + x if x == SchiCommandStatus::ShciErrInvalidParamsV2 as u8 => Ok(SchiCommandStatus::ShciErrInvalidParamsV2), /* available for release >= v1.13.0 */ + x if x == SchiCommandStatus::ShciFusCmdNotSupported as u8 => Ok(SchiCommandStatus::ShciFusCmdNotSupported), + _ => Err(()), + } + } +} + +#[allow(dead_code)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ShciOpcode { + // 0x50 reserved + // 0x51 reserved + FusGetState = opcode(SHCI_OGF, 0x52), + // 0x53 reserved + FusFirmwareUpgrade = opcode(SHCI_OGF, 0x54), + FusFirmwareDelete = opcode(SHCI_OGF, 0x55), + FusUpdateAuthKey = opcode(SHCI_OGF, 0x56), + FusLockAuthKey = opcode(SHCI_OGF, 0x57), + FusStoreUserKey = opcode(SHCI_OGF, 0x58), + FusLoadUserKey = opcode(SHCI_OGF, 0x59), + FusStartWirelessStack = opcode(SHCI_OGF, 0x5a), + // 0x5b reserved + // 0x5c reserved + FusLockUserKey = opcode(SHCI_OGF, 0x5d), + FusUnloadUserKey = opcode(SHCI_OGF, 0x5e), + FusActivateAntirollback = opcode(SHCI_OGF, 0x5f), + // 0x60 reserved + // 0x61 reserved + // 0x62 reserved + // 0x63 reserved + // 0x64 reserved + // 0x65 reserved + BleInit = opcode(SHCI_OGF, 0x66), + ThreadInit = opcode(SHCI_OGF, 0x67), + DebugInit = opcode(SHCI_OGF, 0x68), + FlashEraseActivity = opcode(SHCI_OGF, 0x69), + ConcurrentSetMode = opcode(SHCI_OGF, 0x6a), + FlashStoreData = opcode(SHCI_OGF, 0x6b), + FlashEraseData = opcode(SHCI_OGF, 0x6c), + RadioAllowLowPower = opcode(SHCI_OGF, 0x6d), + Mac802_15_4Init = opcode(SHCI_OGF, 0x6e), + ReInit = opcode(SHCI_OGF, 0x6f), + ZigbeeInit = opcode(SHCI_OGF, 0x70), + LldTestsInit = opcode(SHCI_OGF, 0x71), + ExtraConfig = opcode(SHCI_OGF, 0x72), + SetFlashActivityControl = opcode(SHCI_OGF, 0x73), + BleLldInit = opcode(SHCI_OGF, 0x74), + Config = opcode(SHCI_OGF, 0x75), + ConcurrentGetNextBleEvtTime = opcode(SHCI_OGF, 0x76), + ConcurrentEnableNext802_15_4EvtNotification = opcode(SHCI_OGF, 0x77), + Mac802_15_4DeInit = opcode(SHCI_OGF, 0x78), +} + +pub const SHCI_C2_CONFIG_EVTMASK1_BIT0_ERROR_NOTIF_ENABLE: u8 = 1 << 0; +pub const SHCI_C2_CONFIG_EVTMASK1_BIT1_BLE_NVM_RAM_UPDATE_ENABLE: u8 = 1 << 1; +pub const SHCI_C2_CONFIG_EVTMASK1_BIT2_THREAD_NVM_RAM_UPDATE_ENABLE: u8 = 1 << 2; +pub const SHCI_C2_CONFIG_EVTMASK1_BIT3_NVM_START_WRITE_ENABLE: u8 = 1 << 3; +pub const SHCI_C2_CONFIG_EVTMASK1_BIT4_NVM_END_WRITE_ENABLE: u8 = 1 << 4; +pub const SHCI_C2_CONFIG_EVTMASK1_BIT5_NVM_START_ERASE_ENABLE: u8 = 1 << 5; +pub const SHCI_C2_CONFIG_EVTMASK1_BIT6_NVM_END_ERASE_ENABLE: u8 = 1 << 6; + +#[derive(Clone, Copy)] +#[repr(C, packed)] +pub struct ShciConfigParam { + pub payload_cmd_size: u8, + pub config: u8, + pub event_mask: u8, + pub spare: u8, + pub ble_nvm_ram_address: u32, + pub thread_nvm_ram_address: u32, + pub revision_id: u16, + pub device_id: u16, +} + +impl ShciConfigParam { + pub fn payload<'a>(&'a self) -> &'a [u8] { + unsafe { slice::from_raw_parts(self as *const _ as *const u8, mem::size_of::()) } + } +} + +impl Default for ShciConfigParam { + fn default() -> Self { + Self { + payload_cmd_size: (mem::size_of::() - 1) as u8, + config: 0, + event_mask: SHCI_C2_CONFIG_EVTMASK1_BIT0_ERROR_NOTIF_ENABLE + + SHCI_C2_CONFIG_EVTMASK1_BIT1_BLE_NVM_RAM_UPDATE_ENABLE + + SHCI_C2_CONFIG_EVTMASK1_BIT2_THREAD_NVM_RAM_UPDATE_ENABLE + + SHCI_C2_CONFIG_EVTMASK1_BIT3_NVM_START_WRITE_ENABLE + + SHCI_C2_CONFIG_EVTMASK1_BIT4_NVM_END_WRITE_ENABLE + + SHCI_C2_CONFIG_EVTMASK1_BIT5_NVM_START_ERASE_ENABLE + + SHCI_C2_CONFIG_EVTMASK1_BIT6_NVM_END_ERASE_ENABLE, + spare: 0, + ble_nvm_ram_address: 0, + thread_nvm_ram_address: 0, + revision_id: 0, + device_id: 0, + } + } +} + +#[derive(Clone, Copy)] +#[repr(C, packed)] +pub struct ShciBleInitCmdParam { + /// NOT USED - shall be set to 0 + pub p_ble_buffer_address: u32, + /// NOT USED - shall be set to 0 + pub ble_buffer_size: u32, + /// Maximum number of attribute records related to all the required characteristics (excluding the services) + /// that can be stored in the GATT database, for the specific BLE user application. + /// For each characteristic, the number of attribute records goes from two to five depending on the characteristic properties: + /// - minimum of two (one for declaration and one for the value) + /// - add one more record for each additional property: notify or indicate, broadcast, extended property. + /// The total calculated value must be increased by 9, due to the records related to the standard attribute profile and + /// GAP service characteristics, and automatically added when initializing GATT and GAP layers + /// - Min value: + 9 + /// - Max value: depending on the GATT database defined by user application + pub num_attr_record: u16, + /// Defines the maximum number of services that can be stored in the GATT database. Note that the GAP and GATT services + /// are automatically added at initialization so this parameter must be the number of user services increased by two. + /// - Min value: + 2 + /// - Max value: depending GATT database defined by user application + pub num_attr_serv: u16, + /// NOTE: This parameter is ignored by the CPU2 when the parameter "Options" is set to "LL_only" ( see Options description in that structure ) + /// + /// Size of the storage area for the attribute values. + /// Each characteristic contributes to the attrValueArrSize value as follows: + /// - Characteristic value length plus: + /// + 5 bytes if characteristic UUID is 16 bits + /// + 19 bytes if characteristic UUID is 128 bits + /// + 2 bytes if characteristic has a server configuration descriptor + /// + 2 bytes * NumOfLinks if the characteristic has a client configuration descriptor + /// + 2 bytes if the characteristic has extended properties + /// Each descriptor contributes to the attrValueArrSize value as follows: + /// - Descriptor length + pub attr_value_arr_size: u16, + /// Maximum number of BLE links supported + /// - Min value: 1 + /// - Max value: 8 + pub num_of_links: u8, + /// Disable/enable the extended packet length BLE 5.0 feature + /// - Disable: 0 + /// - Enable: 1 + pub extended_packet_length_enable: u8, + /// NOTE: This parameter is ignored by the CPU2 when the parameter "Options" is set to "LL_only" ( see Options description in that structure ) + /// + /// Maximum number of supported "prepare write request" + /// - Min value: given by the macro DEFAULT_PREP_WRITE_LIST_SIZE + /// - Max value: a value higher than the minimum required can be specified, but it is not recommended + pub prepare_write_list_size: u8, + /// NOTE: This parameter is overwritten by the CPU2 with an hardcoded optimal value when the parameter "Options" is set to "LL_only" + /// ( see Options description in that structure ) + /// + /// Number of allocated memory blocks for the BLE stack + /// - Min value: given by the macro MBLOCKS_CALC + /// - Max value: a higher value can improve data throughput performance, but uses more memory + pub block_count: u8, + /// NOTE: This parameter is ignored by the CPU2 when the parameter "Options" is set to "LL_only" ( see Options description in that structure ) + /// + /// Maximum ATT MTU size supported + /// - Min value: 23 + /// - Max value: 512 + pub att_mtu: u16, + /// The sleep clock accuracy (ppm value) that used in BLE connected slave mode to calculate the window widening + /// (in combination with the sleep clock accuracy sent by master in CONNECT_REQ PDU), + /// refer to BLE 5.0 specifications - Vol 6 - Part B - chap 4.5.7 and 4.2.2 + /// - Min value: 0 + /// - Max value: 500 (worst possible admitted by specification) + pub slave_sca: u16, + /// The sleep clock accuracy handled in master mode. It is used to determine the connection and advertising events timing. + /// It is transmitted to the slave in CONNEC_REQ PDU used by the slave to calculate the window widening, + /// see SlaveSca and Bluetooth Core Specification v5.0 Vol 6 - Part B - chap 4.5.7 and 4.2.2 + /// Possible values: + /// - 251 ppm to 500 ppm: 0 + /// - 151 ppm to 250 ppm: 1 + /// - 101 ppm to 150 ppm: 2 + /// - 76 ppm to 100 ppm: 3 + /// - 51 ppm to 75 ppm: 4 + /// - 31 ppm to 50 ppm: 5 + /// - 21 ppm to 30 ppm: 6 + /// - 0 ppm to 20 ppm: 7 + pub master_sca: u8, + /// Some information for Low speed clock mapped in bits field + /// - bit 0: + /// - 1: Calibration for the RF system wakeup clock source + /// - 0: No calibration for the RF system wakeup clock source + /// - bit 1: + /// - 1: STM32W5M Module device + /// - 0: Other devices as STM32WBxx SOC, STM32WB1M module + /// - bit 2: + /// - 1: HSE/1024 Clock config + /// - 0: LSE Clock config + pub ls_source: u8, + /// This parameter determines the maximum duration of a slave connection event. When this duration is reached the slave closes + /// the current connections event (whatever is the CE_length parameter specified by the master in HCI_CREATE_CONNECTION HCI command), + /// expressed in units of 625/256 µs (~2.44 µs) + /// - Min value: 0 (if 0 is specified, the master and slave perform only a single TX-RX exchange per connection event) + /// - Max value: 1638400 (4000 ms). A higher value can be specified (max 0xFFFFFFFF) but results in a maximum connection time + /// of 4000 ms as specified. In this case the parameter is not applied, and the predicted CE length calculated on slave is not shortened + pub max_conn_event_length: u32, + /// Startup time of the high speed (16 or 32 MHz) crystal oscillator in units of 625/256 µs (~2.44 µs). + /// - Min value: 0 + /// - Max value: 820 (~2 ms). A higher value can be specified, but the value that implemented in stack is forced to ~2 ms + pub hs_startup_time: u16, + /// Viterbi implementation in BLE LL reception. + /// - 0: Enable + /// - 1: Disable + pub viterbi_enable: u8, + /// - bit 0: + /// - 1: LL only + /// - 0: LL + host + /// - bit 1: + /// - 1: no service change desc. + /// - 0: with service change desc. + /// - bit 2: + /// - 1: device name Read-Only + /// - 0: device name R/W + /// - bit 3: + /// - 1: extended advertizing supported + /// - 0: extended advertizing not supported + /// - bit 4: + /// - 1: CS Algo #2 supported + /// - 0: CS Algo #2 not supported + /// - bit 5: + /// - 1: Reduced GATT database in NVM + /// - 0: Full GATT database in NVM + /// - bit 6: + /// - 1: GATT caching is used + /// - 0: GATT caching is not used + /// - bit 7: + /// - 1: LE Power Class 1 + /// - 0: LE Power Classe 2-3 + /// - other bits: complete with Options_extension flag + pub options: u8, + /// Reserved for future use - shall be set to 0 + pub hw_version: u8, + // /** + // * Maximum number of connection-oriented channels in initiator mode. + // * Range: 0 .. 64 + // */ + // pub max_coc_initiator_nbr: u8, + // + // /** + // * Minimum transmit power in dBm supported by the Controller. + // * Range: -127 .. 20 + // */ + // pub min_tx_power: i8, + // + // /** + // * Maximum transmit power in dBm supported by the Controller. + // * Range: -127 .. 20 + // */ + // pub max_tx_power: i8, + // + // /** + // * RX model configuration + // * - bit 0: 1: agc_rssi model improved vs RF blockers 0: Legacy agc_rssi model + // * - other bits: reserved ( shall be set to 0) + // */ + // pub rx_model_config: u8, + // + // /* Maximum number of advertising sets. + // * Range: 1 .. 8 with limitation: + // * This parameter is linked to max_adv_data_len such as both compliant with allocated Total memory computed with BLE_EXT_ADV_BUFFER_SIZE based + // * on Max Extended advertising configuration supported. + // * This parameter is considered by the CPU2 when Options has SHCI_C2_BLE_INIT_OPTIONS_EXT_ADV flag set + // */ + // pub max_adv_set_nbr: u8, + // + // /* Maximum advertising data length (in bytes) + // * Range: 31 .. 1650 with limitation: + // * This parameter is linked to max_adv_set_nbr such as both compliant with allocated Total memory computed with BLE_EXT_ADV_BUFFER_SIZE based + // * on Max Extended advertising configuration supported. + // * This parameter is considered by the CPU2 when Options has SHCI_C2_BLE_INIT_OPTIONS_EXT_ADV flag set + // */ + // pub max_adv_data_len: u16, + // + // /* RF TX Path Compensation Value (16-bit signed integer). Units: 0.1 dB. + // * Range: -1280 .. 1280 + // */ + // pub tx_path_compens: i16, + // + // /* RF RX Path Compensation Value (16-bit signed integer). Units: 0.1 dB. + // * Range: -1280 .. 1280 + // */ + // pub rx_path_compens: i16, + // + // /* BLE core specification version (8-bit unsigned integer). + // * values as: 11(5.2), 12(5.3) + // */ + // pub ble_core_version: u8, + // + // /** + // * Options flags extension + // * - bit 0: 1: appearance Writable 0: appearance Read-Only + // * - bit 1: 1: Enhanced ATT supported 0: Enhanced ATT not supported + // * - other bits: reserved ( shall be set to 0) + // */ + // pub options_extension: u8, +} + +impl ShciBleInitCmdParam { + pub fn payload<'a>(&'a self) -> &'a [u8] { + unsafe { slice::from_raw_parts(self as *const _ as *const u8, mem::size_of::()) } + } +} + +impl Default for ShciBleInitCmdParam { + fn default() -> Self { + Self { + p_ble_buffer_address: 0, + ble_buffer_size: 0, + num_attr_record: 68, + num_attr_serv: 8, + attr_value_arr_size: 1344, + num_of_links: 2, + extended_packet_length_enable: 1, + prepare_write_list_size: 0x3A, + block_count: 0x79, + att_mtu: 156, + slave_sca: 500, + master_sca: 0, + ls_source: 1, + max_conn_event_length: 0xFFFFFFFF, + hs_startup_time: 0x148, + viterbi_enable: 1, + options: 0, + hw_version: 0, + } + } +} + +pub const TL_BLE_EVT_CS_PACKET_SIZE: usize = TL_EVT_HEADER_SIZE + TL_CS_EVT_SIZE; +#[allow(dead_code)] // Not used currently but reserved +const TL_BLE_EVT_CS_BUFFER_SIZE: usize = TL_PACKET_HEADER_SIZE + TL_BLE_EVT_CS_PACKET_SIZE; diff --git a/embassy-stm32-wpan/src/sub/ble.rs b/embassy-stm32-wpan/src/sub/ble.rs new file mode 100644 index 000000000..cd32692e1 --- /dev/null +++ b/embassy-stm32-wpan/src/sub/ble.rs @@ -0,0 +1,96 @@ +use core::marker::PhantomData; +use core::ptr; + +use embassy_stm32::ipcc::Ipcc; +use hci::Opcode; + +use crate::cmd::CmdPacket; +use crate::consts::{TlPacketType, TL_BLEEVT_CC_OPCODE, TL_BLEEVT_CS_OPCODE}; +use crate::evt::{EvtBox, EvtPacket, EvtStub}; +use crate::sub::mm; +use crate::tables::{BleTable, BLE_CMD_BUFFER, CS_BUFFER, EVT_QUEUE, HCI_ACL_DATA_BUFFER, TL_BLE_TABLE}; +use crate::unsafe_linked_list::LinkedListNode; +use crate::{channels, evt}; + +pub struct Ble { + phantom: PhantomData, +} + +impl Ble { + pub(crate) fn new() -> Self { + unsafe { + LinkedListNode::init_head(EVT_QUEUE.as_mut_ptr()); + + TL_BLE_TABLE.as_mut_ptr().write_volatile(BleTable { + pcmd_buffer: BLE_CMD_BUFFER.as_mut_ptr().cast(), + pcs_buffer: CS_BUFFER.as_ptr().cast(), + pevt_queue: EVT_QUEUE.as_ptr().cast(), + phci_acl_data_buffer: HCI_ACL_DATA_BUFFER.as_mut_ptr().cast(), + }); + } + + Self { phantom: PhantomData } + } + /// `HW_IPCC_BLE_EvtNot` + pub async fn tl_read(&self) -> EvtBox { + Ipcc::receive(channels::cpu2::IPCC_BLE_EVENT_CHANNEL, || unsafe { + if let Some(node_ptr) = LinkedListNode::remove_head(EVT_QUEUE.as_mut_ptr()) { + Some(EvtBox::new(node_ptr.cast())) + } else { + None + } + }) + .await + } + + /// `TL_BLE_SendCmd` + pub async fn tl_write(&self, opcode: u16, payload: &[u8]) { + Ipcc::send(channels::cpu1::IPCC_BLE_CMD_CHANNEL, || unsafe { + CmdPacket::write_into(BLE_CMD_BUFFER.as_mut_ptr(), TlPacketType::BleCmd, opcode, payload); + }) + .await; + } + + /// `TL_BLE_SendAclData` + pub async fn acl_write(&self, handle: u16, payload: &[u8]) { + Ipcc::send(channels::cpu1::IPCC_HCI_ACL_DATA_CHANNEL, || unsafe { + CmdPacket::write_into( + HCI_ACL_DATA_BUFFER.as_mut_ptr() as *mut _, + TlPacketType::AclData, + handle, + payload, + ); + }) + .await; + } +} + +impl evt::MemoryManager for Ble { + /// SAFETY: passing a pointer to something other than a managed event packet is UB + unsafe fn drop_event_packet(evt: *mut EvtPacket) { + let stub = unsafe { + let p_evt_stub = &(*evt).evt_serial as *const _ as *const EvtStub; + + ptr::read_volatile(p_evt_stub) + }; + + if !(stub.evt_code == TL_BLEEVT_CS_OPCODE || stub.evt_code == TL_BLEEVT_CC_OPCODE) { + mm::MemoryManager::drop_event_packet(evt); + } + } +} + +pub extern crate stm32wb_hci as hci; + +impl hci::Controller for Ble { + async fn controller_write(&mut self, opcode: Opcode, payload: &[u8]) { + self.tl_write(opcode.0, payload).await; + } + + async fn controller_read_into(&self, buf: &mut [u8]) { + let evt_box = self.tl_read().await; + let evt_serial = evt_box.serial(); + + buf[..evt_serial.len()].copy_from_slice(evt_serial); + } +} diff --git a/embassy-stm32-wpan/src/sub/mac.rs b/embassy-stm32-wpan/src/sub/mac.rs new file mode 100644 index 000000000..d9bf4c909 --- /dev/null +++ b/embassy-stm32-wpan/src/sub/mac.rs @@ -0,0 +1,123 @@ +use core::future::poll_fn; +use core::marker::PhantomData; +use core::ptr; +use core::sync::atomic::{AtomicBool, Ordering}; +use core::task::Poll; + +use embassy_futures::poll_once; +use embassy_stm32::ipcc::Ipcc; +use embassy_sync::waitqueue::AtomicWaker; + +use crate::cmd::CmdPacket; +use crate::consts::TlPacketType; +use crate::evt::{EvtBox, EvtPacket}; +use crate::mac::commands::MacCommand; +use crate::mac::event::Event; +use crate::mac::typedefs::MacError; +use crate::tables::{MAC_802_15_4_CMD_BUFFER, MAC_802_15_4_NOTIF_RSP_EVT_BUFFER}; +use crate::{channels, evt}; + +static MAC_WAKER: AtomicWaker = AtomicWaker::new(); +static MAC_EVT_OUT: AtomicBool = AtomicBool::new(false); + +pub struct Mac { + phantom: PhantomData, +} + +impl Mac { + pub(crate) fn new() -> Self { + Self { phantom: PhantomData } + } + + /// `HW_IPCC_MAC_802_15_4_EvtNot` + /// + /// This function will stall if the previous `EvtBox` has not been dropped + pub async fn tl_read(&self) -> EvtBox { + // Wait for the last event box to be dropped + poll_fn(|cx| { + MAC_WAKER.register(cx.waker()); + if MAC_EVT_OUT.load(Ordering::SeqCst) { + Poll::Pending + } else { + Poll::Ready(()) + } + }) + .await; + + // Return a new event box + Ipcc::receive(channels::cpu2::IPCC_MAC_802_15_4_NOTIFICATION_ACK_CHANNEL, || unsafe { + // The closure is not async, therefore the closure must execute to completion (cannot be dropped) + // Therefore, the event box is guaranteed to be cleaned up if it's not leaked + MAC_EVT_OUT.store(true, Ordering::SeqCst); + + Some(EvtBox::new(MAC_802_15_4_NOTIF_RSP_EVT_BUFFER.as_mut_ptr() as *mut _)) + }) + .await + } + + /// `HW_IPCC_MAC_802_15_4_CmdEvtNot` + pub async fn tl_write_and_get_response(&self, opcode: u16, payload: &[u8]) -> u8 { + self.tl_write(opcode, payload).await; + Ipcc::flush(channels::cpu1::IPCC_MAC_802_15_4_CMD_RSP_CHANNEL).await; + + unsafe { + let p_event_packet = MAC_802_15_4_CMD_BUFFER.as_ptr() as *const EvtPacket; + let p_mac_rsp_evt = &((*p_event_packet).evt_serial.evt.payload) as *const u8; + + ptr::read_volatile(p_mac_rsp_evt) + } + } + + /// `TL_MAC_802_15_4_SendCmd` + pub async fn tl_write(&self, opcode: u16, payload: &[u8]) { + Ipcc::send(channels::cpu1::IPCC_MAC_802_15_4_CMD_RSP_CHANNEL, || unsafe { + CmdPacket::write_into( + MAC_802_15_4_CMD_BUFFER.as_mut_ptr(), + TlPacketType::MacCmd, + opcode, + payload, + ); + }) + .await; + } + + pub async fn send_command(&self, cmd: &T) -> Result<(), MacError> + where + T: MacCommand, + { + let response = self.tl_write_and_get_response(T::OPCODE as u16, cmd.payload()).await; + + if response == 0x00 { + Ok(()) + } else { + Err(MacError::from(response)) + } + } + + pub async fn read(&self) -> Event { + Event::new(self.tl_read().await) + } +} + +impl evt::MemoryManager for Mac { + /// SAFETY: passing a pointer to something other than a managed event packet is UB + unsafe fn drop_event_packet(_: *mut EvtPacket) { + // Write the ack + CmdPacket::write_into( + MAC_802_15_4_NOTIF_RSP_EVT_BUFFER.as_mut_ptr() as *mut _, + TlPacketType::OtAck, + 0, + &[], + ); + + // Clear the rx flag + let _ = poll_once(Ipcc::receive::( + channels::cpu2::IPCC_MAC_802_15_4_NOTIFICATION_ACK_CHANNEL, + || None, + )); + + // Allow a new read call + MAC_EVT_OUT.store(false, Ordering::SeqCst); + MAC_WAKER.wake(); + } +} diff --git a/embassy-stm32-wpan/src/sub/mm.rs b/embassy-stm32-wpan/src/sub/mm.rs new file mode 100644 index 000000000..da05ad1dd --- /dev/null +++ b/embassy-stm32-wpan/src/sub/mm.rs @@ -0,0 +1,84 @@ +//! Memory manager routines +use core::future::poll_fn; +use core::marker::PhantomData; +use core::mem::MaybeUninit; +use core::task::Poll; + +use aligned::{Aligned, A4}; +use cortex_m::interrupt; +use embassy_stm32::ipcc::Ipcc; +use embassy_sync::waitqueue::AtomicWaker; + +use crate::consts::POOL_SIZE; +use crate::evt::EvtPacket; +#[cfg(feature = "ble")] +use crate::tables::BLE_SPARE_EVT_BUF; +use crate::tables::{MemManagerTable, EVT_POOL, FREE_BUF_QUEUE, SYS_SPARE_EVT_BUF, TL_MEM_MANAGER_TABLE}; +use crate::unsafe_linked_list::LinkedListNode; +use crate::{channels, evt}; + +static MM_WAKER: AtomicWaker = AtomicWaker::new(); +static mut LOCAL_FREE_BUF_QUEUE: Aligned> = Aligned(MaybeUninit::uninit()); + +pub struct MemoryManager { + phantom: PhantomData, +} + +impl MemoryManager { + pub(crate) fn new() -> Self { + unsafe { + LinkedListNode::init_head(FREE_BUF_QUEUE.as_mut_ptr()); + LinkedListNode::init_head(LOCAL_FREE_BUF_QUEUE.as_mut_ptr()); + + TL_MEM_MANAGER_TABLE.as_mut_ptr().write_volatile(MemManagerTable { + #[cfg(feature = "ble")] + spare_ble_buffer: BLE_SPARE_EVT_BUF.as_ptr().cast(), + #[cfg(not(feature = "ble"))] + spare_ble_buffer: core::ptr::null(), + spare_sys_buffer: SYS_SPARE_EVT_BUF.as_ptr().cast(), + blepool: EVT_POOL.as_ptr().cast(), + blepoolsize: POOL_SIZE as u32, + pevt_free_buffer_queue: FREE_BUF_QUEUE.as_mut_ptr(), + traces_evt_pool: core::ptr::null(), + tracespoolsize: 0, + }); + } + + Self { phantom: PhantomData } + } + + pub async fn run_queue(&self) { + loop { + poll_fn(|cx| unsafe { + MM_WAKER.register(cx.waker()); + if LinkedListNode::is_empty(LOCAL_FREE_BUF_QUEUE.as_mut_ptr()) { + Poll::Pending + } else { + Poll::Ready(()) + } + }) + .await; + + Ipcc::send(channels::cpu1::IPCC_MM_RELEASE_BUFFER_CHANNEL, || { + interrupt::free(|_| unsafe { + // CS required while moving nodes + while let Some(node_ptr) = LinkedListNode::remove_head(LOCAL_FREE_BUF_QUEUE.as_mut_ptr()) { + LinkedListNode::insert_head(FREE_BUF_QUEUE.as_mut_ptr(), node_ptr); + } + }) + }) + .await; + } + } +} + +impl evt::MemoryManager for MemoryManager { + /// SAFETY: passing a pointer to something other than a managed event packet is UB + unsafe fn drop_event_packet(evt: *mut EvtPacket) { + interrupt::free(|_| unsafe { + LinkedListNode::insert_head(LOCAL_FREE_BUF_QUEUE.as_mut_ptr(), evt as *mut _); + }); + + MM_WAKER.wake(); + } +} diff --git a/embassy-stm32-wpan/src/sub/mod.rs b/embassy-stm32-wpan/src/sub/mod.rs new file mode 100644 index 000000000..bee3dbdf1 --- /dev/null +++ b/embassy-stm32-wpan/src/sub/mod.rs @@ -0,0 +1,6 @@ +#[cfg(feature = "ble")] +pub mod ble; +#[cfg(feature = "mac")] +pub mod mac; +pub mod mm; +pub mod sys; diff --git a/embassy-stm32-wpan/src/sub/sys.rs b/embassy-stm32-wpan/src/sub/sys.rs new file mode 100644 index 000000000..c17fd531d --- /dev/null +++ b/embassy-stm32-wpan/src/sub/sys.rs @@ -0,0 +1,106 @@ +use core::marker::PhantomData; +use core::ptr; + +use crate::cmd::CmdPacket; +use crate::consts::TlPacketType; +use crate::evt::{CcEvt, EvtBox, EvtPacket}; +#[allow(unused_imports)] +use crate::shci::{SchiCommandStatus, ShciBleInitCmdParam, ShciOpcode}; +use crate::sub::mm; +use crate::tables::{SysTable, WirelessFwInfoTable}; +use crate::unsafe_linked_list::LinkedListNode; +use crate::{channels, Ipcc, SYSTEM_EVT_QUEUE, SYS_CMD_BUF, TL_DEVICE_INFO_TABLE, TL_SYS_TABLE}; + +pub struct Sys { + phantom: PhantomData, +} + +impl Sys { + /// TL_Sys_Init + pub(crate) fn new() -> Self { + unsafe { + LinkedListNode::init_head(SYSTEM_EVT_QUEUE.as_mut_ptr()); + + TL_SYS_TABLE.as_mut_ptr().write_volatile(SysTable { + pcmd_buffer: SYS_CMD_BUF.as_mut_ptr(), + sys_queue: SYSTEM_EVT_QUEUE.as_ptr(), + }); + } + + Self { phantom: PhantomData } + } + + /// Returns CPU2 wireless firmware information (if present). + pub fn wireless_fw_info(&self) -> Option { + let info = unsafe { TL_DEVICE_INFO_TABLE.as_mut_ptr().read_volatile().wireless_fw_info_table }; + + // Zero version indicates that CPU2 wasn't active and didn't fill the information table + if info.version != 0 { + Some(info) + } else { + None + } + } + + pub async fn write(&self, opcode: ShciOpcode, payload: &[u8]) { + Ipcc::send(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL, || unsafe { + CmdPacket::write_into(SYS_CMD_BUF.as_mut_ptr(), TlPacketType::SysCmd, opcode as u16, payload); + }) + .await; + } + + /// `HW_IPCC_SYS_CmdEvtNot` + pub async fn write_and_get_response(&self, opcode: ShciOpcode, payload: &[u8]) -> Result { + self.write(opcode, payload).await; + Ipcc::flush(channels::cpu1::IPCC_SYSTEM_CMD_RSP_CHANNEL).await; + + unsafe { + let p_event_packet = SYS_CMD_BUF.as_ptr() as *const EvtPacket; + let p_command_event = &((*p_event_packet).evt_serial.evt.payload) as *const _ as *const CcEvt; + let p_payload = &((*p_command_event).payload) as *const u8; + + ptr::read_volatile(p_payload).try_into() + } + } + + #[cfg(feature = "mac")] + pub async fn shci_c2_mac_802_15_4_init(&self) -> Result { + use crate::tables::{ + Mac802_15_4Table, TracesTable, MAC_802_15_4_CMD_BUFFER, MAC_802_15_4_NOTIF_RSP_EVT_BUFFER, + TL_MAC_802_15_4_TABLE, TL_TRACES_TABLE, TRACES_EVT_QUEUE, + }; + + unsafe { + LinkedListNode::init_head(TRACES_EVT_QUEUE.as_mut_ptr() as *mut _); + + TL_TRACES_TABLE.as_mut_ptr().write_volatile(TracesTable { + traces_queue: TRACES_EVT_QUEUE.as_ptr() as *const _, + }); + + TL_MAC_802_15_4_TABLE.as_mut_ptr().write_volatile(Mac802_15_4Table { + p_cmdrsp_buffer: MAC_802_15_4_CMD_BUFFER.as_mut_ptr().cast(), + p_notack_buffer: MAC_802_15_4_NOTIF_RSP_EVT_BUFFER.as_mut_ptr().cast(), + evt_queue: core::ptr::null_mut(), + }); + }; + + self.write_and_get_response(ShciOpcode::Mac802_15_4Init, &[]).await + } + + #[cfg(feature = "ble")] + pub async fn shci_c2_ble_init(&self, param: ShciBleInitCmdParam) -> Result { + self.write_and_get_response(ShciOpcode::BleInit, param.payload()).await + } + + /// `HW_IPCC_SYS_EvtNot` + pub async fn read(&self) -> EvtBox { + Ipcc::receive(channels::cpu2::IPCC_SYSTEM_EVENT_CHANNEL, || unsafe { + if let Some(node_ptr) = LinkedListNode::remove_head(SYSTEM_EVT_QUEUE.as_mut_ptr()) { + Some(EvtBox::new(node_ptr.cast())) + } else { + None + } + }) + .await + } +} diff --git a/embassy-stm32-wpan/src/tables.rs b/embassy-stm32-wpan/src/tables.rs new file mode 100644 index 000000000..f2c250527 --- /dev/null +++ b/embassy-stm32-wpan/src/tables.rs @@ -0,0 +1,276 @@ +use core::mem::MaybeUninit; + +use aligned::{Aligned, A4}; +use bit_field::BitField; + +use crate::cmd::{AclDataPacket, CmdPacket}; +#[cfg(feature = "mac")] +use crate::consts::C_SIZE_CMD_STRING; +use crate::consts::{POOL_SIZE, TL_CS_EVT_SIZE, TL_EVT_HEADER_SIZE, TL_PACKET_HEADER_SIZE}; +use crate::unsafe_linked_list::LinkedListNode; + +#[derive(Debug, Copy, Clone)] +#[repr(C, packed)] +pub struct SafeBootInfoTable { + version: u32, +} + +#[derive(Debug, Copy, Clone)] +#[repr(C, packed)] +pub struct RssInfoTable { + pub version: u32, + pub memory_size: u32, + pub rss_info: u32, +} + +/** + * Version + * [0:3] = Build - 0: Untracked - 15:Released - x: Tracked version + * [4:7] = branch - 0: Mass Market - x: ... + * [8:15] = Subversion + * [16:23] = Version minor + * [24:31] = Version major + * + * Memory Size + * [0:7] = Flash ( Number of 4k sector) + * [8:15] = Reserved ( Shall be set to 0 - may be used as flash extension ) + * [16:23] = SRAM2b ( Number of 1k sector) + * [24:31] = SRAM2a ( Number of 1k sector) + */ +#[derive(Debug, Copy, Clone)] +#[repr(C, packed)] +pub struct WirelessFwInfoTable { + pub version: u32, + pub memory_size: u32, + pub thread_info: u32, + pub ble_info: u32, +} + +impl WirelessFwInfoTable { + pub fn version_major(&self) -> u8 { + let version = self.version; + (version.get_bits(24..31) & 0xff) as u8 + } + + pub fn version_minor(&self) -> u8 { + let version = self.version; + (version.clone().get_bits(16..23) & 0xff) as u8 + } + + pub fn subversion(&self) -> u8 { + let version = self.version; + (version.clone().get_bits(8..15) & 0xff) as u8 + } + + /// Size of FLASH, expressed in number of 4K sectors. + pub fn flash_size(&self) -> u8 { + let memory_size = self.memory_size; + (memory_size.clone().get_bits(0..7) & 0xff) as u8 + } + + /// Size of SRAM2a, expressed in number of 1K sectors. + pub fn sram2a_size(&self) -> u8 { + let memory_size = self.memory_size; + (memory_size.clone().get_bits(24..31) & 0xff) as u8 + } + + /// Size of SRAM2b, expressed in number of 1K sectors. + pub fn sram2b_size(&self) -> u8 { + let memory_size = self.memory_size; + (memory_size.clone().get_bits(16..23) & 0xff) as u8 + } +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct DeviceInfoTable { + pub safe_boot_info_table: SafeBootInfoTable, + pub rss_info_table: RssInfoTable, + pub wireless_fw_info_table: WirelessFwInfoTable, +} + +#[derive(Debug)] +#[repr(C)] +pub struct BleTable { + pub pcmd_buffer: *mut CmdPacket, + pub pcs_buffer: *const u8, + pub pevt_queue: *const u8, + pub phci_acl_data_buffer: *mut AclDataPacket, +} + +#[derive(Debug)] +#[repr(C)] +pub struct ThreadTable { + pub nostack_buffer: *const u8, + pub clicmdrsp_buffer: *const u8, + pub otcmdrsp_buffer: *const u8, +} + +#[derive(Debug)] +#[repr(C)] +pub struct LldTestsTable { + pub clicmdrsp_buffer: *const u8, + pub m0cmd_buffer: *const u8, +} + +// TODO: use later +#[derive(Debug)] +#[repr(C)] +pub struct BleLldTable { + pub cmdrsp_buffer: *const u8, + pub m0cmd_buffer: *const u8, +} + +// TODO: use later +#[derive(Debug)] +#[repr(C)] +pub struct ZigbeeTable { + pub notif_m0_to_m4_buffer: *const u8, + pub appli_cmd_m4_to_m0_bufer: *const u8, + pub request_m0_to_m4_buffer: *const u8, +} + +#[derive(Debug)] +#[repr(C)] +pub struct SysTable { + pub pcmd_buffer: *mut CmdPacket, + pub sys_queue: *const LinkedListNode, +} + +#[derive(Debug)] +#[repr(C)] +pub struct MemManagerTable { + pub spare_ble_buffer: *const u8, + pub spare_sys_buffer: *const u8, + + pub blepool: *const u8, + pub blepoolsize: u32, + + pub pevt_free_buffer_queue: *mut LinkedListNode, + + pub traces_evt_pool: *const u8, + pub tracespoolsize: u32, +} + +#[derive(Debug)] +#[repr(C)] +pub struct TracesTable { + pub traces_queue: *const u8, +} + +#[derive(Debug)] +#[repr(C)] +pub struct Mac802_15_4Table { + pub p_cmdrsp_buffer: *const u8, + pub p_notack_buffer: *const u8, + pub evt_queue: *const u8, +} + +/// Reference table. Contains pointers to all other tables. +#[derive(Debug, Copy, Clone)] +#[repr(C)] +pub struct RefTable { + pub device_info_table: *const DeviceInfoTable, + pub ble_table: *const BleTable, + pub thread_table: *const ThreadTable, + pub sys_table: *const SysTable, + pub mem_manager_table: *const MemManagerTable, + pub traces_table: *const TracesTable, + pub mac_802_15_4_table: *const Mac802_15_4Table, + pub zigbee_table: *const ZigbeeTable, + pub lld_tests_table: *const LldTestsTable, + pub ble_lld_table: *const BleLldTable, +} + +// --------------------- ref table --------------------- +#[link_section = "TL_REF_TABLE"] +pub static mut TL_REF_TABLE: MaybeUninit = MaybeUninit::uninit(); + +#[link_section = "MB_MEM1"] +pub static mut TL_DEVICE_INFO_TABLE: Aligned> = Aligned(MaybeUninit::uninit()); + +#[link_section = "MB_MEM1"] +pub static mut TL_BLE_TABLE: Aligned> = Aligned(MaybeUninit::uninit()); + +#[link_section = "MB_MEM1"] +pub static mut TL_THREAD_TABLE: Aligned> = Aligned(MaybeUninit::uninit()); + +#[link_section = "MB_MEM1"] +pub static mut TL_LLD_TESTS_TABLE: Aligned> = Aligned(MaybeUninit::uninit()); + +#[link_section = "MB_MEM1"] +pub static mut TL_BLE_LLD_TABLE: Aligned> = Aligned(MaybeUninit::uninit()); + +#[link_section = "MB_MEM1"] +pub static mut TL_SYS_TABLE: Aligned> = Aligned(MaybeUninit::uninit()); + +#[link_section = "MB_MEM1"] +pub static mut TL_MEM_MANAGER_TABLE: Aligned> = Aligned(MaybeUninit::uninit()); + +#[link_section = "MB_MEM1"] +pub static mut TL_TRACES_TABLE: Aligned> = Aligned(MaybeUninit::uninit()); + +#[link_section = "MB_MEM1"] +pub static mut TL_MAC_802_15_4_TABLE: Aligned> = Aligned(MaybeUninit::uninit()); + +#[link_section = "MB_MEM1"] +pub static mut TL_ZIGBEE_TABLE: Aligned> = Aligned(MaybeUninit::uninit()); + +// --------------------- tables --------------------- +#[link_section = "MB_MEM1"] +pub static mut FREE_BUF_QUEUE: Aligned> = Aligned(MaybeUninit::uninit()); + +#[allow(dead_code)] +#[link_section = "MB_MEM1"] +pub static mut TRACES_EVT_QUEUE: Aligned> = Aligned(MaybeUninit::uninit()); + +#[link_section = "MB_MEM2"] +pub static mut CS_BUFFER: Aligned> = + Aligned(MaybeUninit::uninit()); + +#[link_section = "MB_MEM2"] +pub static mut EVT_QUEUE: Aligned> = Aligned(MaybeUninit::uninit()); + +#[link_section = "MB_MEM2"] +pub static mut SYSTEM_EVT_QUEUE: Aligned> = Aligned(MaybeUninit::uninit()); + +// --------------------- app tables --------------------- +#[cfg(feature = "mac")] +#[link_section = "MB_MEM2"] +pub static mut MAC_802_15_4_CMD_BUFFER: Aligned> = Aligned(MaybeUninit::uninit()); + +#[cfg(feature = "mac")] +#[link_section = "MB_MEM2"] +pub static mut MAC_802_15_4_NOTIF_RSP_EVT_BUFFER: MaybeUninit< + Aligned, +> = MaybeUninit::uninit(); + +#[link_section = "MB_MEM2"] +pub static mut EVT_POOL: Aligned> = Aligned(MaybeUninit::uninit()); + +#[link_section = "MB_MEM2"] +pub static mut SYS_CMD_BUF: Aligned> = Aligned(MaybeUninit::uninit()); + +#[link_section = "MB_MEM2"] +pub static mut SYS_SPARE_EVT_BUF: Aligned> = + Aligned(MaybeUninit::uninit()); + +#[cfg(feature = "mac")] +#[link_section = "MB_MEM2"] +pub static mut MAC_802_15_4_CNFINDNOT: Aligned> = + Aligned(MaybeUninit::uninit()); + +#[cfg(feature = "ble")] +#[link_section = "MB_MEM1"] +pub static mut BLE_CMD_BUFFER: Aligned> = Aligned(MaybeUninit::uninit()); + +#[cfg(feature = "ble")] +#[link_section = "MB_MEM2"] +pub static mut BLE_SPARE_EVT_BUF: Aligned> = + Aligned(MaybeUninit::uninit()); + +#[cfg(feature = "ble")] +#[link_section = "MB_MEM2"] +// fuck these "magic" numbers from ST ---v---v +pub static mut HCI_ACL_DATA_BUFFER: Aligned> = + Aligned(MaybeUninit::uninit()); diff --git a/embassy-stm32-wpan/src/unsafe_linked_list.rs b/embassy-stm32-wpan/src/unsafe_linked_list.rs new file mode 100644 index 000000000..d8bc29763 --- /dev/null +++ b/embassy-stm32-wpan/src/unsafe_linked_list.rs @@ -0,0 +1,257 @@ +//! Unsafe linked list. +//! Translated from ST's C by `c2rust` tool. + +#![allow( + dead_code, + mutable_transmutes, + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + unused_assignments, + unused_mut +)] + +use core::ptr; + +use cortex_m::interrupt; + +#[derive(Copy, Clone)] +#[repr(C, packed(4))] +pub struct LinkedListNode { + pub next: *mut LinkedListNode, + pub prev: *mut LinkedListNode, +} + +impl Default for LinkedListNode { + fn default() -> Self { + LinkedListNode { + next: core::ptr::null_mut(), + prev: core::ptr::null_mut(), + } + } +} + +impl LinkedListNode { + pub unsafe fn init_head(mut p_list_head: *mut LinkedListNode) { + ptr::write_volatile( + p_list_head, + LinkedListNode { + next: p_list_head, + prev: p_list_head, + }, + ); + } + + pub unsafe fn is_empty(mut p_list_head: *mut LinkedListNode) -> bool { + interrupt::free(|_| ptr::read_volatile(p_list_head).next == p_list_head) + } + + /// Insert `node` after `list_head` and before the next node + pub unsafe fn insert_head(mut p_list_head: *mut LinkedListNode, mut p_node: *mut LinkedListNode) { + interrupt::free(|_| { + let mut list_head = ptr::read_volatile(p_list_head); + if p_list_head != list_head.next { + let mut node_next = ptr::read_volatile(list_head.next); + let node = LinkedListNode { + next: list_head.next, + prev: p_list_head, + }; + + list_head.next = p_node; + node_next.prev = p_node; + + // All nodes must be written because they will all be seen by another core + ptr::write_volatile(p_node, node); + ptr::write_volatile(node.next, node_next); + ptr::write_volatile(p_list_head, list_head); + } else { + let node = LinkedListNode { + next: list_head.next, + prev: p_list_head, + }; + + list_head.next = p_node; + list_head.prev = p_node; + + // All nodes must be written because they will all be seen by another core + ptr::write_volatile(p_node, node); + ptr::write_volatile(p_list_head, list_head); + } + }); + } + + /// Insert `node` before `list_tail` and after the second-to-last node + pub unsafe fn insert_tail(mut p_list_tail: *mut LinkedListNode, mut p_node: *mut LinkedListNode) { + interrupt::free(|_| { + let mut list_tail = ptr::read_volatile(p_list_tail); + if p_list_tail != list_tail.prev { + let mut node_prev = ptr::read_volatile(list_tail.prev); + let node = LinkedListNode { + next: p_list_tail, + prev: list_tail.prev, + }; + + list_tail.prev = p_node; + node_prev.next = p_node; + + // All nodes must be written because they will all be seen by another core + ptr::write_volatile(p_node, node); + ptr::write_volatile(node.prev, node_prev); + ptr::write_volatile(p_list_tail, list_tail); + } else { + let node = LinkedListNode { + next: p_list_tail, + prev: list_tail.prev, + }; + + list_tail.prev = p_node; + list_tail.next = p_node; + + // All nodes must be written because they will all be seen by another core + ptr::write_volatile(p_node, node); + ptr::write_volatile(p_list_tail, list_tail); + } + }); + } + + /// Remove `node` from the linked list + pub unsafe fn remove_node(mut p_node: *mut LinkedListNode) { + interrupt::free(|_| { + // trace!("remove node: {:x}", p_node); + // apparently linked list nodes are not always aligned. + // if more hardfaults occur, more of these may need to be converted to unaligned. + let node = ptr::read_unaligned(p_node); + // trace!("remove node: prev/next {:x}/{:x}", node.prev, node.next); + + if node.next != node.prev { + let mut node_next = ptr::read_volatile(node.next); + let mut node_prev = ptr::read_volatile(node.prev); + + node_prev.next = node.next; + node_next.prev = node.prev; + + ptr::write_volatile(node.next, node_next); + ptr::write_volatile(node.prev, node_prev); + } else { + let mut node_next = ptr::read_volatile(node.next); + + node_next.next = node.next; + node_next.prev = node.prev; + + ptr::write_volatile(node.next, node_next); + } + }); + } + + /// Remove `list_head` and return a pointer to the `node`. + pub unsafe fn remove_head(mut p_list_head: *mut LinkedListNode) -> Option<*mut LinkedListNode> { + interrupt::free(|_| { + let list_head = ptr::read_volatile(p_list_head); + + if list_head.next == p_list_head { + None + } else { + // Allowed because a removed node is not seen by another core + let p_node = list_head.next; + Self::remove_node(p_node); + + Some(p_node) + } + }) + } + + /// Remove `list_tail` and return a pointer to the `node`. + pub unsafe fn remove_tail(mut p_list_tail: *mut LinkedListNode) -> Option<*mut LinkedListNode> { + interrupt::free(|_| { + let list_tail = ptr::read_volatile(p_list_tail); + + if list_tail.prev == p_list_tail { + None + } else { + // Allowed because a removed node is not seen by another core + let p_node = list_tail.prev; + Self::remove_node(p_node); + + Some(p_node) + } + }) + } + + pub unsafe fn insert_node_after(mut node: *mut LinkedListNode, mut ref_node: *mut LinkedListNode) { + interrupt::free(|_| { + (*node).next = (*ref_node).next; + (*node).prev = ref_node; + (*ref_node).next = node; + (*(*node).next).prev = node; + }); + + todo!("this function has not been converted to volatile semantics"); + } + + pub unsafe fn insert_node_before(mut node: *mut LinkedListNode, mut ref_node: *mut LinkedListNode) { + interrupt::free(|_| { + (*node).next = ref_node; + (*node).prev = (*ref_node).prev; + (*ref_node).prev = node; + (*(*node).prev).next = node; + }); + + todo!("this function has not been converted to volatile semantics"); + } + + pub unsafe fn get_size(mut list_head: *mut LinkedListNode) -> usize { + interrupt::free(|_| { + let mut size = 0; + let mut temp: *mut LinkedListNode = core::ptr::null_mut::(); + + temp = (*list_head).next; + while temp != list_head { + size += 1; + temp = (*temp).next + } + + size + }); + + todo!("this function has not been converted to volatile semantics"); + } + + pub unsafe fn get_next_node(mut p_ref_node: *mut LinkedListNode) -> *mut LinkedListNode { + interrupt::free(|_| { + let ref_node = ptr::read_volatile(p_ref_node); + + // Allowed because a removed node is not seen by another core + ref_node.next + }) + } + + pub unsafe fn get_prev_node(mut p_ref_node: *mut LinkedListNode) -> *mut LinkedListNode { + interrupt::free(|_| { + let ref_node = ptr::read_volatile(p_ref_node); + + // Allowed because a removed node is not seen by another core + ref_node.prev + }) + } +} + +#[allow(dead_code)] +unsafe fn debug_linked_list(mut p_node: *mut LinkedListNode) { + info!("iterating list from node: {:x}", p_node); + let mut p_current_node = p_node; + let mut i = 0; + loop { + let current_node = ptr::read_volatile(p_current_node); + info!( + "node (prev, current, next): {:x}, {:x}, {:x}", + current_node.prev, p_current_node, current_node.next + ); + + i += 1; + if i > 10 || current_node.next == p_node { + break; + } + + p_current_node = current_node.next; + } +} diff --git a/embassy-stm32-wpan/tl_mbox.x.in b/embassy-stm32-wpan/tl_mbox.x.in new file mode 100644 index 000000000..b6eecb429 --- /dev/null +++ b/embassy-stm32-wpan/tl_mbox.x.in @@ -0,0 +1,15 @@ +MEMORY +{ + RAM_SHARED (xrw) : ORIGIN = 0x20030000, LENGTH = 10K +} + +/* + * Scatter the mailbox interface memory sections in shared memory + */ +SECTIONS +{ + TL_REF_TABLE (NOLOAD) : { *(TL_REF_TABLE) } >RAM_SHARED + + MB_MEM1 (NOLOAD) : { *(MB_MEM1) } >RAM_SHARED + MB_MEM2 (NOLOAD) : { _sMB_MEM2 = . ; *(MB_MEM2) ; _eMB_MEM2 = . ; } >RAM_SHARED +} diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index 7a8e5c59b..ec934e8be 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml @@ -2,24 +2,24 @@ name = "embassy-stm32" version = "0.1.0" edition = "2021" +license = "MIT OR Apache-2.0" [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-stm32-v$VERSION/embassy-stm32/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-stm32/src/" -# TODO: sdmmc -# TODO: net -# TODO: subghz -features = ["nightly", "defmt", "unstable-pac", "unstable-traits", "exti", "time-driver-any", "embassy-time/tick-32768hz"] +features = ["nightly", "defmt", "unstable-pac", "unstable-traits", "exti", "time-driver-any", "time"] flavors = [ { regex_feature = "stm32f0.*", target = "thumbv6m-none-eabi" }, { regex_feature = "stm32f1.*", target = "thumbv7m-none-eabi" }, { regex_feature = "stm32f2.*", target = "thumbv7m-none-eabi" }, { regex_feature = "stm32f3.*", target = "thumbv7em-none-eabi" }, - { regex_feature = "stm32f42.*", target = "thumbv7em-none-eabi" }, + { regex_feature = "stm32f4.*", target = "thumbv7em-none-eabi" }, { regex_feature = "stm32f7.*", target = "thumbv7em-none-eabi" }, + { regex_feature = "stm32c0.*", target = "thumbv6m-none-eabi" }, { regex_feature = "stm32g0.*", target = "thumbv6m-none-eabi" }, { regex_feature = "stm32g4.*", target = "thumbv7em-none-eabi" }, + { regex_feature = "stm32h5.*", target = "thumbv8m.main-none-eabihf" }, { regex_feature = "stm32h7.*", target = "thumbv7em-none-eabi" }, { regex_feature = "stm32l0.*", target = "thumbv6m-none-eabi" }, { regex_feature = "stm32l1.*", target = "thumbv7m-none-eabi" }, @@ -31,21 +31,21 @@ flavors = [ ] [dependencies] -embassy-sync = { version = "0.1.0", path = "../embassy-sync" } -embassy-executor = { version = "0.1.0", path = "../embassy-executor" } -embassy-time = { version = "0.1.0", path = "../embassy-time", optional = true } -embassy-cortex-m = { version = "0.1.0", path = "../embassy-cortex-m", features = ["prio-bits-4"]} -embassy-hal-common = {version = "0.1.0", path = "../embassy-hal-common" } +embassy-sync = { version = "0.2.0", path = "../embassy-sync" } +embassy-time = { version = "0.1.2", path = "../embassy-time", optional = true } +embassy-futures = { version = "0.1.0", path = "../embassy-futures" } +embassy-hal-common = {version = "0.1.0", path = "../embassy-hal-common", features = ["cortex-m", "prio-bits-4"] } embassy-embedded-hal = {version = "0.1.0", path = "../embassy-embedded-hal" } -embassy-net = { version = "0.1.0", path = "../embassy-net", optional = true } -embassy-usb = {version = "0.1.0", path = "../embassy-usb", optional = true } +embassy-net-driver = { version = "0.1.0", path = "../embassy-net-driver" } +embassy-usb-driver = {version = "0.1.0", path = "../embassy-usb-driver", optional = true } embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["unproven"] } -embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.8", optional = true} -embedded-hal-async = { version = "0.1.0-alpha.1", optional = true} +embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11", optional = true} +embedded-hal-async = { version = "=0.2.0-alpha.2", optional = true} +embedded-hal-nb = { version = "=1.0.0-alpha.3", optional = true} embedded-storage = "0.3.0" -embedded-storage-async = { version = "0.3.0", optional = true } +embedded-storage-async = { version = "0.4.0", optional = true } defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } @@ -54,34 +54,42 @@ cortex-m = "0.7.6" futures = { version = "0.3.17", default-features = false, features = ["async-await"] } rand_core = "0.6.3" sdio-host = "0.5.0" -embedded-sdmmc = { git = "https://github.com/thalesfragoso/embedded-sdmmc-rs", branch = "async", optional = true } +embedded-sdmmc = { git = "https://github.com/embassy-rs/embedded-sdmmc-rs", rev = "a4f293d3a6f72158385f79c98634cb8a14d0d2fc", optional = true } critical-section = "1.1" atomic-polyfill = "1.0.1" -stm32-metapac = { version = "0.1.0", path = "../stm32-metapac", features = ["rt"] } +stm32-metapac = "12" vcell = "0.1.3" bxcan = "0.7.0" nb = "1.0.0" stm32-fmc = "0.2.4" seq-macro = "0.3.0" cfg-if = "1.0.0" -embedded-io = { version = "0.3.0", features = ["async"], optional = true } +embedded-io = { version = "0.4.0", features = ["async"], optional = true } +chrono = { version = "^0.4", default-features = false, optional = true} +bit_field = "0.10.2" + +[dev-dependencies] +critical-section = { version = "1.1", features = ["std"] } [build-dependencies] proc-macro2 = "1.0.36" quote = "1.0.15" -stm32-metapac = { version = "0.1.0", path = "../stm32-metapac", default-features = false, features = ["metadata"]} +stm32-metapac = { version = "12", default-features = false, features = ["metadata"]} [features] -defmt = ["dep:defmt", "bxcan/unstable-defmt", "embassy-sync/defmt", "embassy-executor/defmt", "embassy-embedded-hal/defmt", "embedded-io?/defmt", "embassy-usb?/defmt"] -sdmmc-rs = ["embedded-sdmmc"] -net = ["embassy-net" ] +default = ["rt"] +rt = ["stm32-metapac/rt"] + +defmt = ["dep:defmt", "bxcan/unstable-defmt", "embassy-sync/defmt", "embassy-embedded-hal/defmt", "embassy-hal-common/defmt", "embedded-io?/defmt", "embassy-usb-driver?/defmt", "embassy-net-driver/defmt"] memory-x = ["stm32-metapac/memory-x"] -subghz = [] exti = [] +# Enables additional driver features that depend on embassy-time +time = ["dep:embassy-time"] + # Features starting with `_` are for internal use only. They're not intended # to be enabled by other crates, and are not covered by semver guarantees. -_time-driver = ["dep:embassy-time"] +_time-driver = ["time"] time-driver-any = ["_time-driver"] time-driver-tim2 = ["_time-driver"] time-driver-tim3 = ["_time-driver"] @@ -91,7 +99,7 @@ time-driver-tim12 = ["_time-driver"] time-driver-tim15 = ["_time-driver"] # Enable nightly-only features -nightly = ["embassy-executor/nightly", "embedded-hal-1", "embedded-hal-async", "embedded-storage-async", "dep:embedded-io", "dep:embassy-usb", "embassy-embedded-hal/nightly"] +nightly = ["embedded-hal-1", "embedded-hal-async", "embedded-storage-async", "dep:embedded-io", "dep:embassy-usb-driver", "embassy-embedded-hal/nightly"] # Reexport stm32-metapac at `embassy_stm32::pac`. # This is unstable because semver-minor (non-breaking) releases of embassy-stm32 may major-bump (breaking) the stm32-metapac version. @@ -101,10 +109,22 @@ unstable-pac = [] # Implement embedded-hal 1.0 alpha traits. # Implement embedded-hal-async traits if `nightly` is set as well. -unstable-traits = ["embedded-hal-1"] +unstable-traits = ["embedded-hal-1", "dep:embedded-hal-nb"] -# BEGIN GENERATED FEATURES -# Generated by stm32-gen-features. DO NOT EDIT. +# Chip-selection features +stm32c011d6 = [ "stm32-metapac/stm32c011d6" ] +stm32c011f4 = [ "stm32-metapac/stm32c011f4" ] +stm32c011f6 = [ "stm32-metapac/stm32c011f6" ] +stm32c011j4 = [ "stm32-metapac/stm32c011j4" ] +stm32c011j6 = [ "stm32-metapac/stm32c011j6" ] +stm32c031c4 = [ "stm32-metapac/stm32c031c4" ] +stm32c031c6 = [ "stm32-metapac/stm32c031c6" ] +stm32c031f4 = [ "stm32-metapac/stm32c031f4" ] +stm32c031f6 = [ "stm32-metapac/stm32c031f6" ] +stm32c031g4 = [ "stm32-metapac/stm32c031g4" ] +stm32c031g6 = [ "stm32-metapac/stm32c031g6" ] +stm32c031k4 = [ "stm32-metapac/stm32c031k4" ] +stm32c031k6 = [ "stm32-metapac/stm32c031k6" ] stm32f030c6 = [ "stm32-metapac/stm32f030c6" ] stm32f030c8 = [ "stm32-metapac/stm32f030c8" ] stm32f030cc = [ "stm32-metapac/stm32f030cc" ] @@ -814,6 +834,37 @@ stm32g4a1ke = [ "stm32-metapac/stm32g4a1ke" ] stm32g4a1me = [ "stm32-metapac/stm32g4a1me" ] stm32g4a1re = [ "stm32-metapac/stm32g4a1re" ] stm32g4a1ve = [ "stm32-metapac/stm32g4a1ve" ] +stm32h503cb = [ "stm32-metapac/stm32h503cb" ] +stm32h503eb = [ "stm32-metapac/stm32h503eb" ] +stm32h503kb = [ "stm32-metapac/stm32h503kb" ] +stm32h503rb = [ "stm32-metapac/stm32h503rb" ] +stm32h562ag = [ "stm32-metapac/stm32h562ag" ] +stm32h562ai = [ "stm32-metapac/stm32h562ai" ] +stm32h562ig = [ "stm32-metapac/stm32h562ig" ] +stm32h562ii = [ "stm32-metapac/stm32h562ii" ] +stm32h562rg = [ "stm32-metapac/stm32h562rg" ] +stm32h562ri = [ "stm32-metapac/stm32h562ri" ] +stm32h562vg = [ "stm32-metapac/stm32h562vg" ] +stm32h562vi = [ "stm32-metapac/stm32h562vi" ] +stm32h562zg = [ "stm32-metapac/stm32h562zg" ] +stm32h562zi = [ "stm32-metapac/stm32h562zi" ] +stm32h563ag = [ "stm32-metapac/stm32h563ag" ] +stm32h563ai = [ "stm32-metapac/stm32h563ai" ] +stm32h563ig = [ "stm32-metapac/stm32h563ig" ] +stm32h563ii = [ "stm32-metapac/stm32h563ii" ] +stm32h563mi = [ "stm32-metapac/stm32h563mi" ] +stm32h563rg = [ "stm32-metapac/stm32h563rg" ] +stm32h563ri = [ "stm32-metapac/stm32h563ri" ] +stm32h563vg = [ "stm32-metapac/stm32h563vg" ] +stm32h563vi = [ "stm32-metapac/stm32h563vi" ] +stm32h563zg = [ "stm32-metapac/stm32h563zg" ] +stm32h563zi = [ "stm32-metapac/stm32h563zi" ] +stm32h573ai = [ "stm32-metapac/stm32h573ai" ] +stm32h573ii = [ "stm32-metapac/stm32h573ii" ] +stm32h573mi = [ "stm32-metapac/stm32h573mi" ] +stm32h573ri = [ "stm32-metapac/stm32h573ri" ] +stm32h573vi = [ "stm32-metapac/stm32h573vi" ] +stm32h573zi = [ "stm32-metapac/stm32h573zi" ] stm32h723ve = [ "stm32-metapac/stm32h723ve" ] stm32h723vg = [ "stm32-metapac/stm32h723vg" ] stm32h723ze = [ "stm32-metapac/stm32h723ze" ] @@ -1053,89 +1104,89 @@ stm32l083rz = [ "stm32-metapac/stm32l083rz" ] stm32l083v8 = [ "stm32-metapac/stm32l083v8" ] stm32l083vb = [ "stm32-metapac/stm32l083vb" ] stm32l083vz = [ "stm32-metapac/stm32l083vz" ] -stm32l100c6-a = [ "stm32-metapac/stm32l100c6-a" ] stm32l100c6 = [ "stm32-metapac/stm32l100c6" ] -stm32l100r8-a = [ "stm32-metapac/stm32l100r8-a" ] +stm32l100c6-a = [ "stm32-metapac/stm32l100c6-a" ] stm32l100r8 = [ "stm32-metapac/stm32l100r8" ] -stm32l100rb-a = [ "stm32-metapac/stm32l100rb-a" ] +stm32l100r8-a = [ "stm32-metapac/stm32l100r8-a" ] stm32l100rb = [ "stm32-metapac/stm32l100rb" ] +stm32l100rb-a = [ "stm32-metapac/stm32l100rb-a" ] stm32l100rc = [ "stm32-metapac/stm32l100rc" ] -stm32l151c6-a = [ "stm32-metapac/stm32l151c6-a" ] stm32l151c6 = [ "stm32-metapac/stm32l151c6" ] -stm32l151c8-a = [ "stm32-metapac/stm32l151c8-a" ] +stm32l151c6-a = [ "stm32-metapac/stm32l151c6-a" ] stm32l151c8 = [ "stm32-metapac/stm32l151c8" ] -stm32l151cb-a = [ "stm32-metapac/stm32l151cb-a" ] +stm32l151c8-a = [ "stm32-metapac/stm32l151c8-a" ] stm32l151cb = [ "stm32-metapac/stm32l151cb" ] +stm32l151cb-a = [ "stm32-metapac/stm32l151cb-a" ] stm32l151cc = [ "stm32-metapac/stm32l151cc" ] stm32l151qc = [ "stm32-metapac/stm32l151qc" ] stm32l151qd = [ "stm32-metapac/stm32l151qd" ] stm32l151qe = [ "stm32-metapac/stm32l151qe" ] -stm32l151r6-a = [ "stm32-metapac/stm32l151r6-a" ] stm32l151r6 = [ "stm32-metapac/stm32l151r6" ] -stm32l151r8-a = [ "stm32-metapac/stm32l151r8-a" ] +stm32l151r6-a = [ "stm32-metapac/stm32l151r6-a" ] stm32l151r8 = [ "stm32-metapac/stm32l151r8" ] -stm32l151rb-a = [ "stm32-metapac/stm32l151rb-a" ] +stm32l151r8-a = [ "stm32-metapac/stm32l151r8-a" ] stm32l151rb = [ "stm32-metapac/stm32l151rb" ] -stm32l151rc-a = [ "stm32-metapac/stm32l151rc-a" ] +stm32l151rb-a = [ "stm32-metapac/stm32l151rb-a" ] stm32l151rc = [ "stm32-metapac/stm32l151rc" ] +stm32l151rc-a = [ "stm32-metapac/stm32l151rc-a" ] stm32l151rd = [ "stm32-metapac/stm32l151rd" ] stm32l151re = [ "stm32-metapac/stm32l151re" ] stm32l151uc = [ "stm32-metapac/stm32l151uc" ] -stm32l151v8-a = [ "stm32-metapac/stm32l151v8-a" ] stm32l151v8 = [ "stm32-metapac/stm32l151v8" ] -stm32l151vb-a = [ "stm32-metapac/stm32l151vb-a" ] +stm32l151v8-a = [ "stm32-metapac/stm32l151v8-a" ] stm32l151vb = [ "stm32-metapac/stm32l151vb" ] -stm32l151vc-a = [ "stm32-metapac/stm32l151vc-a" ] +stm32l151vb-a = [ "stm32-metapac/stm32l151vb-a" ] stm32l151vc = [ "stm32-metapac/stm32l151vc" ] -stm32l151vd-x = [ "stm32-metapac/stm32l151vd-x" ] +stm32l151vc-a = [ "stm32-metapac/stm32l151vc-a" ] stm32l151vd = [ "stm32-metapac/stm32l151vd" ] +stm32l151vd-x = [ "stm32-metapac/stm32l151vd-x" ] stm32l151ve = [ "stm32-metapac/stm32l151ve" ] stm32l151zc = [ "stm32-metapac/stm32l151zc" ] stm32l151zd = [ "stm32-metapac/stm32l151zd" ] stm32l151ze = [ "stm32-metapac/stm32l151ze" ] -stm32l152c6-a = [ "stm32-metapac/stm32l152c6-a" ] stm32l152c6 = [ "stm32-metapac/stm32l152c6" ] -stm32l152c8-a = [ "stm32-metapac/stm32l152c8-a" ] +stm32l152c6-a = [ "stm32-metapac/stm32l152c6-a" ] stm32l152c8 = [ "stm32-metapac/stm32l152c8" ] -stm32l152cb-a = [ "stm32-metapac/stm32l152cb-a" ] +stm32l152c8-a = [ "stm32-metapac/stm32l152c8-a" ] stm32l152cb = [ "stm32-metapac/stm32l152cb" ] +stm32l152cb-a = [ "stm32-metapac/stm32l152cb-a" ] stm32l152cc = [ "stm32-metapac/stm32l152cc" ] stm32l152qc = [ "stm32-metapac/stm32l152qc" ] stm32l152qd = [ "stm32-metapac/stm32l152qd" ] stm32l152qe = [ "stm32-metapac/stm32l152qe" ] -stm32l152r6-a = [ "stm32-metapac/stm32l152r6-a" ] stm32l152r6 = [ "stm32-metapac/stm32l152r6" ] -stm32l152r8-a = [ "stm32-metapac/stm32l152r8-a" ] +stm32l152r6-a = [ "stm32-metapac/stm32l152r6-a" ] stm32l152r8 = [ "stm32-metapac/stm32l152r8" ] -stm32l152rb-a = [ "stm32-metapac/stm32l152rb-a" ] +stm32l152r8-a = [ "stm32-metapac/stm32l152r8-a" ] stm32l152rb = [ "stm32-metapac/stm32l152rb" ] -stm32l152rc-a = [ "stm32-metapac/stm32l152rc-a" ] +stm32l152rb-a = [ "stm32-metapac/stm32l152rb-a" ] stm32l152rc = [ "stm32-metapac/stm32l152rc" ] +stm32l152rc-a = [ "stm32-metapac/stm32l152rc-a" ] stm32l152rd = [ "stm32-metapac/stm32l152rd" ] stm32l152re = [ "stm32-metapac/stm32l152re" ] stm32l152uc = [ "stm32-metapac/stm32l152uc" ] -stm32l152v8-a = [ "stm32-metapac/stm32l152v8-a" ] stm32l152v8 = [ "stm32-metapac/stm32l152v8" ] -stm32l152vb-a = [ "stm32-metapac/stm32l152vb-a" ] +stm32l152v8-a = [ "stm32-metapac/stm32l152v8-a" ] stm32l152vb = [ "stm32-metapac/stm32l152vb" ] -stm32l152vc-a = [ "stm32-metapac/stm32l152vc-a" ] +stm32l152vb-a = [ "stm32-metapac/stm32l152vb-a" ] stm32l152vc = [ "stm32-metapac/stm32l152vc" ] -stm32l152vd-x = [ "stm32-metapac/stm32l152vd-x" ] +stm32l152vc-a = [ "stm32-metapac/stm32l152vc-a" ] stm32l152vd = [ "stm32-metapac/stm32l152vd" ] +stm32l152vd-x = [ "stm32-metapac/stm32l152vd-x" ] stm32l152ve = [ "stm32-metapac/stm32l152ve" ] stm32l152zc = [ "stm32-metapac/stm32l152zc" ] stm32l152zd = [ "stm32-metapac/stm32l152zd" ] stm32l152ze = [ "stm32-metapac/stm32l152ze" ] stm32l162qc = [ "stm32-metapac/stm32l162qc" ] stm32l162qd = [ "stm32-metapac/stm32l162qd" ] -stm32l162rc-a = [ "stm32-metapac/stm32l162rc-a" ] stm32l162rc = [ "stm32-metapac/stm32l162rc" ] +stm32l162rc-a = [ "stm32-metapac/stm32l162rc-a" ] stm32l162rd = [ "stm32-metapac/stm32l162rd" ] stm32l162re = [ "stm32-metapac/stm32l162re" ] -stm32l162vc-a = [ "stm32-metapac/stm32l162vc-a" ] stm32l162vc = [ "stm32-metapac/stm32l162vc" ] -stm32l162vd-x = [ "stm32-metapac/stm32l162vd-x" ] +stm32l162vc-a = [ "stm32-metapac/stm32l162vc-a" ] stm32l162vd = [ "stm32-metapac/stm32l162vd" ] +stm32l162vd-x = [ "stm32-metapac/stm32l162vd-x" ] stm32l162ve = [ "stm32-metapac/stm32l162ve" ] stm32l162zc = [ "stm32-metapac/stm32l162zc" ] stm32l162zd = [ "stm32-metapac/stm32l162zd" ] @@ -1296,6 +1347,22 @@ stm32l562qe = [ "stm32-metapac/stm32l562qe" ] stm32l562re = [ "stm32-metapac/stm32l562re" ] stm32l562ve = [ "stm32-metapac/stm32l562ve" ] stm32l562ze = [ "stm32-metapac/stm32l562ze" ] +stm32u535cb = [ "stm32-metapac/stm32u535cb" ] +stm32u535cc = [ "stm32-metapac/stm32u535cc" ] +stm32u535ce = [ "stm32-metapac/stm32u535ce" ] +stm32u535je = [ "stm32-metapac/stm32u535je" ] +stm32u535nc = [ "stm32-metapac/stm32u535nc" ] +stm32u535ne = [ "stm32-metapac/stm32u535ne" ] +stm32u535rb = [ "stm32-metapac/stm32u535rb" ] +stm32u535rc = [ "stm32-metapac/stm32u535rc" ] +stm32u535re = [ "stm32-metapac/stm32u535re" ] +stm32u535vc = [ "stm32-metapac/stm32u535vc" ] +stm32u535ve = [ "stm32-metapac/stm32u535ve" ] +stm32u545ce = [ "stm32-metapac/stm32u545ce" ] +stm32u545je = [ "stm32-metapac/stm32u545je" ] +stm32u545ne = [ "stm32-metapac/stm32u545ne" ] +stm32u545re = [ "stm32-metapac/stm32u545re" ] +stm32u545ve = [ "stm32-metapac/stm32u545ve" ] stm32u575ag = [ "stm32-metapac/stm32u575ag" ] stm32u575ai = [ "stm32-metapac/stm32u575ai" ] stm32u575cg = [ "stm32-metapac/stm32u575cg" ] @@ -1313,12 +1380,36 @@ stm32u575zi = [ "stm32-metapac/stm32u575zi" ] stm32u585ai = [ "stm32-metapac/stm32u585ai" ] stm32u585ci = [ "stm32-metapac/stm32u585ci" ] stm32u585oi = [ "stm32-metapac/stm32u585oi" ] -stm32u585qe = [ "stm32-metapac/stm32u585qe" ] stm32u585qi = [ "stm32-metapac/stm32u585qi" ] stm32u585ri = [ "stm32-metapac/stm32u585ri" ] stm32u585vi = [ "stm32-metapac/stm32u585vi" ] -stm32u585ze = [ "stm32-metapac/stm32u585ze" ] stm32u585zi = [ "stm32-metapac/stm32u585zi" ] +stm32u595ai = [ "stm32-metapac/stm32u595ai" ] +stm32u595aj = [ "stm32-metapac/stm32u595aj" ] +stm32u595qi = [ "stm32-metapac/stm32u595qi" ] +stm32u595qj = [ "stm32-metapac/stm32u595qj" ] +stm32u595ri = [ "stm32-metapac/stm32u595ri" ] +stm32u595rj = [ "stm32-metapac/stm32u595rj" ] +stm32u595vi = [ "stm32-metapac/stm32u595vi" ] +stm32u595vj = [ "stm32-metapac/stm32u595vj" ] +stm32u595zi = [ "stm32-metapac/stm32u595zi" ] +stm32u595zj = [ "stm32-metapac/stm32u595zj" ] +stm32u599bj = [ "stm32-metapac/stm32u599bj" ] +stm32u599ni = [ "stm32-metapac/stm32u599ni" ] +stm32u599nj = [ "stm32-metapac/stm32u599nj" ] +stm32u599vi = [ "stm32-metapac/stm32u599vi" ] +stm32u599vj = [ "stm32-metapac/stm32u599vj" ] +stm32u599zi = [ "stm32-metapac/stm32u599zi" ] +stm32u599zj = [ "stm32-metapac/stm32u599zj" ] +stm32u5a5aj = [ "stm32-metapac/stm32u5a5aj" ] +stm32u5a5qj = [ "stm32-metapac/stm32u5a5qj" ] +stm32u5a5rj = [ "stm32-metapac/stm32u5a5rj" ] +stm32u5a5vj = [ "stm32-metapac/stm32u5a5vj" ] +stm32u5a5zj = [ "stm32-metapac/stm32u5a5zj" ] +stm32u5a9bj = [ "stm32-metapac/stm32u5a9bj" ] +stm32u5a9nj = [ "stm32-metapac/stm32u5a9nj" ] +stm32u5a9vj = [ "stm32-metapac/stm32u5a9vj" ] +stm32u5a9zj = [ "stm32-metapac/stm32u5a9zj" ] stm32wb10cc = [ "stm32-metapac/stm32wb10cc" ] stm32wb15cc = [ "stm32-metapac/stm32wb15cc" ] stm32wb30ce = [ "stm32-metapac/stm32wb30ce" ] @@ -1335,7 +1426,6 @@ stm32wb55vc = [ "stm32-metapac/stm32wb55vc" ] stm32wb55ve = [ "stm32-metapac/stm32wb55ve" ] stm32wb55vg = [ "stm32-metapac/stm32wb55vg" ] stm32wb55vy = [ "stm32-metapac/stm32wb55vy" ] -stm32wb5mmg = [ "stm32-metapac/stm32wb5mmg" ] stm32wl54cc-cm4 = [ "stm32-metapac/stm32wl54cc-cm4" ] stm32wl54cc-cm0p = [ "stm32-metapac/stm32wl54cc-cm0p" ] stm32wl54jc-cm4 = [ "stm32-metapac/stm32wl54jc-cm4" ] @@ -1344,8 +1434,6 @@ stm32wl55cc-cm4 = [ "stm32-metapac/stm32wl55cc-cm4" ] stm32wl55cc-cm0p = [ "stm32-metapac/stm32wl55cc-cm0p" ] stm32wl55jc-cm4 = [ "stm32-metapac/stm32wl55jc-cm4" ] stm32wl55jc-cm0p = [ "stm32-metapac/stm32wl55jc-cm0p" ] -stm32wl55uc-cm4 = [ "stm32-metapac/stm32wl55uc-cm4" ] -stm32wl55uc-cm0p = [ "stm32-metapac/stm32wl55uc-cm0p" ] stm32wle4c8 = [ "stm32-metapac/stm32wle4c8" ] stm32wle4cb = [ "stm32-metapac/stm32wle4cb" ] stm32wle4cc = [ "stm32-metapac/stm32wle4cc" ] @@ -1357,7 +1445,4 @@ stm32wle5cb = [ "stm32-metapac/stm32wle5cb" ] stm32wle5cc = [ "stm32-metapac/stm32wle5cc" ] stm32wle5j8 = [ "stm32-metapac/stm32wle5j8" ] stm32wle5jb = [ "stm32-metapac/stm32wle5jb" ] -stm32wle5jc = [ "stm32-metapac/stm32wle5jc" ] -stm32wle5u8 = [ "stm32-metapac/stm32wle5u8" ] -stm32wle5ub = [ "stm32-metapac/stm32wle5ub" ] -# END GENERATED FEATURES +stm32wle5jc = [ "stm32-metapac/stm32wle5jc" ] \ No newline at end of file diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index a4709f4ca..995ad1443 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -3,9 +3,9 @@ use std::fmt::Write as _; use std::path::PathBuf; use std::{env, fs}; -use proc_macro2::TokenStream; +use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; -use stm32_metapac::metadata::METADATA; +use stm32_metapac::metadata::{MemoryRegionKind, METADATA}; fn main() { let chip_name = match env::vars() @@ -50,10 +50,13 @@ fn main() { // We *shouldn't* have singletons for these, but the HAL currently requires // singletons, for using with RccPeripheral to enable/disable clocks to them. "rcc" => { - if r.version.starts_with("h7") { + if r.version.starts_with("h5") || r.version.starts_with("h7") || r.version.starts_with("f4") { singletons.push("MCO1".to_string()); singletons.push("MCO2".to_string()); } + if r.version.starts_with("l4") { + singletons.push("MCO".to_string()); + } singletons.push(p.name.to_string()); } //"dbgmcu" => {} @@ -78,11 +81,74 @@ fn main() { singletons.push(c.name.to_string()); } + // ======== + // Handle time-driver-XXXX features. + + let time_driver = match env::vars() + .map(|(a, _)| a) + .filter(|x| x.starts_with("CARGO_FEATURE_TIME_DRIVER_")) + .get_one() + { + Ok(x) => Some( + x.strip_prefix("CARGO_FEATURE_TIME_DRIVER_") + .unwrap() + .to_ascii_lowercase(), + ), + Err(GetOneError::None) => None, + Err(GetOneError::Multiple) => panic!("Multiple stm32xx Cargo features enabled"), + }; + + let time_driver_singleton = match time_driver.as_ref().map(|x| x.as_ref()) { + None => "", + Some("tim2") => "TIM2", + Some("tim3") => "TIM3", + Some("tim4") => "TIM4", + Some("tim5") => "TIM5", + Some("tim12") => "TIM12", + Some("tim15") => "TIM15", + Some("any") => { + if singletons.contains(&"TIM2".to_string()) { + "TIM2" + } else if singletons.contains(&"TIM3".to_string()) { + "TIM3" + } else if singletons.contains(&"TIM4".to_string()) { + "TIM4" + } else if singletons.contains(&"TIM5".to_string()) { + "TIM5" + } else if singletons.contains(&"TIM12".to_string()) { + "TIM12" + } else if singletons.contains(&"TIM15".to_string()) { + "TIM15" + } else { + panic!("time-driver-any requested, but the chip doesn't have TIM2, TIM3, TIM4, TIM5, TIM12 or TIM15.") + } + } + _ => panic!("unknown time_driver {:?}", time_driver), + }; + + if time_driver_singleton != "" { + println!("cargo:rustc-cfg=time_driver_{}", time_driver_singleton.to_lowercase()); + } + + // ======== + // Write singletons + let mut g = TokenStream::new(); let singleton_tokens: Vec<_> = singletons.iter().map(|s| format_ident!("{}", s)).collect(); + g.extend(quote! { - embassy_hal_common::peripherals!(#(#singleton_tokens),*); + embassy_hal_common::peripherals_definition!(#(#singleton_tokens),*); + }); + + let singleton_tokens: Vec<_> = singletons + .iter() + .filter(|s| *s != &time_driver_singleton.to_string()) + .map(|s| format_ident!("{}", s)) + .collect(); + + g.extend(quote! { + embassy_hal_common::peripherals_struct!(#(#singleton_tokens),*); }); // ======== @@ -94,19 +160,116 @@ fn main() { } g.extend(quote! { - pub mod interrupt { - use crate::pac::Interrupt as InterruptEnum; - use embassy_cortex_m::interrupt::_export::declare; + embassy_hal_common::interrupt_mod!( #( - declare!(#irqs); + #irqs, )* - } + ); }); + // ======== + // Generate FLASH regions + let mut flash_regions = TokenStream::new(); + let flash_memory_regions: Vec<_> = METADATA + .memory + .iter() + .filter(|x| x.kind == MemoryRegionKind::Flash && x.settings.is_some()) + .collect(); + for region in flash_memory_regions.iter() { + let region_name = format_ident!("{}", get_flash_region_name(region.name)); + let bank_variant = format_ident!( + "{}", + if region.name.starts_with("BANK_1") { + "Bank1" + } else if region.name.starts_with("BANK_2") { + "Bank2" + } else if region.name == "OTP" { + "Otp" + } else { + continue; + } + ); + let base = region.address; + let size = region.size; + let settings = region.settings.as_ref().unwrap(); + let erase_size = settings.erase_size; + let write_size = settings.write_size; + let erase_value = settings.erase_value; + + flash_regions.extend(quote! { + pub const #region_name: crate::flash::FlashRegion = crate::flash::FlashRegion { + bank: crate::flash::FlashBank::#bank_variant, + base: #base, + size: #size, + erase_size: #erase_size, + write_size: #write_size, + erase_value: #erase_value, + _ensure_internal: (), + }; + }); + + let region_type = format_ident!("{}", get_flash_region_type_name(region.name)); + flash_regions.extend(quote! { + #[cfg(flash)] + pub struct #region_type<'d, MODE = crate::flash::Async>(pub &'static crate::flash::FlashRegion, pub(crate) embassy_hal_common::PeripheralRef<'d, crate::peripherals::FLASH>, pub(crate) core::marker::PhantomData); + }); + } + + let (fields, (inits, region_names)): (Vec, (Vec, Vec)) = flash_memory_regions + .iter() + .map(|f| { + let region_name = get_flash_region_name(f.name); + let field_name = format_ident!("{}", region_name.to_lowercase()); + let field_type = format_ident!("{}", get_flash_region_type_name(f.name)); + let field = quote! { + pub #field_name: #field_type<'d, MODE> + }; + let region_name = format_ident!("{}", region_name); + let init = quote! { + #field_name: #field_type(&#region_name, unsafe { p.clone_unchecked()}, core::marker::PhantomData) + }; + + (field, (init, region_name)) + }) + .unzip(); + + let regions_len = flash_memory_regions.len(); + flash_regions.extend(quote! { + #[cfg(flash)] + pub struct FlashLayout<'d, MODE = crate::flash::Async> { + #(#fields),*, + _mode: core::marker::PhantomData, + } + + #[cfg(flash)] + impl<'d, MODE> FlashLayout<'d, MODE> { + pub(crate) fn new(p: embassy_hal_common::PeripheralRef<'d, crate::peripherals::FLASH>) -> Self { + Self { + #(#inits),*, + _mode: core::marker::PhantomData, + } + } + } + + pub const FLASH_REGIONS: [&crate::flash::FlashRegion; #regions_len] = [ + #(&#region_names),* + ]; + }); + + let max_erase_size = flash_memory_regions + .iter() + .map(|region| region.settings.as_ref().unwrap().erase_size) + .max() + .unwrap(); + + g.extend(quote! { pub const MAX_ERASE_SIZE: usize = #max_erase_size as usize; }); + + g.extend(quote! { pub mod flash_regions { #flash_regions } }); + // ======== // Generate DMA IRQs. - let mut dma_irqs: HashMap<&str, Vec<(&str, &str)>> = HashMap::new(); + let mut dma_irqs: HashMap<&str, Vec<(&str, &str, &str)>> = HashMap::new(); for p in METADATA.peripherals { if let Some(r) = &p.registers { @@ -116,7 +279,10 @@ fn main() { continue; } for irq in p.interrupts { - dma_irqs.entry(irq.interrupt).or_default().push((p.name, irq.signal)); + dma_irqs + .entry(irq.interrupt) + .or_default() + .push((r.kind, p.name, irq.signal)); } } } @@ -125,13 +291,15 @@ fn main() { for (irq, channels) in dma_irqs { let irq = format_ident!("{}", irq); - let channels = channels.iter().map(|(dma, ch)| format_ident!("{}_{}", dma, ch)); + let xdma = format_ident!("{}", channels[0].0); + let channels = channels.iter().map(|(_, dma, ch)| format_ident!("{}_{}", dma, ch)); g.extend(quote! { + #[cfg(feature = "rt")] #[crate::interrupt] unsafe fn #irq () { #( - ::on_irq(); + ::on_irq(); )* } }); @@ -154,7 +322,7 @@ fn main() { let rst_reg = format_ident!("{}", rst.register.to_ascii_lowercase()); let set_rst_field = format_ident!("set_{}", rst.field.to_ascii_lowercase()); quote! { - critical_section::with(|_| unsafe { + critical_section::with(|_| { crate::pac::RCC.#rst_reg().modify(|w| w.#set_rst_field(true)); crate::pac::RCC.#rst_reg().modify(|w| w.#set_rst_field(false)); }); @@ -180,18 +348,16 @@ fn main() { g.extend(quote! { impl crate::rcc::sealed::RccPeripheral for peripherals::#pname { fn frequency() -> crate::time::Hertz { - critical_section::with(|_| unsafe { - crate::rcc::get_freqs().#clk - }) + unsafe { crate::rcc::get_freqs().#clk } } fn enable() { - critical_section::with(|_| unsafe { + critical_section::with(|_| { crate::pac::RCC.#en_reg().modify(|w| w.#set_en_field(true)); #after_enable }) } fn disable() { - critical_section::with(|_| unsafe { + critical_section::with(|_| { crate::pac::RCC.#en_reg().modify(|w| w.#set_en_field(false)); }) } @@ -244,18 +410,25 @@ fn main() { (("usart", "CTS"), quote!(crate::usart::CtsPin)), (("usart", "RTS"), quote!(crate::usart::RtsPin)), (("usart", "CK"), quote!(crate::usart::CkPin)), + (("usart", "DE"), quote!(crate::usart::DePin)), (("lpuart", "TX"), quote!(crate::usart::TxPin)), (("lpuart", "RX"), quote!(crate::usart::RxPin)), (("lpuart", "CTS"), quote!(crate::usart::CtsPin)), (("lpuart", "RTS"), quote!(crate::usart::RtsPin)), (("lpuart", "CK"), quote!(crate::usart::CkPin)), + (("lpuart", "DE"), quote!(crate::usart::DePin)), (("spi", "SCK"), quote!(crate::spi::SckPin)), (("spi", "MOSI"), quote!(crate::spi::MosiPin)), (("spi", "MISO"), quote!(crate::spi::MisoPin)), + (("spi", "NSS"), quote!(crate::spi::CsPin)), + (("spi", "I2S_MCK"), quote!(crate::spi::MckPin)), + (("spi", "I2S_CK"), quote!(crate::spi::CkPin)), + (("spi", "I2S_WS"), quote!(crate::spi::WsPin)), (("i2c", "SDA"), quote!(crate::i2c::SdaPin)), (("i2c", "SCL"), quote!(crate::i2c::SclPin)), (("rcc", "MCO_1"), quote!(crate::rcc::McoPin)), (("rcc", "MCO_2"), quote!(crate::rcc::McoPin)), + (("rcc", "MCO"), quote!(crate::rcc::McoPin)), (("dcmi", "D0"), quote!(crate::dcmi::D0Pin)), (("dcmi", "D1"), quote!(crate::dcmi::D1Pin)), (("dcmi", "D2"), quote!(crate::dcmi::D2Pin)), @@ -275,22 +448,20 @@ fn main() { (("dcmi", "PIXCLK"), quote!(crate::dcmi::PixClkPin)), (("usb", "DP"), quote!(crate::usb::DpPin)), (("usb", "DM"), quote!(crate::usb::DmPin)), - (("otgfs", "DP"), quote!(crate::usb_otg::DpPin)), - (("otgfs", "DM"), quote!(crate::usb_otg::DmPin)), - (("otghs", "DP"), quote!(crate::usb_otg::DpPin)), - (("otghs", "DM"), quote!(crate::usb_otg::DmPin)), - (("otghs", "ULPI_CK"), quote!(crate::usb_otg::UlpiClkPin)), - (("otghs", "ULPI_DIR"), quote!(crate::usb_otg::UlpiDirPin)), - (("otghs", "ULPI_NXT"), quote!(crate::usb_otg::UlpiNxtPin)), - (("otghs", "ULPI_STP"), quote!(crate::usb_otg::UlpiStpPin)), - (("otghs", "ULPI_D0"), quote!(crate::usb_otg::UlpiD0Pin)), - (("otghs", "ULPI_D1"), quote!(crate::usb_otg::UlpiD1Pin)), - (("otghs", "ULPI_D2"), quote!(crate::usb_otg::UlpiD2Pin)), - (("otghs", "ULPI_D3"), quote!(crate::usb_otg::UlpiD3Pin)), - (("otghs", "ULPI_D4"), quote!(crate::usb_otg::UlpiD4Pin)), - (("otghs", "ULPI_D5"), quote!(crate::usb_otg::UlpiD5Pin)), - (("otghs", "ULPI_D6"), quote!(crate::usb_otg::UlpiD6Pin)), - (("otghs", "ULPI_D7"), quote!(crate::usb_otg::UlpiD7Pin)), + (("otg", "DP"), quote!(crate::usb_otg::DpPin)), + (("otg", "DM"), quote!(crate::usb_otg::DmPin)), + (("otg", "ULPI_CK"), quote!(crate::usb_otg::UlpiClkPin)), + (("otg", "ULPI_DIR"), quote!(crate::usb_otg::UlpiDirPin)), + (("otg", "ULPI_NXT"), quote!(crate::usb_otg::UlpiNxtPin)), + (("otg", "ULPI_STP"), quote!(crate::usb_otg::UlpiStpPin)), + (("otg", "ULPI_D0"), quote!(crate::usb_otg::UlpiD0Pin)), + (("otg", "ULPI_D1"), quote!(crate::usb_otg::UlpiD1Pin)), + (("otg", "ULPI_D2"), quote!(crate::usb_otg::UlpiD2Pin)), + (("otg", "ULPI_D3"), quote!(crate::usb_otg::UlpiD3Pin)), + (("otg", "ULPI_D4"), quote!(crate::usb_otg::UlpiD4Pin)), + (("otg", "ULPI_D5"), quote!(crate::usb_otg::UlpiD5Pin)), + (("otg", "ULPI_D6"), quote!(crate::usb_otg::UlpiD6Pin)), + (("otg", "ULPI_D7"), quote!(crate::usb_otg::UlpiD7Pin)), (("can", "TX"), quote!(crate::can::TxPin)), (("can", "RX"), quote!(crate::can::RxPin)), (("eth", "REF_CLK"), quote!(crate::eth::RefClkPin)), @@ -427,6 +598,12 @@ fn main() { (("sdmmc", "D6"), quote!(crate::sdmmc::D6Pin)), (("sdmmc", "D6"), quote!(crate::sdmmc::D7Pin)), (("sdmmc", "D8"), quote!(crate::sdmmc::D8Pin)), + (("quadspi", "BK1_IO0"), quote!(crate::qspi::D0Pin)), + (("quadspi", "BK1_IO1"), quote!(crate::qspi::D1Pin)), + (("quadspi", "BK1_IO2"), quote!(crate::qspi::D2Pin)), + (("quadspi", "BK1_IO3"), quote!(crate::qspi::D3Pin)), + (("quadspi", "CLK"), quote!(crate::qspi::SckPin)), + (("quadspi", "BK1_NCS"), quote!(crate::qspi::NSSPin)), ].into(); for p in METADATA.peripherals { @@ -441,13 +618,25 @@ fn main() { // MCO is special if pin.signal.starts_with("MCO_") { // Supported in H7 only for now - if regs.version.starts_with("h7") { + if regs.version.starts_with("h5") + || regs.version.starts_with("h7") + || regs.version.starts_with("f4") + { peri = format_ident!("{}", pin.signal.replace("_", "")); } else { continue; } } + if pin.signal == "MCO" { + // Supported in H7 only for now + if regs.version.starts_with("l4") { + peri = format_ident!("MCO"); + } else { + continue; + } + } + g.extend(quote! { pin_trait_impl!(#tr, #peri, #pin_name, #af); }) @@ -507,6 +696,9 @@ fn main() { (("dcmi", "PSSI"), quote!(crate::dcmi::FrameDma)), // SDMMCv1 uses the same channel for both directions, so just implement for RX (("sdmmc", "RX"), quote!(crate::sdmmc::SdmmcDma)), + (("quadspi", "QUADSPI"), quote!(crate::qspi::QuadDma)), + (("dac", "CH1"), quote!(crate::dac::DmaCh1)), + (("dac", "CH2"), quote!(crate::dac::DmaCh2)), ] .into(); @@ -558,11 +750,25 @@ fn main() { // ======== // Write foreach_foo! macrotables + let mut flash_regions_table: Vec> = Vec::new(); let mut interrupts_table: Vec> = Vec::new(); let mut peripherals_table: Vec> = Vec::new(); let mut pins_table: Vec> = Vec::new(); let mut dma_channels_table: Vec> = Vec::new(); + for m in METADATA + .memory + .iter() + .filter(|m| m.kind == MemoryRegionKind::Flash && m.settings.is_some()) + { + let settings = m.settings.as_ref().unwrap(); + let mut row = Vec::new(); + row.push(get_flash_region_type_name(m.name)); + row.push(settings.write_size.to_string()); + row.push(settings.erase_size.to_string()); + flash_regions_table.push(row); + } + let gpio_base = METADATA.peripherals.iter().find(|p| p.name == "GPIOA").unwrap().address as u32; let gpio_stride = 0x400; @@ -659,6 +865,7 @@ fn main() { let mut m = String::new(); + make_table(&mut m, "foreach_flash_region", &flash_regions_table); make_table(&mut m, "foreach_interrupt", &interrupts_table); make_table(&mut m, "foreach_peripheral", &peripherals_table); make_table(&mut m, "foreach_pin", &pins_table); @@ -712,51 +919,6 @@ fn main() { println!("cargo:rustc-cfg={}x", &chip_name[..8]); // stm32f42x println!("cargo:rustc-cfg={}x{}", &chip_name[..7], &chip_name[8..9]); // stm32f4x9 - // ======== - // Handle time-driver-XXXX features. - - let time_driver = match env::vars() - .map(|(a, _)| a) - .filter(|x| x.starts_with("CARGO_FEATURE_TIME_DRIVER_")) - .get_one() - { - Ok(x) => Some( - x.strip_prefix("CARGO_FEATURE_TIME_DRIVER_") - .unwrap() - .to_ascii_lowercase(), - ), - Err(GetOneError::None) => None, - Err(GetOneError::Multiple) => panic!("Multiple stm32xx Cargo features enabled"), - }; - - match time_driver.as_ref().map(|x| x.as_ref()) { - None => {} - Some("tim2") => println!("cargo:rustc-cfg=time_driver_tim2"), - Some("tim3") => println!("cargo:rustc-cfg=time_driver_tim3"), - Some("tim4") => println!("cargo:rustc-cfg=time_driver_tim4"), - Some("tim5") => println!("cargo:rustc-cfg=time_driver_tim5"), - Some("tim12") => println!("cargo:rustc-cfg=time_driver_tim12"), - Some("tim15") => println!("cargo:rustc-cfg=time_driver_tim15"), - Some("any") => { - if singletons.contains(&"TIM2".to_string()) { - println!("cargo:rustc-cfg=time_driver_tim2"); - } else if singletons.contains(&"TIM3".to_string()) { - println!("cargo:rustc-cfg=time_driver_tim3"); - } else if singletons.contains(&"TIM4".to_string()) { - println!("cargo:rustc-cfg=time_driver_tim4"); - } else if singletons.contains(&"TIM5".to_string()) { - println!("cargo:rustc-cfg=time_driver_tim5"); - } else if singletons.contains(&"TIM12".to_string()) { - println!("cargo:rustc-cfg=time_driver_tim12"); - } else if singletons.contains(&"TIM15".to_string()) { - println!("cargo:rustc-cfg=time_driver_tim15"); - } else { - panic!("time-driver-any requested, but the chip doesn't have TIM2, TIM3, TIM4, TIM5, TIM12 or TIM15.") - } - } - _ => panic!("unknown time_driver {:?}", time_driver), - } - // Handle time-driver-XXXX features. if env::var("CARGO_FEATURE_TIME_DRIVER_ANY").is_ok() {} println!("cargo:rustc-cfg={}", &chip_name[..chip_name.len() - 2]); @@ -811,3 +973,19 @@ macro_rules! {} {{ ) .unwrap(); } + +fn get_flash_region_name(name: &str) -> String { + let name = name.replace("BANK_", "BANK").replace("REGION_", "REGION"); + if name.contains("REGION") { + name + } else { + name + "_REGION" + } +} + +fn get_flash_region_type_name(name: &str) -> String { + get_flash_region_name(name) + .replace("BANK", "Bank") + .replace("REGION", "Region") + .replace("_", "") +} diff --git a/embassy-stm32/src/adc/f1.rs b/embassy-stm32/src/adc/f1.rs index 50d4f9bf9..2322204d5 100644 --- a/embassy-stm32/src/adc/f1.rs +++ b/embassy-stm32/src/adc/f1.rs @@ -1,9 +1,7 @@ -use core::marker::PhantomData; - use embassy_hal_common::into_ref; use embedded_hal_02::blocking::delay::DelayUs; -use crate::adc::{AdcPin, Instance}; +use crate::adc::{Adc, AdcPin, Instance, SampleTime}; use crate::rcc::get_freqs; use crate::time::Hertz; use crate::Peripheral; @@ -29,101 +27,35 @@ impl super::sealed::AdcPin for Temperature { } } -mod sample_time { - /// ADC sample time - /// - /// The default setting is 1.5 ADC clock cycles. - #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] - pub enum SampleTime { - /// 1.5 ADC clock cycles - Cycles1_5 = 0b000, - - /// 7.5 ADC clock cycles - Cycles7_5 = 0b001, - - /// 13.5 ADC clock cycles - Cycles13_5 = 0b010, - - /// 28.5 ADC clock cycles - Cycles28_5 = 0b011, - - /// 41.5 ADC clock cycles - Cycles41_5 = 0b100, - - /// 55.5 ADC clock cycles - Cycles55_5 = 0b101, - - /// 71.5 ADC clock cycles - Cycles71_5 = 0b110, - - /// 239.5 ADC clock cycles - Cycles239_5 = 0b111, - } - - impl SampleTime { - pub(crate) fn sample_time(&self) -> crate::pac::adc::vals::SampleTime { - match self { - SampleTime::Cycles1_5 => crate::pac::adc::vals::SampleTime::CYCLES1_5, - SampleTime::Cycles7_5 => crate::pac::adc::vals::SampleTime::CYCLES7_5, - SampleTime::Cycles13_5 => crate::pac::adc::vals::SampleTime::CYCLES13_5, - SampleTime::Cycles28_5 => crate::pac::adc::vals::SampleTime::CYCLES28_5, - SampleTime::Cycles41_5 => crate::pac::adc::vals::SampleTime::CYCLES41_5, - SampleTime::Cycles55_5 => crate::pac::adc::vals::SampleTime::CYCLES55_5, - SampleTime::Cycles71_5 => crate::pac::adc::vals::SampleTime::CYCLES71_5, - SampleTime::Cycles239_5 => crate::pac::adc::vals::SampleTime::CYCLES239_5, - } - } - } - - impl Default for SampleTime { - fn default() -> Self { - Self::Cycles28_5 - } - } -} - -pub use sample_time::SampleTime; - -pub struct Adc<'d, T: Instance> { - sample_time: SampleTime, - calibrated_vdda: u32, - phantom: PhantomData<&'d mut T>, -} - impl<'d, T: Instance> Adc<'d, T> { - pub fn new(_peri: impl Peripheral

+ 'd, delay: &mut impl DelayUs) -> Self { - into_ref!(_peri); + pub fn new(adc: impl Peripheral

+ 'd, delay: &mut impl DelayUs) -> Self { + into_ref!(adc); T::enable(); T::reset(); - unsafe { - T::regs().cr2().modify(|reg| reg.set_adon(true)); - } + T::regs().cr2().modify(|reg| reg.set_adon(true)); // 11.4: Before starting a calibration, the ADC must have been in power-on state (ADON bit = ‘1’) // for at least two ADC clock cycles delay.delay_us((1_000_000 * 2) / Self::freq().0 + 1); - unsafe { - // Reset calibration - T::regs().cr2().modify(|reg| reg.set_rstcal(true)); - while T::regs().cr2().read().rstcal() { - // spin - } + // Reset calibration + T::regs().cr2().modify(|reg| reg.set_rstcal(true)); + while T::regs().cr2().read().rstcal() { + // spin + } - // Calibrate - T::regs().cr2().modify(|reg| reg.set_cal(true)); - while T::regs().cr2().read().cal() { - // spin - } + // Calibrate + T::regs().cr2().modify(|reg| reg.set_cal(true)); + while T::regs().cr2().read().cal() { + // spin } // One cycle after calibration delay.delay_us((1_000_000) / Self::freq().0 + 1); Self { + adc, sample_time: Default::default(), - calibrated_vdda: VDDA_CALIB_MV, - phantom: PhantomData, } } @@ -145,91 +77,61 @@ impl<'d, T: Instance> Adc<'d, T> { } pub fn enable_vref(&self, _delay: &mut impl DelayUs) -> Vref { - unsafe { - T::regs().cr2().modify(|reg| { - reg.set_tsvrefe(true); - }) - } + T::regs().cr2().modify(|reg| { + reg.set_tsvrefe(true); + }); Vref {} } pub fn enable_temperature(&self) -> Temperature { - unsafe { - T::regs().cr2().modify(|reg| { - reg.set_tsvrefe(true); - }) - } + T::regs().cr2().modify(|reg| { + reg.set_tsvrefe(true); + }); Temperature {} } - /// Calculates the system VDDA by sampling the internal VREF channel and comparing - /// to the expected value. If the chip's VDDA is not stable, run this before each ADC - /// conversion. - pub fn calibrate(&mut self, vref: &mut Vref) -> u32 { - let old_sample_time = self.sample_time; - self.sample_time = SampleTime::Cycles239_5; - - let vref_samp = self.read(vref); - self.sample_time = old_sample_time; - - self.calibrated_vdda = (ADC_MAX * VREF_INT) / u32::from(vref_samp); - self.calibrated_vdda - } - pub fn set_sample_time(&mut self, sample_time: SampleTime) { self.sample_time = sample_time; } - /// Convert a measurement to millivolts - pub fn to_millivolts(&self, sample: u16) -> u16 { - ((u32::from(sample) * self.calibrated_vdda) / ADC_MAX) as u16 - } - /// Perform a single conversion. fn convert(&mut self) -> u16 { - unsafe { - T::regs().cr2().modify(|reg| { - reg.set_adon(true); - reg.set_swstart(true); - }); - while T::regs().cr2().read().swstart() {} - while !T::regs().sr().read().eoc() {} + T::regs().cr2().modify(|reg| { + reg.set_adon(true); + reg.set_swstart(true); + }); + while T::regs().cr2().read().swstart() {} + while !T::regs().sr().read().eoc() {} - T::regs().dr().read().0 as u16 - } + T::regs().dr().read().0 as u16 } pub fn read(&mut self, pin: &mut impl AdcPin) -> u16 { - unsafe { - Self::set_channel_sample_time(pin.channel(), self.sample_time); - T::regs().cr1().modify(|reg| { - reg.set_scan(false); - reg.set_discen(false); - }); - T::regs().sqr1().modify(|reg| reg.set_l(0)); + Self::set_channel_sample_time(pin.channel(), self.sample_time); + T::regs().cr1().modify(|reg| { + reg.set_scan(false); + reg.set_discen(false); + }); + T::regs().sqr1().modify(|reg| reg.set_l(0)); - T::regs().cr2().modify(|reg| { - reg.set_cont(false); - reg.set_exttrig(true); - reg.set_swstart(false); - reg.set_extsel(crate::pac::adc::vals::Extsel::SWSTART); - }); - } + T::regs().cr2().modify(|reg| { + reg.set_cont(false); + reg.set_exttrig(true); + reg.set_swstart(false); + reg.set_extsel(crate::pac::adc::vals::Extsel::SWSTART); + }); // Configure the channel to sample - unsafe { T::regs().sqr3().write(|reg| reg.set_sq(0, pin.channel())) } + T::regs().sqr3().write(|reg| reg.set_sq(0, pin.channel())); self.convert() } - unsafe fn set_channel_sample_time(ch: u8, sample_time: SampleTime) { + fn set_channel_sample_time(ch: u8, sample_time: SampleTime) { + let sample_time = sample_time.into(); if ch <= 9 { - T::regs() - .smpr2() - .modify(|reg| reg.set_smp(ch as _, sample_time.sample_time())); + T::regs().smpr2().modify(|reg| reg.set_smp(ch as _, sample_time)); } else { - T::regs() - .smpr1() - .modify(|reg| reg.set_smp((ch - 10) as _, sample_time.sample_time())); + T::regs().smpr1().modify(|reg| reg.set_smp((ch - 10) as _, sample_time)); } } } diff --git a/embassy-stm32/src/adc/mod.rs b/embassy-stm32/src/adc/mod.rs index 8da13073e..56ecd63ca 100644 --- a/embassy-stm32/src/adc/mod.rs +++ b/embassy-stm32/src/adc/mod.rs @@ -1,55 +1,66 @@ #![macro_use] -#[cfg_attr(adc_v4, path = "v4.rs")] -#[cfg_attr(adc_v3, path = "v3.rs")] -#[cfg_attr(adc_v2, path = "v2.rs")] -#[cfg_attr(adc_g0, path = "v3.rs")] #[cfg_attr(adc_f1, path = "f1.rs")] #[cfg_attr(adc_v1, path = "v1.rs")] +#[cfg_attr(adc_v2, path = "v2.rs")] +#[cfg_attr(any(adc_v3, adc_g0), path = "v3.rs")] +#[cfg_attr(adc_v4, path = "v4.rs")] mod _version; +#[cfg(not(adc_f1))] +mod resolution; +mod sample_time; + #[allow(unused)] pub use _version::*; +#[cfg(not(adc_f1))] +pub use resolution::Resolution; +pub use sample_time::SampleTime; use crate::peripherals; +pub struct Adc<'d, T: Instance> { + #[allow(unused)] + adc: crate::PeripheralRef<'d, T>, + sample_time: SampleTime, +} + pub(crate) mod sealed { pub trait Instance { - fn regs() -> &'static crate::pac::adc::Adc; + fn regs() -> crate::pac::adc::Adc; #[cfg(all(not(adc_f1), not(adc_v1)))] - fn common_regs() -> &'static crate::pac::adccommon::AdcCommon; - } - - #[cfg(all(not(adc_f1), not(adc_v1)))] - pub trait Common { - fn regs() -> &'static crate::pac::adccommon::AdcCommon; + fn common_regs() -> crate::pac::adccommon::AdcCommon; } pub trait AdcPin { fn channel(&self) -> u8; } + + pub trait InternalChannel { + fn channel(&self) -> u8; + } } -#[cfg(not(adc_f1))] -pub trait Instance: sealed::Instance + 'static {} -#[cfg(adc_f1)] -pub trait Instance: sealed::Instance + crate::rcc::RccPeripheral + 'static {} -#[cfg(all(not(adc_f1), not(adc_v1)))] -pub trait Common: sealed::Common + 'static {} +#[cfg(not(any(adc_f1, adc_v1, adc_v2, adc_v4)))] +pub trait Instance: sealed::Instance + crate::Peripheral

{} +#[cfg(any(adc_f1, adc_v1, adc_v2, adc_v4))] +pub trait Instance: sealed::Instance + crate::Peripheral

+ crate::rcc::RccPeripheral {} + pub trait AdcPin: sealed::AdcPin {} +pub trait InternalChannel: sealed::InternalChannel {} #[cfg(not(stm32h7))] foreach_peripheral!( (adc, $inst:ident) => { impl crate::adc::sealed::Instance for peripherals::$inst { - fn regs() -> &'static crate::pac::adc::Adc { - &crate::pac::$inst + fn regs() -> crate::pac::adc::Adc { + crate::pac::$inst } #[cfg(all(not(adc_f1), not(adc_v1)))] - fn common_regs() -> &'static crate::pac::adccommon::AdcCommon { + fn common_regs() -> crate::pac::adccommon::AdcCommon { foreach_peripheral!{ (adccommon, $common_inst:ident) => { - return &crate::pac::$common_inst + return crate::pac::$common_inst }; } } @@ -63,14 +74,14 @@ foreach_peripheral!( foreach_peripheral!( (adc, ADC3) => { impl crate::adc::sealed::Instance for peripherals::ADC3 { - fn regs() -> &'static crate::pac::adc::Adc { - &crate::pac::ADC3 + fn regs() -> crate::pac::adc::Adc { + crate::pac::ADC3 } #[cfg(all(not(adc_f1), not(adc_v1)))] - fn common_regs() -> &'static crate::pac::adccommon::AdcCommon { + fn common_regs() -> crate::pac::adccommon::AdcCommon { foreach_peripheral!{ (adccommon, ADC3_COMMON) => { - return &crate::pac::ADC3_COMMON + return crate::pac::ADC3_COMMON }; } } @@ -80,14 +91,14 @@ foreach_peripheral!( }; (adc, $inst:ident) => { impl crate::adc::sealed::Instance for peripherals::$inst { - fn regs() -> &'static crate::pac::adc::Adc { - &crate::pac::$inst + fn regs() -> crate::pac::adc::Adc { + crate::pac::$inst } #[cfg(all(not(adc_f1), not(adc_v1)))] - fn common_regs() -> &'static crate::pac::adccommon::AdcCommon { + fn common_regs() -> crate::pac::adccommon::AdcCommon { foreach_peripheral!{ (adccommon, ADC_COMMON) => { - return &crate::pac::ADC_COMMON + return crate::pac::ADC_COMMON }; } } @@ -97,19 +108,6 @@ foreach_peripheral!( }; ); -#[cfg(all(not(adc_f1), not(adc_v1)))] -foreach_peripheral!( - (adccommon, $inst:ident) => { - impl sealed::Common for peripherals::$inst { - fn regs() -> &'static crate::pac::adccommon::AdcCommon { - &crate::pac::$inst - } - } - - impl crate::adc::Common for peripherals::$inst {} - }; -); - macro_rules! impl_adc_pin { ($inst:ident, $pin:ident, $ch:expr) => { impl crate::adc::AdcPin for crate::peripherals::$pin {} diff --git a/embassy-stm32/src/adc/resolution.rs b/embassy-stm32/src/adc/resolution.rs new file mode 100644 index 000000000..67fb9b8c0 --- /dev/null +++ b/embassy-stm32/src/adc/resolution.rs @@ -0,0 +1,63 @@ +#[cfg(any(adc_v1, adc_v2, adc_v3, adc_g0))] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Resolution { + TwelveBit, + TenBit, + EightBit, + SixBit, +} + +#[cfg(adc_v4)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Resolution { + SixteenBit, + FourteenBit, + TwelveBit, + TenBit, + EightBit, +} + +impl Default for Resolution { + fn default() -> Self { + #[cfg(any(adc_v1, adc_v2, adc_v3, adc_g0))] + { + Self::TwelveBit + } + #[cfg(adc_v4)] + { + Self::SixteenBit + } + } +} + +impl From for crate::pac::adc::vals::Res { + fn from(res: Resolution) -> crate::pac::adc::vals::Res { + match res { + #[cfg(adc_v4)] + Resolution::SixteenBit => crate::pac::adc::vals::Res::SIXTEENBIT, + #[cfg(adc_v4)] + Resolution::FourteenBit => crate::pac::adc::vals::Res::FOURTEENBITV, + Resolution::TwelveBit => crate::pac::adc::vals::Res::TWELVEBIT, + Resolution::TenBit => crate::pac::adc::vals::Res::TENBIT, + Resolution::EightBit => crate::pac::adc::vals::Res::EIGHTBIT, + #[cfg(any(adc_v1, adc_v2, adc_v3, adc_g0))] + Resolution::SixBit => crate::pac::adc::vals::Res::SIXBIT, + } + } +} + +impl Resolution { + pub fn to_max_count(&self) -> u32 { + match self { + #[cfg(adc_v4)] + Resolution::SixteenBit => (1 << 16) - 1, + #[cfg(adc_v4)] + Resolution::FourteenBit => (1 << 14) - 1, + Resolution::TwelveBit => (1 << 12) - 1, + Resolution::TenBit => (1 << 10) - 1, + Resolution::EightBit => (1 << 8) - 1, + #[cfg(any(adc_v1, adc_v2, adc_v3, adc_g0))] + Resolution::SixBit => (1 << 6) - 1, + } + } +} diff --git a/embassy-stm32/src/adc/sample_time.rs b/embassy-stm32/src/adc/sample_time.rs new file mode 100644 index 000000000..0faa1e3c0 --- /dev/null +++ b/embassy-stm32/src/adc/sample_time.rs @@ -0,0 +1,106 @@ +macro_rules! impl_sample_time { + ($default_doc:expr, $default:ident, ($(($doc:expr, $variant:ident, $pac_variant:ident)),*)) => { + #[doc = concat!("ADC sample time\n\nThe default setting is ", $default_doc, " ADC clock cycles.")] + #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] + pub enum SampleTime { + $( + #[doc = concat!($doc, " ADC clock cycles.")] + $variant, + )* + } + + impl From for crate::pac::adc::vals::SampleTime { + fn from(sample_time: SampleTime) -> crate::pac::adc::vals::SampleTime { + match sample_time { + $(SampleTime::$variant => crate::pac::adc::vals::SampleTime::$pac_variant),* + } + } + } + + impl Default for SampleTime { + fn default() -> Self { + Self::$default + } + } + }; +} + +#[cfg(any(adc_f1, adc_v1))] +impl_sample_time!( + "1.5", + Cycles1_5, + ( + ("1.5", Cycles1_5, CYCLES1_5), + ("7.5", Cycles7_5, CYCLES7_5), + ("13.5", Cycles13_5, CYCLES13_5), + ("28.5", Cycles28_5, CYCLES28_5), + ("41.5", Cycles41_5, CYCLES41_5), + ("55.5", Cycles55_5, CYCLES55_5), + ("71.5", Cycles71_5, CYCLES71_5), + ("239.5", Cycles239_5, CYCLES239_5) + ) +); + +#[cfg(adc_v2)] +impl_sample_time!( + "3", + Cycles3, + ( + ("3", Cycles3, CYCLES3), + ("15", Cycles15, CYCLES15), + ("28", Cycles28, CYCLES28), + ("56", Cycles56, CYCLES56), + ("84", Cycles84, CYCLES84), + ("112", Cycles112, CYCLES112), + ("144", Cycles144, CYCLES144), + ("480", Cycles480, CYCLES480) + ) +); + +#[cfg(adc_v3)] +impl_sample_time!( + "2.5", + Cycles2_5, + ( + ("2.5", Cycles2_5, CYCLES2_5), + ("6.5", Cycles6_5, CYCLES6_5), + ("12.5", Cycles12_5, CYCLES12_5), + ("24.5", Cycles24_5, CYCLES24_5), + ("47.5", Cycles47_5, CYCLES47_5), + ("92.5", Cycles92_5, CYCLES92_5), + ("247.5", Cycles247_5, CYCLES247_5), + ("640.5", Cycles640_5, CYCLES640_5) + ) +); + +#[cfg(adc_g0)] +impl_sample_time!( + "1.5", + Cycles1_5, + ( + ("1.5", Cycles1_5, CYCLES1_5), + ("3.5", Cycles3_5, CYCLES3_5), + ("7.5", Cycles7_5, CYCLES7_5), + ("12.5", Cycles12_5, CYCLES12_5), + ("19.5", Cycles19_5, CYCLES19_5), + ("39.5", Cycles39_5, CYCLES39_5), + ("79.5", Cycles79_5, CYCLES79_5), + ("160.5", Cycles160_5, CYCLES160_5) + ) +); + +#[cfg(adc_v4)] +impl_sample_time!( + "1.5", + Cycles1_5, + ( + ("1.5", Cycles1_5, CYCLES1_5), + ("2.5", Cycles2_5, CYCLES2_5), + ("8.5", Cycles8_5, CYCLES8_5), + ("16.5", Cycles16_5, CYCLES16_5), + ("32.5", Cycles32_5, CYCLES32_5), + ("64.5", Cycles64_5, CYCLES64_5), + ("387.5", Cycles387_5, CYCLES387_5), + ("810.5", Cycles810_5, CYCLES810_5) + ) +); diff --git a/embassy-stm32/src/adc/v1.rs b/embassy-stm32/src/adc/v1.rs index 8b1378917..d9af0c55e 100644 --- a/embassy-stm32/src/adc/v1.rs +++ b/embassy-stm32/src/adc/v1.rs @@ -1 +1,159 @@ +use embassy_hal_common::into_ref; +use embedded_hal_02::blocking::delay::DelayUs; +use crate::adc::{Adc, AdcPin, Instance, InternalChannel, Resolution, SampleTime}; +use crate::peripherals::ADC; +use crate::Peripheral; + +pub const VDDA_CALIB_MV: u32 = 3300; +pub const VREF_INT: u32 = 1230; + +pub struct Vbat; +impl InternalChannel for Vbat {} +impl super::sealed::InternalChannel for Vbat { + fn channel(&self) -> u8 { + 18 + } +} + +pub struct Vref; +impl InternalChannel for Vref {} +impl super::sealed::InternalChannel for Vref { + fn channel(&self) -> u8 { + 17 + } +} + +pub struct Temperature; +impl InternalChannel for Temperature {} +impl super::sealed::InternalChannel for Temperature { + fn channel(&self) -> u8 { + 16 + } +} + +impl<'d, T: Instance> Adc<'d, T> { + pub fn new(adc: impl Peripheral

+ 'd, delay: &mut impl DelayUs) -> Self { + into_ref!(adc); + T::enable(); + T::reset(); + + // Delay 1μs when using HSI14 as the ADC clock. + // + // Table 57. ADC characteristics + // tstab = 14 * 1/fadc + delay.delay_us(1); + + let s = Self { + adc, + sample_time: Default::default(), + }; + s.calibrate(); + s + } + + pub fn enable_vbat(&self, _delay: &mut impl DelayUs) -> Vbat { + // SMP must be ≥ 56 ADC clock cycles when using HSI14. + // + // 6.3.20 Vbat monitoring characteristics + // ts_vbat ≥ 4μs + T::regs().ccr().modify(|reg| reg.set_vbaten(true)); + Vbat + } + + pub fn enable_vref(&self, delay: &mut impl DelayUs) -> Vref { + // Table 28. Embedded internal reference voltage + // tstart = 10μs + T::regs().ccr().modify(|reg| reg.set_vrefen(true)); + delay.delay_us(10); + Vref + } + + pub fn enable_temperature(&self, delay: &mut impl DelayUs) -> Temperature { + // SMP must be ≥ 56 ADC clock cycles when using HSI14. + // + // 6.3.19 Temperature sensor characteristics + // tstart ≤ 10μs + // ts_temp ≥ 4μs + T::regs().ccr().modify(|reg| reg.set_tsen(true)); + delay.delay_us(10); + Temperature + } + + fn calibrate(&self) { + // A.7.1 ADC calibration code example + if T::regs().cr().read().aden() { + T::regs().cr().modify(|reg| reg.set_addis(true)); + } + while T::regs().cr().read().aden() { + // spin + } + T::regs().cfgr1().modify(|reg| reg.set_dmaen(false)); + T::regs().cr().modify(|reg| reg.set_adcal(true)); + while T::regs().cr().read().adcal() { + // spin + } + } + + pub fn set_sample_time(&mut self, sample_time: SampleTime) { + self.sample_time = sample_time; + } + + pub fn set_resolution(&mut self, resolution: Resolution) { + T::regs().cfgr1().modify(|reg| reg.set_res(resolution.into())); + } + + pub fn read

(&mut self, pin: &mut P) -> u16 + where + P: AdcPin + crate::gpio::sealed::Pin, + { + let channel = pin.channel(); + pin.set_as_analog(); + self.read_channel(channel) + } + + pub fn read_internal(&mut self, channel: &mut impl InternalChannel) -> u16 { + let channel = channel.channel(); + self.read_channel(channel) + } + + fn read_channel(&mut self, channel: u8) -> u16 { + // A.7.2 ADC enable sequence code example + if T::regs().isr().read().adrdy() { + T::regs().isr().modify(|reg| reg.set_adrdy(true)); + } + T::regs().cr().modify(|reg| reg.set_aden(true)); + while !T::regs().isr().read().adrdy() { + // ES0233, 2.4.3 ADEN bit cannot be set immediately after the ADC calibration + // Workaround: When the ADC calibration is complete (ADCAL = 0), keep setting the + // ADEN bit until the ADRDY flag goes high. + T::regs().cr().modify(|reg| reg.set_aden(true)); + } + + T::regs().isr().modify(|reg| { + reg.set_eoc(true); + reg.set_eosmp(true); + }); + + // A.7.5 Single conversion sequence code example - Software trigger + T::regs().chselr().write(|reg| reg.set_chselx(channel as usize, true)); + T::regs().smpr().modify(|reg| reg.set_smp(self.sample_time.into())); + T::regs().cr().modify(|reg| reg.set_adstart(true)); + while !T::regs().isr().read().eoc() { + // spin + } + let value = T::regs().dr().read().0 as u16; + + // A.7.3 ADC disable code example + T::regs().cr().modify(|reg| reg.set_adstp(true)); + while T::regs().cr().read().adstp() { + // spin + } + T::regs().cr().modify(|reg| reg.set_addis(true)); + while T::regs().cr().read().aden() { + // spin + } + + value + } +} diff --git a/embassy-stm32/src/adc/v2.rs b/embassy-stm32/src/adc/v2.rs index 25b7ba967..091c1d447 100644 --- a/embassy-stm32/src/adc/v2.rs +++ b/embassy-stm32/src/adc/v2.rs @@ -1,9 +1,9 @@ -use core::marker::PhantomData; - use embassy_hal_common::into_ref; use embedded_hal_02::blocking::delay::DelayUs; -use crate::adc::{AdcPin, Instance}; +use super::InternalChannel; +use crate::adc::{Adc, AdcPin, Instance, Resolution, SampleTime}; +use crate::peripherals::ADC1; use crate::time::Hertz; use crate::Peripheral; @@ -12,111 +12,50 @@ pub const VREF_DEFAULT_MV: u32 = 3300; /// VREF voltage used for factory calibration of VREFINTCAL register. pub const VREF_CALIB_MV: u32 = 3300; -#[cfg(not(any(rcc_f4, rcc_f7)))] -fn enable() { - todo!() -} - -#[cfg(any(rcc_f4, rcc_f7))] -fn enable() { - critical_section::with(|_| unsafe { - // TODO do not enable all adc clocks if not needed - crate::pac::RCC.apb2enr().modify(|w| w.set_adc1en(true)); - crate::pac::RCC.apb2enr().modify(|w| w.set_adc2en(true)); - crate::pac::RCC.apb2enr().modify(|w| w.set_adc3en(true)); - }); -} - -pub enum Resolution { - TwelveBit, - TenBit, - EightBit, - SixBit, -} - -impl Default for Resolution { - fn default() -> Self { - Self::TwelveBit - } -} - -impl Resolution { - fn res(&self) -> crate::pac::adc::vals::Res { - match self { - Resolution::TwelveBit => crate::pac::adc::vals::Res::TWELVEBIT, - Resolution::TenBit => crate::pac::adc::vals::Res::TENBIT, - Resolution::EightBit => crate::pac::adc::vals::Res::EIGHTBIT, - Resolution::SixBit => crate::pac::adc::vals::Res::SIXBIT, - } - } - - pub fn to_max_count(&self) -> u32 { - match self { - Resolution::TwelveBit => (1 << 12) - 1, - Resolution::TenBit => (1 << 10) - 1, - Resolution::EightBit => (1 << 8) - 1, - Resolution::SixBit => (1 << 6) - 1, - } - } -} +/// ADC turn-on time +pub const ADC_POWERUP_TIME_US: u32 = 3; pub struct VrefInt; -impl AdcPin for VrefInt {} -impl super::sealed::AdcPin for VrefInt { +impl InternalChannel for VrefInt {} +impl super::sealed::InternalChannel for VrefInt { fn channel(&self) -> u8 { 17 } } +impl VrefInt { + /// Time needed for internal voltage reference to stabilize + pub fn start_time_us() -> u32 { + 10 + } +} + pub struct Temperature; -impl AdcPin for Temperature {} -impl super::sealed::AdcPin for Temperature { +impl InternalChannel for Temperature {} +impl super::sealed::InternalChannel for Temperature { fn channel(&self) -> u8 { - 16 - } -} - -pub struct Vbat; -impl AdcPin for Vbat {} -impl super::sealed::AdcPin for Vbat { - fn channel(&self) -> u8 { - 18 - } -} - -/// ADC sample time -/// -/// The default setting is 3 ADC clock cycles. -#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] -pub enum SampleTime { - Cycles3 = 0b000, - Cycles15 = 0b001, - Cycles28 = 0b010, - Cycles56 = 0b011, - Cycles85 = 0b100, - Cycles112 = 0b101, - Cycles144 = 0b110, - Cycles480 = 0b111, -} - -impl SampleTime { - pub(crate) fn sample_time(&self) -> crate::pac::adc::vals::Smp { - match self { - SampleTime::Cycles3 => crate::pac::adc::vals::Smp::CYCLES3, - SampleTime::Cycles15 => crate::pac::adc::vals::Smp::CYCLES15, - SampleTime::Cycles28 => crate::pac::adc::vals::Smp::CYCLES28, - SampleTime::Cycles56 => crate::pac::adc::vals::Smp::CYCLES56, - SampleTime::Cycles85 => crate::pac::adc::vals::Smp::CYCLES84, - SampleTime::Cycles112 => crate::pac::adc::vals::Smp::CYCLES112, - SampleTime::Cycles144 => crate::pac::adc::vals::Smp::CYCLES144, - SampleTime::Cycles480 => crate::pac::adc::vals::Smp::CYCLES480, + cfg_if::cfg_if! { + if #[cfg(any(stm32f40, stm32f41))] { + 16 + } else { + 18 + } } } } -impl Default for SampleTime { - fn default() -> Self { - Self::Cycles3 +impl Temperature { + /// Time needed for temperature sensor readings to stabilize + pub fn start_time_us() -> u32 { + 10 + } +} + +pub struct Vbat; +impl InternalChannel for Vbat {} +impl super::sealed::InternalChannel for Vbat { + fn channel(&self) -> u8 { + 18 } } @@ -151,40 +90,26 @@ impl Prescaler { } } -pub struct Adc<'d, T: Instance> { - sample_time: SampleTime, - vref_mv: u32, - resolution: Resolution, - phantom: PhantomData<&'d mut T>, -} - impl<'d, T> Adc<'d, T> where T: Instance, { - pub fn new(_peri: impl Peripheral

+ 'd, delay: &mut impl DelayUs) -> Self { - into_ref!(_peri); - enable(); + pub fn new(adc: impl Peripheral

+ 'd, delay: &mut impl DelayUs) -> Self { + into_ref!(adc); + T::enable(); + T::reset(); - let presc = unsafe { Prescaler::from_pclk2(crate::rcc::get_freqs().apb2) }; - unsafe { - T::common_regs().ccr().modify(|w| w.set_adcpre(presc.adcpre())); - } + let presc = Prescaler::from_pclk2(T::frequency()); + T::common_regs().ccr().modify(|w| w.set_adcpre(presc.adcpre())); + T::regs().cr2().modify(|reg| { + reg.set_adon(crate::pac::adc::vals::Adon::ENABLED); + }); - unsafe { - // disable before config is set - T::regs().cr2().modify(|reg| { - reg.set_adon(crate::pac::adc::vals::Adon::DISABLED); - }); - } - - delay.delay_us(20); // TODO? + delay.delay_us(ADC_POWERUP_TIME_US); Self { + adc, sample_time: Default::default(), - resolution: Resolution::default(), - vref_mv: VREF_DEFAULT_MV, - phantom: PhantomData, } } @@ -193,43 +118,62 @@ where } pub fn set_resolution(&mut self, resolution: Resolution) { - self.resolution = resolution; + T::regs().cr1().modify(|reg| reg.set_res(resolution.into())); } - /// Set VREF value in millivolts. This value is used for [to_millivolts()] sample conversion. + /// Enables internal voltage reference and returns [VrefInt], which can be used in + /// [Adc::read_internal()] to perform conversion. + pub fn enable_vrefint(&self) -> VrefInt { + T::common_regs().ccr().modify(|reg| { + reg.set_tsvrefe(crate::pac::adccommon::vals::Tsvrefe::ENABLED); + }); + + VrefInt {} + } + + /// Enables internal temperature sensor and returns [Temperature], which can be used in + /// [Adc::read_internal()] to perform conversion. /// - /// Use this if you have a known precise VREF (VDDA) pin reference voltage. - pub fn set_vref_mv(&mut self, vref_mv: u32) { - self.vref_mv = vref_mv; + /// On STM32F42 and STM32F43 this can not be used together with [Vbat]. If both are enabled, + /// temperature sensor will return vbat value. + pub fn enable_temperature(&self) -> Temperature { + T::common_regs().ccr().modify(|reg| { + reg.set_tsvrefe(crate::pac::adccommon::vals::Tsvrefe::ENABLED); + }); + + Temperature {} } - /// Convert a measurement to millivolts - pub fn to_millivolts(&self, sample: u16) -> u16 { - ((u32::from(sample) * self.vref_mv) / self.resolution.to_max_count()) as u16 + /// Enables vbat input and returns [Vbat], which can be used in + /// [Adc::read_internal()] to perform conversion. + pub fn enable_vbat(&self) -> Vbat { + T::common_regs().ccr().modify(|reg| { + reg.set_vbate(crate::pac::adccommon::vals::Vbate::ENABLED); + }); + + Vbat {} } /// Perform a single conversion. fn convert(&mut self) -> u16 { - unsafe { - // clear end of conversion flag - T::regs().sr().modify(|reg| { - reg.set_eoc(crate::pac::adc::vals::Eoc::NOTCOMPLETE); - }); + // clear end of conversion flag + T::regs().sr().modify(|reg| { + reg.set_eoc(crate::pac::adc::vals::Eoc::NOTCOMPLETE); + }); - // Start conversion - T::regs().cr2().modify(|reg| { - reg.set_swstart(true); - }); + // Start conversion + T::regs().cr2().modify(|reg| { + reg.set_swstart(true); + }); - while T::regs().sr().read().strt() == crate::pac::adc::vals::Strt::NOTSTARTED { - // spin //wait for actual start - } - while T::regs().sr().read().eoc() == crate::pac::adc::vals::Eoc::NOTCOMPLETE { - // spin //wait for finish - } - - T::regs().dr().read().0 as u16 + while T::regs().sr().read().strt() == crate::pac::adc::vals::Strt::NOTSTARTED { + // spin //wait for actual start } + while T::regs().sr().read().eoc() == crate::pac::adc::vals::Eoc::NOTCOMPLETE { + // spin //wait for finish + } + + T::regs().dr().read().0 as u16 } pub fn read

(&mut self, pin: &mut P) -> u16 @@ -237,54 +181,41 @@ where P: AdcPin, P: crate::gpio::sealed::Pin, { - unsafe { - // dissable ADC - T::regs().cr2().modify(|reg| { - reg.set_swstart(false); - }); - T::regs().cr2().modify(|reg| { - reg.set_adon(crate::pac::adc::vals::Adon::DISABLED); - }); + pin.set_as_analog(); - pin.set_as_analog(); - - // Configure ADC - T::regs().cr1().modify(|reg| reg.set_res(self.resolution.res())); - - // Select channel - T::regs().sqr3().write(|reg| reg.set_sq(0, pin.channel())); - - // Configure channel - Self::set_channel_sample_time(pin.channel(), self.sample_time); - - // enable adc - T::regs().cr2().modify(|reg| { - reg.set_adon(crate::pac::adc::vals::Adon::ENABLED); - }); - - let val = self.convert(); - - // dissable ADC - T::regs().cr2().modify(|reg| { - reg.set_swstart(false); - }); - T::regs().cr2().modify(|reg| { - reg.set_adon(crate::pac::adc::vals::Adon::DISABLED); - }); - - val - } + self.read_channel(pin.channel()) } - unsafe fn set_channel_sample_time(ch: u8, sample_time: SampleTime) { + pub fn read_internal(&mut self, channel: &mut impl InternalChannel) -> u16 { + self.read_channel(channel.channel()) + } + + fn read_channel(&mut self, channel: u8) -> u16 { + // Configure ADC + + // Select channel + T::regs().sqr3().write(|reg| reg.set_sq(0, channel)); + + // Configure channel + Self::set_channel_sample_time(channel, self.sample_time); + + let val = self.convert(); + + val + } + + fn set_channel_sample_time(ch: u8, sample_time: SampleTime) { + let sample_time = sample_time.into(); if ch <= 9 { - T::regs() - .smpr2() - .modify(|reg| reg.set_smp(ch as _, sample_time.sample_time())); + T::regs().smpr2().modify(|reg| reg.set_smp(ch as _, sample_time)); } else { - T::regs() - .smpr1() - .modify(|reg| reg.set_smp((ch - 10) as _, sample_time.sample_time())); + T::regs().smpr1().modify(|reg| reg.set_smp((ch - 10) as _, sample_time)); } } } + +impl<'d, T: Instance> Drop for Adc<'d, T> { + fn drop(&mut self) { + T::disable(); + } +} diff --git a/embassy-stm32/src/adc/v3.rs b/embassy-stm32/src/adc/v3.rs index 0f1090888..3a6e58cf6 100644 --- a/embassy-stm32/src/adc/v3.rs +++ b/embassy-stm32/src/adc/v3.rs @@ -1,9 +1,7 @@ -use core::marker::PhantomData; - use embassy_hal_common::into_ref; use embedded_hal_02::blocking::delay::DelayUs; -use crate::adc::{AdcPin, Instance}; +use crate::adc::{Adc, AdcPin, Instance, Resolution, SampleTime}; use crate::Peripheral; /// Default VREF voltage used for sample conversion to millivolts. @@ -14,7 +12,7 @@ pub const VREF_CALIB_MV: u32 = 3000; /// Sadly we cannot use `RccPeripheral::enable` since devices are quite inconsistent ADC clock /// configuration. fn enable() { - critical_section::with(|_| unsafe { + critical_section::with(|_| { #[cfg(stm32h7)] crate::pac::RCC.apb2enr().modify(|w| w.set_adcen(true)); #[cfg(stm32g0)] @@ -24,39 +22,6 @@ fn enable() { }); } -pub enum Resolution { - TwelveBit, - TenBit, - EightBit, - SixBit, -} - -impl Default for Resolution { - fn default() -> Self { - Self::TwelveBit - } -} - -impl Resolution { - fn res(&self) -> crate::pac::adc::vals::Res { - match self { - Resolution::TwelveBit => crate::pac::adc::vals::Res::TWELVEBIT, - Resolution::TenBit => crate::pac::adc::vals::Res::TENBIT, - Resolution::EightBit => crate::pac::adc::vals::Res::EIGHTBIT, - Resolution::SixBit => crate::pac::adc::vals::Res::SIXBIT, - } - } - - pub fn to_max_count(&self) -> u32 { - match self { - Resolution::TwelveBit => (1 << 12) - 1, - Resolution::TenBit => (1 << 10) - 1, - Resolution::EightBit => (1 << 8) - 1, - Resolution::SixBit => (1 << 6) - 1, - } - } -} - pub struct VrefInt; impl AdcPin for VrefInt {} impl super::sealed::AdcPin for VrefInt { @@ -93,168 +58,43 @@ impl super::sealed::AdcPin for Vbat { } } -#[cfg(not(adc_g0))] -mod sample_time { - /// ADC sample time - /// - /// The default setting is 2.5 ADC clock cycles. - #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] - pub enum SampleTime { - /// 2.5 ADC clock cycles - Cycles2_5 = 0b000, - - /// 6.5 ADC clock cycles - Cycles6_5 = 0b001, - - /// 12.5 ADC clock cycles - Cycles12_5 = 0b010, - - /// 24.5 ADC clock cycles - Cycles24_5 = 0b011, - - /// 47.5 ADC clock cycles - Cycles47_5 = 0b100, - - /// 92.5 ADC clock cycles - Cycles92_5 = 0b101, - - /// 247.5 ADC clock cycles - Cycles247_5 = 0b110, - - /// 640.5 ADC clock cycles - Cycles640_5 = 0b111, - } - - impl SampleTime { - pub(crate) fn sample_time(&self) -> crate::pac::adc::vals::SampleTime { - match self { - SampleTime::Cycles2_5 => crate::pac::adc::vals::SampleTime::CYCLES2_5, - SampleTime::Cycles6_5 => crate::pac::adc::vals::SampleTime::CYCLES6_5, - SampleTime::Cycles12_5 => crate::pac::adc::vals::SampleTime::CYCLES12_5, - SampleTime::Cycles24_5 => crate::pac::adc::vals::SampleTime::CYCLES24_5, - SampleTime::Cycles47_5 => crate::pac::adc::vals::SampleTime::CYCLES47_5, - SampleTime::Cycles92_5 => crate::pac::adc::vals::SampleTime::CYCLES92_5, - SampleTime::Cycles247_5 => crate::pac::adc::vals::SampleTime::CYCLES247_5, - SampleTime::Cycles640_5 => crate::pac::adc::vals::SampleTime::CYCLES640_5, - } - } - } - - impl Default for SampleTime { - fn default() -> Self { - Self::Cycles2_5 - } - } -} - -#[cfg(adc_g0)] -mod sample_time { - /// ADC sample time - /// - /// The default setting is 1.5 ADC clock cycles. - #[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] - pub enum SampleTime { - /// 1.5 ADC clock cycles - Cycles1_5 = 0b000, - - /// 3.5 ADC clock cycles - Cycles3_5 = 0b001, - - /// 7.5 ADC clock cycles - Cycles7_5 = 0b010, - - /// 12.5 ADC clock cycles - Cycles12_5 = 0b011, - - /// 19.5 ADC clock cycles - Cycles19_5 = 0b100, - - /// 39.5 ADC clock cycles - Cycles39_5 = 0b101, - - /// 79.5 ADC clock cycles - Cycles79_5 = 0b110, - - /// 160.5 ADC clock cycles - Cycles160_5 = 0b111, - } - - impl SampleTime { - pub(crate) fn sample_time(&self) -> crate::pac::adc::vals::SampleTime { - match self { - SampleTime::Cycles1_5 => crate::pac::adc::vals::SampleTime::CYCLES1_5, - SampleTime::Cycles3_5 => crate::pac::adc::vals::SampleTime::CYCLES3_5, - SampleTime::Cycles7_5 => crate::pac::adc::vals::SampleTime::CYCLES7_5, - SampleTime::Cycles12_5 => crate::pac::adc::vals::SampleTime::CYCLES12_5, - SampleTime::Cycles19_5 => crate::pac::adc::vals::SampleTime::CYCLES19_5, - SampleTime::Cycles39_5 => crate::pac::adc::vals::SampleTime::CYCLES39_5, - SampleTime::Cycles79_5 => crate::pac::adc::vals::SampleTime::CYCLES79_5, - SampleTime::Cycles160_5 => crate::pac::adc::vals::SampleTime::CYCLES160_5, - } - } - } - - impl Default for SampleTime { - fn default() -> Self { - Self::Cycles1_5 - } - } -} - -pub use sample_time::SampleTime; - -pub struct Adc<'d, T: Instance> { - sample_time: SampleTime, - vref_mv: u32, - resolution: Resolution, - phantom: PhantomData<&'d mut T>, -} - impl<'d, T: Instance> Adc<'d, T> { - pub fn new(_peri: impl Peripheral

+ 'd, delay: &mut impl DelayUs) -> Self { - into_ref!(_peri); + pub fn new(adc: impl Peripheral

+ 'd, delay: &mut impl DelayUs) -> Self { + into_ref!(adc); enable(); - unsafe { - T::regs().cr().modify(|reg| { - #[cfg(not(adc_g0))] - reg.set_deeppwd(false); - reg.set_advregen(true); - }); + T::regs().cr().modify(|reg| { + #[cfg(not(adc_g0))] + reg.set_deeppwd(false); + reg.set_advregen(true); + }); - #[cfg(adc_g0)] - T::regs().cfgr1().modify(|reg| { - reg.set_chselrmod(true); - }); - } + #[cfg(adc_g0)] + T::regs().cfgr1().modify(|reg| { + reg.set_chselrmod(false); + }); delay.delay_us(20); - unsafe { - T::regs().cr().modify(|reg| { - reg.set_adcal(true); - }); + T::regs().cr().modify(|reg| { + reg.set_adcal(true); + }); - while T::regs().cr().read().adcal() { - // spin - } + while T::regs().cr().read().adcal() { + // spin } delay.delay_us(1); Self { + adc, sample_time: Default::default(), - resolution: Resolution::default(), - vref_mv: VREF_DEFAULT_MV, - phantom: PhantomData, } } pub fn enable_vrefint(&self, delay: &mut impl DelayUs) -> VrefInt { - unsafe { - T::common_regs().ccr().modify(|reg| { - reg.set_vrefen(true); - }); - } + T::common_regs().ccr().modify(|reg| { + reg.set_vrefen(true); + }); // "Table 24. Embedded internal voltage reference" states that it takes a maximum of 12 us // to stabilize the internal voltage reference, we wait a little more. @@ -266,68 +106,30 @@ impl<'d, T: Instance> Adc<'d, T> { } pub fn enable_temperature(&self) -> Temperature { - unsafe { - T::common_regs().ccr().modify(|reg| { - reg.set_ch17sel(true); - }); - } + T::common_regs().ccr().modify(|reg| { + reg.set_ch17sel(true); + }); Temperature {} } pub fn enable_vbat(&self) -> Vbat { - unsafe { - T::common_regs().ccr().modify(|reg| { - reg.set_ch18sel(true); - }); - } + T::common_regs().ccr().modify(|reg| { + reg.set_ch18sel(true); + }); Vbat {} } - /// Calculates the system VDDA by sampling the internal VREFINT channel and comparing - /// the result with the value stored at the factory. If the chip's VDDA is not stable, run - /// this before each ADC conversion. - #[cfg(not(stm32g0))] // TODO is this supposed to be public? - #[allow(unused)] // TODO is this supposed to be public? - fn calibrate(&mut self, vrefint: &mut VrefInt) { - #[cfg(stm32l5)] - let vrefint_cal: u32 = todo!(); - #[cfg(not(stm32l5))] - let vrefint_cal = unsafe { crate::pac::VREFINTCAL.data().read().value() }; - let old_sample_time = self.sample_time; - - // "Table 24. Embedded internal voltage reference" states that the sample time needs to be - // at a minimum 4 us. With 640.5 ADC cycles we have a minimum of 8 us at 80 MHz, leaving - // some headroom. - self.sample_time = SampleTime::Cycles640_5; - - // This can't actually fail, it's just in a result to satisfy hal trait - let vrefint_samp = self.read(vrefint); - - self.sample_time = old_sample_time; - - self.vref_mv = (VREF_CALIB_MV * u32::from(vrefint_cal)) / u32::from(vrefint_samp); - } - pub fn set_sample_time(&mut self, sample_time: SampleTime) { self.sample_time = sample_time; } pub fn set_resolution(&mut self, resolution: Resolution) { - self.resolution = resolution; - } - - /// Set VREF value in millivolts. This value is used for [to_millivolts()] sample conversion. - /// - /// Use this if you have a known precise VREF (VDDA) pin reference voltage. - pub fn set_vref_mv(&mut self, vref_mv: u32) { - self.vref_mv = vref_mv; - } - - /// Convert a measurement to millivolts - pub fn to_millivolts(&self, sample: u16) -> u16 { - ((u32::from(sample) * self.vref_mv) / self.resolution.to_max_count()) as u16 + #[cfg(not(stm32g0))] + T::regs().cfgr().modify(|reg| reg.set_res(resolution.into())); + #[cfg(stm32g0)] + T::regs().cfgr1().modify(|reg| reg.set_res(resolution.into())); } /* @@ -341,91 +143,76 @@ impl<'d, T: Instance> Adc<'d, T> { /// Perform a single conversion. fn convert(&mut self) -> u16 { - unsafe { - T::regs().isr().modify(|reg| { - reg.set_eos(true); - reg.set_eoc(true); - }); + T::regs().isr().modify(|reg| { + reg.set_eos(true); + reg.set_eoc(true); + }); - // Start conversion - T::regs().cr().modify(|reg| { - reg.set_adstart(true); - }); + // Start conversion + T::regs().cr().modify(|reg| { + reg.set_adstart(true); + }); - while !T::regs().isr().read().eos() { - // spin - } - - T::regs().dr().read().0 as u16 + while !T::regs().isr().read().eos() { + // spin } + + T::regs().dr().read().0 as u16 } pub fn read(&mut self, pin: &mut impl AdcPin) -> u16 { - unsafe { - // Make sure bits are off - while T::regs().cr().read().addis() { - // spin - } - - // Enable ADC - T::regs().isr().modify(|reg| { - reg.set_adrdy(true); - }); - T::regs().cr().modify(|reg| { - reg.set_aden(true); - }); - - while !T::regs().isr().read().adrdy() { - // spin - } - - // Configure ADC - #[cfg(not(stm32g0))] - T::regs().cfgr().modify(|reg| reg.set_res(self.resolution.res())); - #[cfg(stm32g0)] - T::regs().cfgr1().modify(|reg| reg.set_res(self.resolution.res())); - - // Configure channel - Self::set_channel_sample_time(pin.channel(), self.sample_time); - - // Select channel - #[cfg(not(stm32g0))] - T::regs().sqr1().write(|reg| reg.set_sq(0, pin.channel())); - #[cfg(stm32g0)] - T::regs().chselr().write(|reg| reg.set_chsel(pin.channel() as u32)); - - // Some models are affected by an erratum: - // If we perform conversions slower than 1 kHz, the first read ADC value can be - // corrupted, so we discard it and measure again. - // - // STM32L471xx: Section 2.7.3 - // STM32G4: Section 2.7.3 - #[cfg(any(rcc_l4, rcc_g4))] - let _ = self.convert(); - - let val = self.convert(); - - T::regs().cr().modify(|reg| reg.set_addis(true)); - - val + // Make sure bits are off + while T::regs().cr().read().addis() { + // spin } + + // Enable ADC + T::regs().isr().modify(|reg| { + reg.set_adrdy(true); + }); + T::regs().cr().modify(|reg| { + reg.set_aden(true); + }); + + while !T::regs().isr().read().adrdy() { + // spin + } + + // Configure channel + Self::set_channel_sample_time(pin.channel(), self.sample_time); + + // Select channel + #[cfg(not(stm32g0))] + T::regs().sqr1().write(|reg| reg.set_sq(0, pin.channel())); + #[cfg(stm32g0)] + T::regs().chselr().write(|reg| reg.set_chsel(1 << pin.channel())); + + // Some models are affected by an erratum: + // If we perform conversions slower than 1 kHz, the first read ADC value can be + // corrupted, so we discard it and measure again. + // + // STM32L471xx: Section 2.7.3 + // STM32G4: Section 2.7.3 + #[cfg(any(rcc_l4, rcc_g4))] + let _ = self.convert(); + + let val = self.convert(); + + T::regs().cr().modify(|reg| reg.set_addis(true)); + + val } #[cfg(stm32g0)] - unsafe fn set_channel_sample_time(_ch: u8, sample_time: SampleTime) { - T::regs().smpr().modify(|reg| reg.set_smp1(sample_time.sample_time())); + fn set_channel_sample_time(_ch: u8, sample_time: SampleTime) { + T::regs().smpr().modify(|reg| reg.set_smp1(sample_time.into())); } #[cfg(not(stm32g0))] - unsafe fn set_channel_sample_time(ch: u8, sample_time: SampleTime) { - if ch <= 9 { - T::regs() - .smpr1() - .modify(|reg| reg.set_smp(ch as _, sample_time.sample_time())); - } else { - T::regs() - .smpr2() - .modify(|reg| reg.set_smp((ch - 10) as _, sample_time.sample_time())); - } + fn set_channel_sample_time(ch: u8, sample_time: SampleTime) { + let sample_time = sample_time.into(); + T::regs() + .smpr(ch as usize / 10) + .modify(|reg| reg.set_smp(ch as usize % 10, sample_time)); } } diff --git a/embassy-stm32/src/adc/v4.rs b/embassy-stm32/src/adc/v4.rs index d356d7b66..c51c6840f 100644 --- a/embassy-stm32/src/adc/v4.rs +++ b/embassy-stm32/src/adc/v4.rs @@ -1,11 +1,10 @@ -use core::marker::PhantomData; +use core::sync::atomic::{AtomicU8, Ordering}; -use atomic_polyfill::{AtomicU8, Ordering}; use embedded_hal_02::blocking::delay::DelayUs; use pac::adc::vals::{Adcaldif, Boost, Difsel, Exten, Pcsel}; use pac::adccommon::vals::Presc; -use super::{AdcPin, Instance}; +use super::{Adc, AdcPin, Instance, InternalChannel, Resolution, SampleTime}; use crate::time::Hertz; use crate::{pac, Peripheral}; @@ -14,54 +13,10 @@ pub const VREF_DEFAULT_MV: u32 = 3300; /// VREF voltage used for factory calibration of VREFINTCAL register. pub const VREF_CALIB_MV: u32 = 3300; -pub enum Resolution { - SixteenBit, - FourteenBit, - TwelveBit, - TenBit, - EightBit, -} - -impl Default for Resolution { - fn default() -> Self { - Self::SixteenBit - } -} - -impl Resolution { - fn res(&self) -> pac::adc::vals::Res { - match self { - Resolution::SixteenBit => pac::adc::vals::Res::SIXTEENBIT, - Resolution::FourteenBit => pac::adc::vals::Res::FOURTEENBITV, - Resolution::TwelveBit => pac::adc::vals::Res::TWELVEBITV, - Resolution::TenBit => pac::adc::vals::Res::TENBIT, - Resolution::EightBit => pac::adc::vals::Res::EIGHTBIT, - } - } - - pub fn to_max_count(&self) -> u32 { - match self { - Resolution::SixteenBit => (1 << 16) - 1, - Resolution::FourteenBit => (1 << 14) - 1, - Resolution::TwelveBit => (1 << 12) - 1, - Resolution::TenBit => (1 << 10) - 1, - Resolution::EightBit => (1 << 8) - 1, - } - } -} - -pub trait InternalChannel: sealed::InternalChannel {} - -mod sealed { - pub trait InternalChannel { - fn channel(&self) -> u8; - } -} - // NOTE: Vrefint/Temperature/Vbat are only available on ADC3 on H7, this currently cannot be modeled with stm32-data, so these are available from the software on all ADCs pub struct VrefInt; impl InternalChannel for VrefInt {} -impl sealed::InternalChannel for VrefInt { +impl super::sealed::InternalChannel for VrefInt { fn channel(&self) -> u8 { 19 } @@ -69,7 +24,7 @@ impl sealed::InternalChannel for VrefInt { pub struct Temperature; impl InternalChannel for Temperature {} -impl sealed::InternalChannel for Temperature { +impl super::sealed::InternalChannel for Temperature { fn channel(&self) -> u8 { 18 } @@ -77,7 +32,7 @@ impl sealed::InternalChannel for Temperature { pub struct Vbat; impl InternalChannel for Vbat {} -impl sealed::InternalChannel for Vbat { +impl super::sealed::InternalChannel for Vbat { fn channel(&self) -> u8 { // TODO this should be 14 for H7a/b/35 17 @@ -91,8 +46,8 @@ foreach_peripheral!( (adc, ADC1) => { impl crate::rcc::sealed::RccPeripheral for crate::peripherals::ADC1 { fn frequency() -> crate::time::Hertz { - critical_section::with(|_| unsafe { - match crate::rcc::get_freqs().adc { + critical_section::with(|_| { + match unsafe { crate::rcc::get_freqs() }.adc { Some(ck) => ck, None => panic!("Invalid ADC clock configuration, AdcClockSource was likely not properly configured.") } @@ -100,7 +55,7 @@ foreach_peripheral!( } fn enable() { - critical_section::with(|_| unsafe { + critical_section::with(|_| { crate::pac::RCC.ahb1enr().modify(|w| w.set_adc12en(true)) }); ADC12_ENABLE_COUNTER.fetch_add(1, Ordering::SeqCst); @@ -108,7 +63,7 @@ foreach_peripheral!( fn disable() { if ADC12_ENABLE_COUNTER.load(Ordering::SeqCst) == 1 { - critical_section::with(|_| unsafe { + critical_section::with(|_| { crate::pac::RCC.ahb1enr().modify(|w| w.set_adc12en(false)); }) } @@ -117,7 +72,7 @@ foreach_peripheral!( fn reset() { if ADC12_ENABLE_COUNTER.load(Ordering::SeqCst) == 1 { - critical_section::with(|_| unsafe { + critical_section::with(|_| { crate::pac::RCC.ahb1rstr().modify(|w| w.set_adc12rst(true)); crate::pac::RCC.ahb1rstr().modify(|w| w.set_adc12rst(false)); }); @@ -130,8 +85,8 @@ foreach_peripheral!( (adc, ADC2) => { impl crate::rcc::sealed::RccPeripheral for crate::peripherals::ADC2 { fn frequency() -> crate::time::Hertz { - critical_section::with(|_| unsafe { - match crate::rcc::get_freqs().adc { + critical_section::with(|_| { + match unsafe { crate::rcc::get_freqs() }.adc { Some(ck) => ck, None => panic!("Invalid ADC clock configuration, AdcClockSource was likely not properly configured.") } @@ -139,7 +94,7 @@ foreach_peripheral!( } fn enable() { - critical_section::with(|_| unsafe { + critical_section::with(|_| { crate::pac::RCC.ahb1enr().modify(|w| w.set_adc12en(true)) }); ADC12_ENABLE_COUNTER.fetch_add(1, Ordering::SeqCst); @@ -147,7 +102,7 @@ foreach_peripheral!( fn disable() { if ADC12_ENABLE_COUNTER.load(Ordering::SeqCst) == 1 { - critical_section::with(|_| unsafe { + critical_section::with(|_| { crate::pac::RCC.ahb1enr().modify(|w| w.set_adc12en(false)); }) } @@ -156,7 +111,7 @@ foreach_peripheral!( fn reset() { if ADC12_ENABLE_COUNTER.load(Ordering::SeqCst) == 1 { - critical_section::with(|_| unsafe { + critical_section::with(|_| { crate::pac::RCC.ahb1rstr().modify(|w| w.set_adc12rst(true)); crate::pac::RCC.ahb1rstr().modify(|w| w.set_adc12rst(false)); }); @@ -169,8 +124,8 @@ foreach_peripheral!( (adc, ADC3) => { impl crate::rcc::sealed::RccPeripheral for crate::peripherals::ADC3 { fn frequency() -> crate::time::Hertz { - critical_section::with(|_| unsafe { - match crate::rcc::get_freqs().adc { + critical_section::with(|_| { + match unsafe { crate::rcc::get_freqs() }.adc { Some(ck) => ck, None => panic!("Invalid ADC clock configuration, AdcClockSource was likely not properly configured.") } @@ -178,22 +133,22 @@ foreach_peripheral!( } fn enable() { - critical_section::with(|_| unsafe { + critical_section::with(|_| { crate::pac::RCC.ahb4enr().modify(|w| w.set_adc3en(true)) }); } fn disable() { - critical_section::with(|_| unsafe { - crate::pac::RCC.ahb4enr().modify(|w| w.set_adc3en(false)); - }) + critical_section::with(|_| { + crate::pac::RCC.ahb4enr().modify(|w| w.set_adc3en(false)); + }) } fn reset() { - critical_section::with(|_| unsafe { - crate::pac::RCC.ahb4rstr().modify(|w| w.set_adc3rst(true)); - crate::pac::RCC.ahb4rstr().modify(|w| w.set_adc3rst(false)); - }); + critical_section::with(|_| { + crate::pac::RCC.ahb4rstr().modify(|w| w.set_adc3rst(true)); + crate::pac::RCC.ahb4rstr().modify(|w| w.set_adc3rst(false)); + }); } } @@ -201,57 +156,6 @@ foreach_peripheral!( }; ); -/// ADC sample time -/// -/// The default setting is 2.5 ADC clock cycles. -#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] -pub enum SampleTime { - /// 1.5 ADC clock cycles - Cycles1_5, - - /// 2.5 ADC clock cycles - Cycles2_5, - - /// 8.5 ADC clock cycles - Cycles8_5, - - /// 16.5 ADC clock cycles - Cycles16_5, - - /// 32.5 ADC clock cycles - Cycles32_5, - - /// 64.5 ADC clock cycles - Cycles64_5, - - /// 387.5 ADC clock cycles - Cycles387_5, - - /// 810.5 ADC clock cycles - Cycles810_5, -} - -impl SampleTime { - pub(crate) fn sample_time(&self) -> pac::adc::vals::Smp { - match self { - SampleTime::Cycles1_5 => pac::adc::vals::Smp::CYCLES1_5, - SampleTime::Cycles2_5 => pac::adc::vals::Smp::CYCLES2_5, - SampleTime::Cycles8_5 => pac::adc::vals::Smp::CYCLES8_5, - SampleTime::Cycles16_5 => pac::adc::vals::Smp::CYCLES16_5, - SampleTime::Cycles32_5 => pac::adc::vals::Smp::CYCLES32_5, - SampleTime::Cycles64_5 => pac::adc::vals::Smp::CYCLES64_5, - SampleTime::Cycles387_5 => pac::adc::vals::Smp::CYCLES387_5, - SampleTime::Cycles810_5 => pac::adc::vals::Smp::CYCLES810_5, - } - } -} - -impl Default for SampleTime { - fn default() -> Self { - Self::Cycles1_5 - } -} - // NOTE (unused): The prescaler enum closely copies the hardware capabilities, // but high prescaling doesn't make a lot of sense in the current implementation and is ommited. #[allow(unused)] @@ -320,24 +224,15 @@ impl Prescaler { } } -pub struct Adc<'d, T: Instance> { - sample_time: SampleTime, - vref_mv: u32, - resolution: Resolution, - phantom: PhantomData<&'d mut T>, -} - -impl<'d, T: Instance + crate::rcc::RccPeripheral> Adc<'d, T> { - pub fn new(_peri: impl Peripheral

+ 'd, delay: &mut impl DelayUs) -> Self { - embassy_hal_common::into_ref!(_peri); +impl<'d, T: Instance> Adc<'d, T> { + pub fn new(adc: impl Peripheral

+ 'd, delay: &mut impl DelayUs) -> Self { + embassy_hal_common::into_ref!(adc); T::enable(); T::reset(); let prescaler = Prescaler::from_ker_ck(T::frequency()); - unsafe { - T::common_regs().ccr().modify(|w| w.set_presc(prescaler.presc())); - } + T::common_regs().ccr().modify(|w| w.set_presc(prescaler.presc())); let frequency = Hertz(T::frequency().0 / prescaler.divisor()); info!("ADC frequency set to {} Hz", frequency.0); @@ -354,15 +249,11 @@ impl<'d, T: Instance + crate::rcc::RccPeripheral> Adc<'d, T> { } else { Boost::LT50 }; - unsafe { - T::regs().cr().modify(|w| w.set_boost(boost)); - } + T::regs().cr().modify(|w| w.set_boost(boost)); let mut s = Self { + adc, sample_time: Default::default(), - vref_mv: VREF_DEFAULT_MV, - resolution: Resolution::default(), - phantom: PhantomData, }; s.power_up(delay); s.configure_differential_inputs(); @@ -377,84 +268,68 @@ impl<'d, T: Instance + crate::rcc::RccPeripheral> Adc<'d, T> { } fn power_up(&mut self, delay: &mut impl DelayUs) { - unsafe { - T::regs().cr().modify(|reg| { - reg.set_deeppwd(false); - reg.set_advregen(true); - }); - } + T::regs().cr().modify(|reg| { + reg.set_deeppwd(false); + reg.set_advregen(true); + }); delay.delay_us(10); } fn configure_differential_inputs(&mut self) { - unsafe { - T::regs().difsel().modify(|w| { - for n in 0..20 { - w.set_difsel(n, Difsel::SINGLEENDED); - } - }) - }; + T::regs().difsel().modify(|w| { + for n in 0..20 { + w.set_difsel(n, Difsel::SINGLEENDED); + } + }); } fn calibrate(&mut self) { - unsafe { - T::regs().cr().modify(|w| { - w.set_adcaldif(Adcaldif::SINGLEENDED); - w.set_adcallin(true); - }); + T::regs().cr().modify(|w| { + w.set_adcaldif(Adcaldif::SINGLEENDED); + w.set_adcallin(true); + }); - T::regs().cr().modify(|w| w.set_adcal(true)); + T::regs().cr().modify(|w| w.set_adcal(true)); - while T::regs().cr().read().adcal() {} - } + while T::regs().cr().read().adcal() {} } fn enable(&mut self) { - unsafe { - T::regs().isr().write(|w| w.set_adrdy(true)); - T::regs().cr().modify(|w| w.set_aden(true)); - while !T::regs().isr().read().adrdy() {} - T::regs().isr().write(|w| w.set_adrdy(true)); - } + T::regs().isr().write(|w| w.set_adrdy(true)); + T::regs().cr().modify(|w| w.set_aden(true)); + while !T::regs().isr().read().adrdy() {} + T::regs().isr().write(|w| w.set_adrdy(true)); } fn configure(&mut self) { // single conversion mode, software trigger - unsafe { - T::regs().cfgr().modify(|w| { - w.set_cont(false); - w.set_exten(Exten::DISABLED); - }) - } + T::regs().cfgr().modify(|w| { + w.set_cont(false); + w.set_exten(Exten::DISABLED); + }); } pub fn enable_vrefint(&self) -> VrefInt { - unsafe { - T::common_regs().ccr().modify(|reg| { - reg.set_vrefen(true); - }); - } + T::common_regs().ccr().modify(|reg| { + reg.set_vrefen(true); + }); VrefInt {} } pub fn enable_temperature(&self) -> Temperature { - unsafe { - T::common_regs().ccr().modify(|reg| { - reg.set_vsenseen(true); - }); - } + T::common_regs().ccr().modify(|reg| { + reg.set_vsenseen(true); + }); Temperature {} } pub fn enable_vbat(&self) -> Vbat { - unsafe { - T::common_regs().ccr().modify(|reg| { - reg.set_vbaten(true); - }); - } + T::common_regs().ccr().modify(|reg| { + reg.set_vbaten(true); + }); Vbat {} } @@ -464,40 +339,26 @@ impl<'d, T: Instance + crate::rcc::RccPeripheral> Adc<'d, T> { } pub fn set_resolution(&mut self, resolution: Resolution) { - self.resolution = resolution; - } - - /// Set VREF value in millivolts. This value is used for [to_millivolts()] sample conversion. - /// - /// Use this if you have a known precise VREF (VDDA) pin reference voltage. - pub fn set_vref_mv(&mut self, vref_mv: u32) { - self.vref_mv = vref_mv; - } - - /// Convert a measurement to millivolts - pub fn to_millivolts(&self, sample: u16) -> u16 { - ((u32::from(sample) * self.vref_mv) / self.resolution.to_max_count()) as u16 + T::regs().cfgr().modify(|reg| reg.set_res(resolution.into())); } /// Perform a single conversion. fn convert(&mut self) -> u16 { - unsafe { - T::regs().isr().modify(|reg| { - reg.set_eos(true); - reg.set_eoc(true); - }); + T::regs().isr().modify(|reg| { + reg.set_eos(true); + reg.set_eoc(true); + }); - // Start conversion - T::regs().cr().modify(|reg| { - reg.set_adstart(true); - }); + // Start conversion + T::regs().cr().modify(|reg| { + reg.set_adstart(true); + }); - while !T::regs().isr().read().eos() { - // spin - } - - T::regs().dr().read().0 as u16 + while !T::regs().isr().read().eos() { + // spin } + + T::regs().dr().read().0 as u16 } pub fn read

(&mut self, pin: &mut P) -> u16 @@ -505,21 +366,16 @@ impl<'d, T: Instance + crate::rcc::RccPeripheral> Adc<'d, T> { P: AdcPin, P: crate::gpio::sealed::Pin, { - unsafe { - pin.set_as_analog(); + pin.set_as_analog(); - self.read_channel(pin.channel()) - } + self.read_channel(pin.channel()) } pub fn read_internal(&mut self, channel: &mut impl InternalChannel) -> u16 { - unsafe { self.read_channel(channel.channel()) } + self.read_channel(channel.channel()) } - unsafe fn read_channel(&mut self, channel: u8) -> u16 { - // Configure ADC - T::regs().cfgr().modify(|reg| reg.set_res(self.resolution.res())); - + fn read_channel(&mut self, channel: u8) -> u16 { // Configure channel Self::set_channel_sample_time(channel, self.sample_time); @@ -535,15 +391,12 @@ impl<'d, T: Instance + crate::rcc::RccPeripheral> Adc<'d, T> { self.convert() } - unsafe fn set_channel_sample_time(ch: u8, sample_time: SampleTime) { + fn set_channel_sample_time(ch: u8, sample_time: SampleTime) { + let sample_time = sample_time.into(); if ch <= 9 { - T::regs() - .smpr(0) - .modify(|reg| reg.set_smp(ch as _, sample_time.sample_time())); + T::regs().smpr(0).modify(|reg| reg.set_smp(ch as _, sample_time)); } else { - T::regs() - .smpr(1) - .modify(|reg| reg.set_smp((ch - 10) as _, sample_time.sample_time())); + T::regs().smpr(1).modify(|reg| reg.set_smp((ch - 10) as _, sample_time)); } } } diff --git a/embassy-stm32/src/can/bxcan.rs b/embassy-stm32/src/can/bxcan.rs index c0bd44e0f..5a0153464 100644 --- a/embassy-stm32/src/can/bxcan.rs +++ b/embassy-stm32/src/can/bxcan.rs @@ -1,49 +1,443 @@ +use core::cell::{RefCell, RefMut}; +use core::future::poll_fn; +use core::marker::PhantomData; use core::ops::{Deref, DerefMut}; +use core::task::Poll; pub use bxcan; +use bxcan::{Data, ExtendedId, Frame, Id, StandardId}; use embassy_hal_common::{into_ref, PeripheralRef}; +use futures::FutureExt; use crate::gpio::sealed::AFType; +use crate::interrupt::typelevel::Interrupt; +use crate::pac::can::vals::{Lec, RirIde}; use crate::rcc::RccPeripheral; -use crate::{peripherals, Peripheral}; +use crate::time::Hertz; +use crate::{interrupt, peripherals, Peripheral}; + +/// Interrupt handler. +pub struct TxInterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for TxInterruptHandler { + unsafe fn on_interrupt() { + T::regs().tsr().write(|v| { + v.set_rqcp(0, true); + v.set_rqcp(1, true); + v.set_rqcp(2, true); + }); + + T::state().tx_waker.wake(); + } +} + +pub struct Rx0InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for Rx0InterruptHandler { + unsafe fn on_interrupt() { + // info!("rx0 irq"); + Can::::receive_fifo(RxFifo::Fifo0); + } +} + +pub struct Rx1InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for Rx1InterruptHandler { + unsafe fn on_interrupt() { + // info!("rx1 irq"); + Can::::receive_fifo(RxFifo::Fifo1); + } +} + +pub struct SceInterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for SceInterruptHandler { + unsafe fn on_interrupt() { + // info!("sce irq"); + let msr = T::regs().msr(); + let msr_val = msr.read(); + + if msr_val.erri() { + msr.modify(|v| v.set_erri(true)); + T::state().err_waker.wake(); + } + } +} pub struct Can<'d, T: Instance> { - can: bxcan::Can>, + pub can: RefCell>>, +} + +#[derive(Debug)] +pub enum BusError { + Stuff, + Form, + Acknowledge, + BitRecessive, + BitDominant, + Crc, + Software, + BusOff, + BusPassive, + BusWarning, } impl<'d, T: Instance> Can<'d, T> { + /// Creates a new Bxcan instance, keeping the peripheral in sleep mode. + /// You must call [Can::enable_non_blocking] to use the peripheral. pub fn new( peri: impl Peripheral

+ 'd, rx: impl Peripheral

> + 'd, tx: impl Peripheral

> + 'd, + _irqs: impl interrupt::typelevel::Binding> + + interrupt::typelevel::Binding> + + interrupt::typelevel::Binding> + + interrupt::typelevel::Binding> + + 'd, ) -> Self { into_ref!(peri, rx, tx); - unsafe { - rx.set_as_af(rx.af_num(), AFType::Input); - tx.set_as_af(tx.af_num(), AFType::OutputPushPull); - } + rx.set_as_af(rx.af_num(), AFType::Input); + tx.set_as_af(tx.af_num(), AFType::OutputPushPull); T::enable(); T::reset(); - Self { - can: bxcan::Can::builder(BxcanInstance(peri)).enable(), + { + use crate::pac::can::vals::{Errie, Fmpie, Tmeie}; + + T::regs().ier().write(|w| { + // TODO: fix metapac + + w.set_errie(Errie::from_bits(1)); + w.set_fmpie(0, Fmpie::from_bits(1)); + w.set_fmpie(1, Fmpie::from_bits(1)); + w.set_tmeie(Tmeie::from_bits(1)); + }); + + T::regs().mcr().write(|w| { + // Enable timestamps on rx messages + + w.set_ttcm(true); + }); + } + + unsafe { + T::TXInterrupt::unpend(); + T::TXInterrupt::enable(); + + T::RX0Interrupt::unpend(); + T::RX0Interrupt::enable(); + + T::RX1Interrupt::unpend(); + T::RX1Interrupt::enable(); + + T::SCEInterrupt::unpend(); + T::SCEInterrupt::enable(); + } + + rx.set_as_af(rx.af_num(), AFType::Input); + tx.set_as_af(tx.af_num(), AFType::OutputPushPull); + + let can = bxcan::Can::builder(BxcanInstance(peri)).leave_disabled(); + let can_ref_cell = RefCell::new(can); + Self { can: can_ref_cell } + } + + pub fn set_bitrate(&mut self, bitrate: u32) { + let bit_timing = Self::calc_bxcan_timings(T::frequency(), bitrate).unwrap(); + self.can + .borrow_mut() + .modify_config() + .set_bit_timing(bit_timing) + .leave_disabled(); + } + + /// Queues the message to be sent but exerts backpressure + pub async fn write(&mut self, frame: &Frame) -> bxcan::TransmitStatus { + poll_fn(|cx| { + T::state().tx_waker.register(cx.waker()); + if let Ok(status) = self.can.borrow_mut().transmit(frame) { + return Poll::Ready(status); + } + + Poll::Pending + }) + .await + } + + pub async fn flush(&self, mb: bxcan::Mailbox) { + poll_fn(|cx| { + T::state().tx_waker.register(cx.waker()); + if T::regs().tsr().read().tme(mb.index()) { + return Poll::Ready(()); + } + + Poll::Pending + }) + .await; + } + + /// Returns a tuple of the time the message was received and the message frame + pub async fn read(&mut self) -> Result<(u16, bxcan::Frame), BusError> { + poll_fn(|cx| { + T::state().err_waker.register(cx.waker()); + if let Poll::Ready((time, frame)) = T::state().rx_queue.recv().poll_unpin(cx) { + return Poll::Ready(Ok((time, frame))); + } else if let Some(err) = self.curr_error() { + return Poll::Ready(Err(err)); + } + + Poll::Pending + }) + .await + } + + fn curr_error(&self) -> Option { + let err = { T::regs().esr().read() }; + if err.boff() { + return Some(BusError::BusOff); + } else if err.epvf() { + return Some(BusError::BusPassive); + } else if err.ewgf() { + return Some(BusError::BusWarning); + } else if let Some(err) = err.lec().into_bus_err() { + return Some(err); + } + None + } + + unsafe fn receive_fifo(fifo: RxFifo) { + let state = T::state(); + let regs = T::regs(); + let fifo_idx = match fifo { + RxFifo::Fifo0 => 0usize, + RxFifo::Fifo1 => 1usize, + }; + let rfr = regs.rfr(fifo_idx); + let fifo = regs.rx(fifo_idx); + + loop { + // If there are no pending messages, there is nothing to do + if rfr.read().fmp() == 0 { + return; + } + + let rir = fifo.rir().read(); + let id = if rir.ide() == RirIde::STANDARD { + Id::from(StandardId::new_unchecked(rir.stid())) + } else { + let stid = (rir.stid() & 0x7FF) as u32; + let exid = rir.exid() & 0x3FFFF; + let id = (stid << 18) | (exid as u32); + Id::from(ExtendedId::new_unchecked(id)) + }; + let data_len = fifo.rdtr().read().dlc() as usize; + let mut data: [u8; 8] = [0; 8]; + data[0..4].copy_from_slice(&fifo.rdlr().read().0.to_ne_bytes()); + data[4..8].copy_from_slice(&fifo.rdhr().read().0.to_ne_bytes()); + + let time = fifo.rdtr().read().time(); + let frame = Frame::new_data(id, Data::new(&data[0..data_len]).unwrap()); + + rfr.modify(|v| v.set_rfom(true)); + + /* + NOTE: consensus was reached that if rx_queue is full, packets should be dropped + */ + let _ = state.rx_queue.try_send((time, frame)); } } + + pub const fn calc_bxcan_timings(periph_clock: Hertz, can_bitrate: u32) -> Option { + const BS1_MAX: u8 = 16; + const BS2_MAX: u8 = 8; + const MAX_SAMPLE_POINT_PERMILL: u16 = 900; + + let periph_clock = periph_clock.0; + + if can_bitrate < 1000 { + return None; + } + + // Ref. "Automatic Baudrate Detection in CANopen Networks", U. Koppe, MicroControl GmbH & Co. KG + // CAN in Automation, 2003 + // + // According to the source, optimal quanta per bit are: + // Bitrate Optimal Maximum + // 1000 kbps 8 10 + // 500 kbps 16 17 + // 250 kbps 16 17 + // 125 kbps 16 17 + let max_quanta_per_bit: u8 = if can_bitrate >= 1_000_000 { 10 } else { 17 }; + + // Computing (prescaler * BS): + // BITRATE = 1 / (PRESCALER * (1 / PCLK) * (1 + BS1 + BS2)) -- See the Reference Manual + // BITRATE = PCLK / (PRESCALER * (1 + BS1 + BS2)) -- Simplified + // let: + // BS = 1 + BS1 + BS2 -- Number of time quanta per bit + // PRESCALER_BS = PRESCALER * BS + // ==> + // PRESCALER_BS = PCLK / BITRATE + let prescaler_bs = periph_clock / can_bitrate; + + // Searching for such prescaler value so that the number of quanta per bit is highest. + let mut bs1_bs2_sum = max_quanta_per_bit - 1; + while (prescaler_bs % (1 + bs1_bs2_sum) as u32) != 0 { + if bs1_bs2_sum <= 2 { + return None; // No solution + } + bs1_bs2_sum -= 1; + } + + let prescaler = prescaler_bs / (1 + bs1_bs2_sum) as u32; + if (prescaler < 1) || (prescaler > 1024) { + return None; // No solution + } + + // Now we have a constraint: (BS1 + BS2) == bs1_bs2_sum. + // We need to find such values so that the sample point is as close as possible to the optimal value, + // which is 87.5%, which is 7/8. + // + // Solve[(1 + bs1)/(1 + bs1 + bs2) == 7/8, bs2] (* Where 7/8 is 0.875, the recommended sample point location *) + // {{bs2 -> (1 + bs1)/7}} + // + // Hence: + // bs2 = (1 + bs1) / 7 + // bs1 = (7 * bs1_bs2_sum - 1) / 8 + // + // Sample point location can be computed as follows: + // Sample point location = (1 + bs1) / (1 + bs1 + bs2) + // + // Since the optimal solution is so close to the maximum, we prepare two solutions, and then pick the best one: + // - With rounding to nearest + // - With rounding to zero + let mut bs1 = ((7 * bs1_bs2_sum - 1) + 4) / 8; // Trying rounding to nearest first + let mut bs2 = bs1_bs2_sum - bs1; + core::assert!(bs1_bs2_sum > bs1); + + let sample_point_permill = 1000 * ((1 + bs1) / (1 + bs1 + bs2)) as u16; + if sample_point_permill > MAX_SAMPLE_POINT_PERMILL { + // Nope, too far; now rounding to zero + bs1 = (7 * bs1_bs2_sum - 1) / 8; + bs2 = bs1_bs2_sum - bs1; + } + + // Check is BS1 and BS2 are in range + if (bs1 < 1) || (bs1 > BS1_MAX) || (bs2 < 1) || (bs2 > BS2_MAX) { + return None; + } + + // Check if final bitrate matches the requested + if can_bitrate != (periph_clock / (prescaler * (1 + bs1 + bs2) as u32)) { + return None; + } + + // One is recommended by DS-015, CANOpen, and DeviceNet + let sjw = 1; + + // Pack into BTR register values + Some((sjw - 1) << 24 | (bs1 as u32 - 1) << 16 | (bs2 as u32 - 1) << 20 | (prescaler as u32 - 1)) + } + + pub fn split<'c>(&'c self) -> (CanTx<'c, 'd, T>, CanRx<'c, 'd, T>) { + (CanTx { can: &self.can }, CanRx { can: &self.can }) + } + + pub fn as_mut(&self) -> RefMut<'_, bxcan::Can>> { + self.can.borrow_mut() + } +} + +pub struct CanTx<'c, 'd, T: Instance> { + can: &'c RefCell>>, +} + +impl<'c, 'd, T: Instance> CanTx<'c, 'd, T> { + pub async fn write(&mut self, frame: &Frame) -> bxcan::TransmitStatus { + poll_fn(|cx| { + T::state().tx_waker.register(cx.waker()); + if let Ok(status) = self.can.borrow_mut().transmit(frame) { + return Poll::Ready(status); + } + + Poll::Pending + }) + .await + } + + pub async fn flush(&self, mb: bxcan::Mailbox) { + poll_fn(|cx| { + T::state().tx_waker.register(cx.waker()); + if T::regs().tsr().read().tme(mb.index()) { + return Poll::Ready(()); + } + + Poll::Pending + }) + .await; + } +} + +#[allow(dead_code)] +pub struct CanRx<'c, 'd, T: Instance> { + can: &'c RefCell>>, +} + +impl<'c, 'd, T: Instance> CanRx<'c, 'd, T> { + pub async fn read(&mut self) -> Result<(u16, bxcan::Frame), BusError> { + poll_fn(|cx| { + T::state().err_waker.register(cx.waker()); + if let Poll::Ready((time, frame)) = T::state().rx_queue.recv().poll_unpin(cx) { + return Poll::Ready(Ok((time, frame))); + } else if let Some(err) = self.curr_error() { + return Poll::Ready(Err(err)); + } + + Poll::Pending + }) + .await + } + + fn curr_error(&self) -> Option { + let err = { T::regs().esr().read() }; + if err.boff() { + return Some(BusError::BusOff); + } else if err.epvf() { + return Some(BusError::BusPassive); + } else if err.ewgf() { + return Some(BusError::BusWarning); + } else if let Some(err) = err.lec().into_bus_err() { + return Some(err); + } + None + } +} + +enum RxFifo { + Fifo0, + Fifo1, } impl<'d, T: Instance> Drop for Can<'d, T> { fn drop(&mut self) { // Cannot call `free()` because it moves the instance. // Manually reset the peripheral. - unsafe { T::regs().mcr().write(|w| w.set_reset(true)) } + T::regs().mcr().write(|w| w.set_reset(true)); T::disable(); } } impl<'d, T: Instance> Deref for Can<'d, T> { - type Target = bxcan::Can>; + type Target = RefCell>>; fn deref(&self) -> &Self::Target { &self.can @@ -57,14 +451,52 @@ impl<'d, T: Instance> DerefMut for Can<'d, T> { } pub(crate) mod sealed { + use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; + use embassy_sync::channel::Channel; + use embassy_sync::waitqueue::AtomicWaker; + + pub struct State { + pub tx_waker: AtomicWaker, + pub err_waker: AtomicWaker, + pub rx_queue: Channel, + } + + impl State { + pub const fn new() -> Self { + Self { + tx_waker: AtomicWaker::new(), + err_waker: AtomicWaker::new(), + rx_queue: Channel::new(), + } + } + } + pub trait Instance { const REGISTERS: *mut bxcan::RegisterBlock; fn regs() -> &'static crate::pac::can::Can; + fn state() -> &'static State; } } -pub trait Instance: sealed::Instance + RccPeripheral {} +pub trait TXInstance { + type TXInterrupt: crate::interrupt::typelevel::Interrupt; +} + +pub trait RX0Instance { + type RX0Interrupt: crate::interrupt::typelevel::Interrupt; +} + +pub trait RX1Instance { + type RX1Interrupt: crate::interrupt::typelevel::Interrupt; +} + +pub trait SCEInstance { + type SCEInterrupt: crate::interrupt::typelevel::Interrupt; +} + +pub trait InterruptableInstance: TXInstance + RX0Instance + RX1Instance + SCEInstance {} +pub trait Instance: sealed::Instance + RccPeripheral + InterruptableInstance + 'static {} pub struct BxcanInstance<'a, T>(PeripheralRef<'a, T>); @@ -75,15 +507,44 @@ unsafe impl<'d, T: Instance> bxcan::Instance for BxcanInstance<'d, T> { foreach_peripheral!( (can, $inst:ident) => { impl sealed::Instance for peripherals::$inst { - const REGISTERS: *mut bxcan::RegisterBlock = crate::pac::$inst.0 as *mut _; + const REGISTERS: *mut bxcan::RegisterBlock = crate::pac::$inst.as_ptr() as *mut _; fn regs() -> &'static crate::pac::can::Can { &crate::pac::$inst } + + fn state() -> &'static sealed::State { + static STATE: sealed::State = sealed::State::new(); + &STATE + } } impl Instance for peripherals::$inst {} + foreach_interrupt!( + ($inst,can,CAN,TX,$irq:ident) => { + impl TXInstance for peripherals::$inst { + type TXInterrupt = crate::interrupt::typelevel::$irq; + } + }; + ($inst,can,CAN,RX0,$irq:ident) => { + impl RX0Instance for peripherals::$inst { + type RX0Interrupt = crate::interrupt::typelevel::$irq; + } + }; + ($inst,can,CAN,RX1,$irq:ident) => { + impl RX1Instance for peripherals::$inst { + type RX1Interrupt = crate::interrupt::typelevel::$irq; + } + }; + ($inst,can,CAN,SCE,$irq:ident) => { + impl SCEInstance for peripherals::$inst { + type SCEInterrupt = crate::interrupt::typelevel::$irq; + } + }; + ); + + impl InterruptableInstance for peripherals::$inst {} }; ); @@ -124,3 +585,36 @@ foreach_peripheral!( pin_trait!(RxPin, Instance); pin_trait!(TxPin, Instance); + +trait Index { + fn index(&self) -> usize; +} + +impl Index for bxcan::Mailbox { + fn index(&self) -> usize { + match self { + bxcan::Mailbox::Mailbox0 => 0, + bxcan::Mailbox::Mailbox1 => 1, + bxcan::Mailbox::Mailbox2 => 2, + } + } +} + +trait IntoBusError { + fn into_bus_err(self) -> Option; +} + +impl IntoBusError for Lec { + fn into_bus_err(self) -> Option { + match self { + Lec::STUFF => Some(BusError::Stuff), + Lec::FORM => Some(BusError::Form), + Lec::ACK => Some(BusError::Acknowledge), + Lec::BITRECESSIVE => Some(BusError::BitRecessive), + Lec::BITDOMINANT => Some(BusError::BitDominant), + Lec::CRC => Some(BusError::Crc), + Lec::CUSTOM => Some(BusError::Software), + _ => None, + } + } +} diff --git a/embassy-stm32/src/crc/v1.rs b/embassy-stm32/src/crc/v1.rs index 393089eed..3946a2d47 100644 --- a/embassy-stm32/src/crc/v1.rs +++ b/embassy-stm32/src/crc/v1.rs @@ -27,26 +27,24 @@ impl<'d> Crc<'d> { /// Resets the CRC unit to default value (0xFFFF_FFFF) pub fn reset(&mut self) { - unsafe { PAC_CRC.cr().write(|w| w.set_reset(true)) }; + PAC_CRC.cr().write(|w| w.set_reset(true)); } /// Feeds a word to the peripheral and returns the current CRC value pub fn feed_word(&mut self, word: u32) -> u32 { // write a single byte to the device, and return the result - unsafe { - PAC_CRC.dr().write_value(word); - } + PAC_CRC.dr().write_value(word); self.read() } /// Feed a slice of words to the peripheral and return the result. pub fn feed_words(&mut self, words: &[u32]) -> u32 { for word in words { - unsafe { PAC_CRC.dr().write_value(*word) } + PAC_CRC.dr().write_value(*word); } self.read() } pub fn read(&self) -> u32 { - unsafe { PAC_CRC.dr().read() } + PAC_CRC.dr().read() } } diff --git a/embassy-stm32/src/crc/v2v3.rs b/embassy-stm32/src/crc/v2v3.rs index 8acb3a770..f337055a7 100644 --- a/embassy-stm32/src/crc/v2v3.rs +++ b/embassy-stm32/src/crc/v2v3.rs @@ -85,95 +85,79 @@ impl<'d> Crc<'d> { } pub fn reset(&mut self) { - unsafe { - PAC_CRC.cr().modify(|w| w.set_reset(true)); - } + PAC_CRC.cr().modify(|w| w.set_reset(true)); } /// Reconfigures the CRC peripheral. Doesn't reset. fn reconfigure(&mut self) { - unsafe { - // Init CRC value - PAC_CRC.init().write_value(self._config.crc_init_value); - #[cfg(crc_v3)] - PAC_CRC.pol().write_value(self._config.crc_poly); + // Init CRC value + PAC_CRC.init().write_value(self._config.crc_init_value); + #[cfg(crc_v3)] + PAC_CRC.pol().write_value(self._config.crc_poly); - // configure CR components - // (reverse I/O, polysize, poly) - PAC_CRC.cr().write(|w| { - // configure reverse output - w.set_rev_out(match self._config.reverse_out { - true => vals::RevOut::REVERSED, - false => vals::RevOut::NORMAL, - }); - // configure reverse input - w.set_rev_in(match self._config.reverse_in { - InputReverseConfig::None => vals::RevIn::NORMAL, - InputReverseConfig::Byte => vals::RevIn::BYTE, - InputReverseConfig::Halfword => vals::RevIn::HALFWORD, - InputReverseConfig::Word => vals::RevIn::WORD, - }); - // configure the polynomial. - #[cfg(crc_v3)] - w.set_polysize(match self._config.poly_size { - PolySize::Width7 => vals::Polysize::POLYSIZE7, - PolySize::Width8 => vals::Polysize::POLYSIZE8, - PolySize::Width16 => vals::Polysize::POLYSIZE16, - PolySize::Width32 => vals::Polysize::POLYSIZE32, - }); - }) - } + // configure CR components + // (reverse I/O, polysize, poly) + PAC_CRC.cr().write(|w| { + // configure reverse output + w.set_rev_out(match self._config.reverse_out { + true => vals::RevOut::REVERSED, + false => vals::RevOut::NORMAL, + }); + // configure reverse input + w.set_rev_in(match self._config.reverse_in { + InputReverseConfig::None => vals::RevIn::NORMAL, + InputReverseConfig::Byte => vals::RevIn::BYTE, + InputReverseConfig::Halfword => vals::RevIn::HALFWORD, + InputReverseConfig::Word => vals::RevIn::WORD, + }); + // configure the polynomial. + #[cfg(crc_v3)] + w.set_polysize(match self._config.poly_size { + PolySize::Width7 => vals::Polysize::POLYSIZE7, + PolySize::Width8 => vals::Polysize::POLYSIZE8, + PolySize::Width16 => vals::Polysize::POLYSIZE16, + PolySize::Width32 => vals::Polysize::POLYSIZE32, + }); + }); self.reset(); } /// Feeds a byte into the CRC peripheral. Returns the computed checksum. pub fn feed_byte(&mut self, byte: u8) -> u32 { - unsafe { - PAC_CRC.dr8().write_value(byte); - PAC_CRC.dr().read() - } + PAC_CRC.dr8().write_value(byte); + PAC_CRC.dr().read() } /// Feeds an slice of bytes into the CRC peripheral. Returns the computed checksum. pub fn feed_bytes(&mut self, bytes: &[u8]) -> u32 { for byte in bytes { - unsafe { - PAC_CRC.dr8().write_value(*byte); - } + PAC_CRC.dr8().write_value(*byte); } - unsafe { PAC_CRC.dr().read() } + PAC_CRC.dr().read() } /// Feeds a halfword into the CRC peripheral. Returns the computed checksum. pub fn feed_halfword(&mut self, halfword: u16) -> u32 { - unsafe { - PAC_CRC.dr16().write_value(halfword); - PAC_CRC.dr().read() - } + PAC_CRC.dr16().write_value(halfword); + PAC_CRC.dr().read() } /// Feeds an slice of halfwords into the CRC peripheral. Returns the computed checksum. pub fn feed_halfwords(&mut self, halfwords: &[u16]) -> u32 { for halfword in halfwords { - unsafe { - PAC_CRC.dr16().write_value(*halfword); - } + PAC_CRC.dr16().write_value(*halfword); } - unsafe { PAC_CRC.dr().read() } + PAC_CRC.dr().read() } /// Feeds a words into the CRC peripheral. Returns the computed checksum. pub fn feed_word(&mut self, word: u32) -> u32 { - unsafe { - PAC_CRC.dr().write_value(word as u32); - PAC_CRC.dr().read() - } + PAC_CRC.dr().write_value(word as u32); + PAC_CRC.dr().read() } /// Feeds an slice of words into the CRC peripheral. Returns the computed checksum. pub fn feed_words(&mut self, words: &[u32]) -> u32 { for word in words { - unsafe { - PAC_CRC.dr().write_value(*word as u32); - } + PAC_CRC.dr().write_value(*word as u32); } - unsafe { PAC_CRC.dr().read() } + PAC_CRC.dr().read() } } diff --git a/embassy-stm32/src/dac.rs b/embassy-stm32/src/dac.rs deleted file mode 100644 index 60e856c78..000000000 --- a/embassy-stm32/src/dac.rs +++ /dev/null @@ -1,278 +0,0 @@ -#![macro_use] - -use embassy_hal_common::{into_ref, PeripheralRef}; - -use crate::pac::dac; -use crate::rcc::RccPeripheral; -use crate::{peripherals, Peripheral}; - -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Error { - UnconfiguredChannel, - InvalidValue, -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Channel { - Ch1, - Ch2, -} - -impl Channel { - fn index(&self) -> usize { - match self { - Channel::Ch1 => 0, - Channel::Ch2 => 1, - } - } -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Ch1Trigger { - Tim6, - Tim3, - Tim7, - Tim15, - Tim2, - Exti9, - Software, -} - -impl Ch1Trigger { - fn tsel(&self) -> dac::vals::Tsel1 { - match self { - Ch1Trigger::Tim6 => dac::vals::Tsel1::TIM6_TRGO, - Ch1Trigger::Tim3 => dac::vals::Tsel1::TIM3_TRGO, - Ch1Trigger::Tim7 => dac::vals::Tsel1::TIM7_TRGO, - Ch1Trigger::Tim15 => dac::vals::Tsel1::TIM15_TRGO, - Ch1Trigger::Tim2 => dac::vals::Tsel1::TIM2_TRGO, - Ch1Trigger::Exti9 => dac::vals::Tsel1::EXTI9, - Ch1Trigger::Software => dac::vals::Tsel1::SOFTWARE, - } - } -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Ch2Trigger { - Tim6, - Tim8, - Tim7, - Tim5, - Tim2, - Tim4, - Exti9, - Software, -} - -impl Ch2Trigger { - fn tsel(&self) -> dac::vals::Tsel2 { - match self { - Ch2Trigger::Tim6 => dac::vals::Tsel2::TIM6_TRGO, - Ch2Trigger::Tim8 => dac::vals::Tsel2::TIM8_TRGO, - Ch2Trigger::Tim7 => dac::vals::Tsel2::TIM7_TRGO, - Ch2Trigger::Tim5 => dac::vals::Tsel2::TIM5_TRGO, - Ch2Trigger::Tim2 => dac::vals::Tsel2::TIM2_TRGO, - Ch2Trigger::Tim4 => dac::vals::Tsel2::TIM4_TRGO, - Ch2Trigger::Exti9 => dac::vals::Tsel2::EXTI9, - Ch2Trigger::Software => dac::vals::Tsel2::SOFTWARE, - } - } -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Alignment { - Left, - Right, -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Value { - Bit8(u8), - Bit12(u16, Alignment), -} - -pub struct Dac<'d, T: Instance> { - channels: u8, - _peri: PeripheralRef<'d, T>, -} - -impl<'d, T: Instance> Dac<'d, T> { - pub fn new_1ch(peri: impl Peripheral

+ 'd, _ch1: impl Peripheral

> + 'd) -> Self { - into_ref!(peri); - Self::new_inner(peri, 1) - } - - pub fn new_2ch( - peri: impl Peripheral

+ 'd, - _ch1: impl Peripheral

> + 'd, - _ch2: impl Peripheral

> + 'd, - ) -> Self { - into_ref!(peri); - Self::new_inner(peri, 2) - } - - fn new_inner(peri: PeripheralRef<'d, T>, channels: u8) -> Self { - T::enable(); - T::reset(); - - unsafe { - T::regs().cr().modify(|reg| { - for ch in 0..channels { - reg.set_en(ch as usize, true); - } - }); - } - - Self { channels, _peri: peri } - } - - /// Check the channel is configured - fn check_channel_exists(&self, ch: Channel) -> Result<(), Error> { - if ch == Channel::Ch2 && self.channels < 2 { - Err(Error::UnconfiguredChannel) - } else { - Ok(()) - } - } - - fn set_channel_enable(&mut self, ch: Channel, on: bool) -> Result<(), Error> { - self.check_channel_exists(ch)?; - unsafe { - T::regs().cr().modify(|reg| { - reg.set_en(ch.index(), on); - }) - } - Ok(()) - } - - pub fn enable_channel(&mut self, ch: Channel) -> Result<(), Error> { - self.set_channel_enable(ch, true) - } - - pub fn disable_channel(&mut self, ch: Channel) -> Result<(), Error> { - self.set_channel_enable(ch, false) - } - - pub fn select_trigger_ch1(&mut self, trigger: Ch1Trigger) -> Result<(), Error> { - self.check_channel_exists(Channel::Ch1)?; - unwrap!(self.disable_channel(Channel::Ch1)); - unsafe { - T::regs().cr().modify(|reg| { - reg.set_tsel1(trigger.tsel()); - }) - } - Ok(()) - } - - pub fn select_trigger_ch2(&mut self, trigger: Ch2Trigger) -> Result<(), Error> { - self.check_channel_exists(Channel::Ch2)?; - unwrap!(self.disable_channel(Channel::Ch2)); - unsafe { - T::regs().cr().modify(|reg| { - reg.set_tsel2(trigger.tsel()); - }) - } - Ok(()) - } - - pub fn trigger(&mut self, ch: Channel) -> Result<(), Error> { - self.check_channel_exists(ch)?; - unsafe { - T::regs().swtrigr().write(|reg| { - reg.set_swtrig(ch.index(), true); - }); - } - Ok(()) - } - - pub fn trigger_all(&mut self) { - unsafe { - T::regs().swtrigr().write(|reg| { - reg.set_swtrig(Channel::Ch1.index(), true); - reg.set_swtrig(Channel::Ch2.index(), true); - }) - } - } - - pub fn set(&mut self, ch: Channel, value: Value) -> Result<(), Error> { - self.check_channel_exists(ch)?; - match value { - Value::Bit8(v) => unsafe { - T::regs().dhr8r(ch.index()).write(|reg| reg.set_dhr(v)); - }, - Value::Bit12(v, Alignment::Left) => unsafe { - T::regs().dhr12l(ch.index()).write(|reg| reg.set_dhr(v)); - }, - Value::Bit12(v, Alignment::Right) => unsafe { - T::regs().dhr12r(ch.index()).write(|reg| reg.set_dhr(v)); - }, - } - Ok(()) - } -} - -pub(crate) mod sealed { - pub trait Instance { - fn regs() -> &'static crate::pac::dac::Dac; - } -} - -pub trait Instance: sealed::Instance + RccPeripheral + 'static {} - -pub trait DacPin: crate::gpio::Pin + 'static {} - -foreach_peripheral!( - (dac, $inst:ident) => { - // H7 uses single bit for both DAC1 and DAC2, this is a hack until a proper fix is implemented - #[cfg(rcc_h7)] - impl crate::rcc::sealed::RccPeripheral for peripherals::$inst { - fn frequency() -> crate::time::Hertz { - critical_section::with(|_| unsafe { - crate::rcc::get_freqs().apb1 - }) - } - - fn reset() { - critical_section::with(|_| unsafe { - crate::pac::RCC.apb1lrstr().modify(|w| w.set_dac12rst(true)); - crate::pac::RCC.apb1lrstr().modify(|w| w.set_dac12rst(false)); - }) - } - - fn enable() { - critical_section::with(|_| unsafe { - crate::pac::RCC.apb1lenr().modify(|w| w.set_dac12en(true)); - }) - } - - fn disable() { - critical_section::with(|_| unsafe { - crate::pac::RCC.apb1lenr().modify(|w| w.set_dac12en(false)); - }) - } - } - - #[cfg(rcc_h7)] - impl crate::rcc::RccPeripheral for peripherals::$inst {} - - impl crate::dac::sealed::Instance for peripherals::$inst { - fn regs() -> &'static crate::pac::dac::Dac { - &crate::pac::$inst - } - } - - impl crate::dac::Instance for peripherals::$inst {} - }; -); - -macro_rules! impl_dac_pin { - ($inst:ident, $pin:ident, $ch:expr) => { - impl crate::dac::DacPin for crate::peripherals::$pin {} - }; -} diff --git a/embassy-stm32/src/dac/mod.rs b/embassy-stm32/src/dac/mod.rs new file mode 100644 index 000000000..31a2d8863 --- /dev/null +++ b/embassy-stm32/src/dac/mod.rs @@ -0,0 +1,570 @@ +#![macro_use] + +//! Provide access to the STM32 digital-to-analog converter (DAC). +use core::marker::PhantomData; + +use embassy_hal_common::{into_ref, PeripheralRef}; + +use crate::pac::dac; +use crate::rcc::RccPeripheral; +use crate::{peripherals, Peripheral}; + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Curstom Errors +pub enum Error { + UnconfiguredChannel, + InvalidValue, +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// DAC Channels +pub enum Channel { + Ch1, + Ch2, +} + +impl Channel { + const fn index(&self) -> usize { + match self { + Channel::Ch1 => 0, + Channel::Ch2 => 1, + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Trigger sources for CH1 +pub enum Ch1Trigger { + Tim6, + Tim3, + Tim7, + Tim15, + Tim2, + Exti9, + Software, +} + +impl Ch1Trigger { + fn tsel(&self) -> dac::vals::Tsel1 { + match self { + Ch1Trigger::Tim6 => dac::vals::Tsel1::TIM6_TRGO, + Ch1Trigger::Tim3 => dac::vals::Tsel1::TIM3_TRGO, + Ch1Trigger::Tim7 => dac::vals::Tsel1::TIM7_TRGO, + Ch1Trigger::Tim15 => dac::vals::Tsel1::TIM15_TRGO, + Ch1Trigger::Tim2 => dac::vals::Tsel1::TIM2_TRGO, + Ch1Trigger::Exti9 => dac::vals::Tsel1::EXTI9, + Ch1Trigger::Software => dac::vals::Tsel1::SOFTWARE, + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Trigger sources for CH2 +pub enum Ch2Trigger { + Tim6, + Tim8, + Tim7, + Tim5, + Tim2, + Tim4, + Exti9, + Software, +} + +impl Ch2Trigger { + fn tsel(&self) -> dac::vals::Tsel2 { + match self { + Ch2Trigger::Tim6 => dac::vals::Tsel2::TIM6_TRGO, + Ch2Trigger::Tim8 => dac::vals::Tsel2::TIM8_TRGO, + Ch2Trigger::Tim7 => dac::vals::Tsel2::TIM7_TRGO, + Ch2Trigger::Tim5 => dac::vals::Tsel2::TIM5_TRGO, + Ch2Trigger::Tim2 => dac::vals::Tsel2::TIM2_TRGO, + Ch2Trigger::Tim4 => dac::vals::Tsel2::TIM4_TRGO, + Ch2Trigger::Exti9 => dac::vals::Tsel2::EXTI9, + Ch2Trigger::Software => dac::vals::Tsel2::SOFTWARE, + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Single 8 or 12 bit value that can be output by the DAC +pub enum Value { + // 8 bit value + Bit8(u8), + // 12 bit value stored in a u16, left-aligned + Bit12Left(u16), + // 12 bit value stored in a u16, right-aligned + Bit12Right(u16), +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Array variant of [`Value`] +pub enum ValueArray<'a> { + // 8 bit values + Bit8(&'a [u8]), + // 12 bit value stored in a u16, left-aligned + Bit12Left(&'a [u16]), + // 12 bit values stored in a u16, right-aligned + Bit12Right(&'a [u16]), +} +/// Provide common functions for DAC channels +pub trait DacChannel { + const CHANNEL: Channel; + + /// Enable trigger of the given channel + fn set_trigger_enable(&mut self, on: bool) -> Result<(), Error> { + T::regs().cr().modify(|reg| { + reg.set_ten(Self::CHANNEL.index(), on); + }); + Ok(()) + } + + /// Set mode register of the given channel + #[cfg(dac_v2)] + fn set_channel_mode(&mut self, val: u8) -> Result<(), Error> { + T::regs().mcr().modify(|reg| { + reg.set_mode(Self::CHANNEL.index(), val); + }); + Ok(()) + } + + /// Set enable register of the given channel + fn set_channel_enable(&mut self, on: bool) -> Result<(), Error> { + T::regs().cr().modify(|reg| { + reg.set_en(Self::CHANNEL.index(), on); + }); + Ok(()) + } + + /// Enable the DAC channel `ch` + fn enable_channel(&mut self) -> Result<(), Error> { + self.set_channel_enable(true) + } + + /// Disable the DAC channel `ch` + fn disable_channel(&mut self) -> Result<(), Error> { + self.set_channel_enable(false) + } + + /// Perform a software trigger on `ch` + fn trigger(&mut self) { + T::regs().swtrigr().write(|reg| { + reg.set_swtrig(Self::CHANNEL.index(), true); + }); + } + + /// Set a value to be output by the DAC on trigger. + /// + /// The `value` is written to the corresponding "data holding register". + fn set(&mut self, value: Value) -> Result<(), Error> { + match value { + Value::Bit8(v) => T::regs().dhr8r(Self::CHANNEL.index()).write(|reg| reg.set_dhr(v)), + Value::Bit12Left(v) => T::regs().dhr12l(Self::CHANNEL.index()).write(|reg| reg.set_dhr(v)), + Value::Bit12Right(v) => T::regs().dhr12r(Self::CHANNEL.index()).write(|reg| reg.set_dhr(v)), + } + Ok(()) + } +} + +/// Hold two DAC channels +/// +/// Note: This consumes the DAC `Instance` only once, allowing to get both channels simultaneously. +/// +/// # Example for obtaining both DAC channels +/// +/// ```ignore +/// // DMA channels and pins may need to be changed for your controller +/// let (dac_ch1, dac_ch2) = +/// embassy_stm32::dac::Dac::new(p.DAC1, p.DMA1_CH3, p.DMA1_CH4, p.PA4, p.PA5).split(); +/// ``` +pub struct Dac<'d, T: Instance, TxCh1, TxCh2> { + ch1: DacCh1<'d, T, TxCh1>, + ch2: DacCh2<'d, T, TxCh2>, +} + +/// DAC CH1 +/// +/// Note: This consumes the DAC `Instance`. Use [`Dac::new`] to get both channels simultaneously. +pub struct DacCh1<'d, T: Instance, Tx> { + /// To consume T + _peri: PeripheralRef<'d, T>, + #[allow(unused)] // For chips whose DMA is not (yet) supported + dma: PeripheralRef<'d, Tx>, +} + +/// DAC CH2 +/// +/// Note: This consumes the DAC `Instance`. Use [`Dac::new`] to get both channels simultaneously. +pub struct DacCh2<'d, T: Instance, Tx> { + /// Instead of PeripheralRef to consume T + phantom: PhantomData<&'d mut T>, + #[allow(unused)] // For chips whose DMA is not (yet) supported + dma: PeripheralRef<'d, Tx>, +} + +impl<'d, T: Instance, Tx> DacCh1<'d, T, Tx> { + /// Obtain DAC CH1 + pub fn new( + peri: impl Peripheral

+ 'd, + dma: impl Peripheral

+ 'd, + _pin: impl Peripheral

> + 'd, + ) -> Self { + into_ref!(peri, dma); + T::enable(); + T::reset(); + + let mut dac = Self { _peri: peri, dma }; + + // Configure each activated channel. All results can be `unwrap`ed since they + // will only error if the channel is not configured (i.e. ch1, ch2 are false) + #[cfg(dac_v2)] + dac.set_channel_mode(0).unwrap(); + dac.enable_channel().unwrap(); + dac.set_trigger_enable(true).unwrap(); + + dac + } + + /// Select a new trigger for this channel + /// + /// **Important**: This disables the channel! + pub fn select_trigger(&mut self, trigger: Ch1Trigger) -> Result<(), Error> { + unwrap!(self.disable_channel()); + T::regs().cr().modify(|reg| { + reg.set_tsel1(trigger.tsel()); + }); + Ok(()) + } + + /// Write `data` to the DAC CH1 via DMA. + /// + /// To prevent delays/glitches when outputting a periodic waveform, the `circular` flag can be set. + /// This will configure a circular DMA transfer that periodically outputs the `data`. + /// Note that for performance reasons in circular mode the transfer complete interrupt is disabled. + /// + /// **Important:** Channel 1 has to be configured for the DAC instance! + #[cfg(all(bdma, not(dma)))] // It currently only works with BDMA-only chips (DMA should theoretically work though) + pub async fn write(&mut self, data: ValueArray<'_>, circular: bool) -> Result<(), Error> + where + Tx: DmaCh1, + { + let channel = Channel::Ch1.index(); + debug!("Writing to channel {}", channel); + + // Enable DAC and DMA + T::regs().cr().modify(|w| { + w.set_en(channel, true); + w.set_dmaen(channel, true); + }); + + let tx_request = self.dma.request(); + let dma_channel = &mut self.dma; + + let tx_options = crate::dma::TransferOptions { + circular, + half_transfer_ir: false, + complete_transfer_ir: !circular, + ..Default::default() + }; + + // Initiate the correct type of DMA transfer depending on what data is passed + let tx_f = match data { + ValueArray::Bit8(buf) => unsafe { + crate::dma::Transfer::new_write( + dma_channel, + tx_request, + buf, + T::regs().dhr8r(channel).as_ptr() as *mut u8, + tx_options, + ) + }, + ValueArray::Bit12Left(buf) => unsafe { + crate::dma::Transfer::new_write( + dma_channel, + tx_request, + buf, + T::regs().dhr12l(channel).as_ptr() as *mut u16, + tx_options, + ) + }, + ValueArray::Bit12Right(buf) => unsafe { + crate::dma::Transfer::new_write( + dma_channel, + tx_request, + buf, + T::regs().dhr12r(channel).as_ptr() as *mut u16, + tx_options, + ) + }, + }; + + tx_f.await; + + // finish dma + // TODO: Do we need to check any status registers here? + T::regs().cr().modify(|w| { + // Disable the DAC peripheral + w.set_en(channel, false); + // Disable the DMA. TODO: Is this necessary? + w.set_dmaen(channel, false); + }); + + Ok(()) + } +} + +impl<'d, T: Instance, Tx> DacCh2<'d, T, Tx> { + /// Obtain DAC CH2 + pub fn new( + _peri: impl Peripheral

+ 'd, + dma: impl Peripheral

+ 'd, + _pin: impl Peripheral

> + 'd, + ) -> Self { + into_ref!(_peri, dma); + T::enable(); + T::reset(); + + let mut dac = Self { + phantom: PhantomData, + dma, + }; + + // Configure each activated channel. All results can be `unwrap`ed since they + // will only error if the channel is not configured (i.e. ch1, ch2 are false) + #[cfg(dac_v2)] + dac.set_channel_mode(0).unwrap(); + dac.enable_channel().unwrap(); + dac.set_trigger_enable(true).unwrap(); + + dac + } + + /// Select a new trigger for this channel + pub fn select_trigger(&mut self, trigger: Ch2Trigger) -> Result<(), Error> { + unwrap!(self.disable_channel()); + T::regs().cr().modify(|reg| { + reg.set_tsel2(trigger.tsel()); + }); + Ok(()) + } + + /// Write `data` to the DAC CH2 via DMA. + /// + /// To prevent delays/glitches when outputting a periodic waveform, the `circular` flag can be set. + /// This will configure a circular DMA transfer that periodically outputs the `data`. + /// Note that for performance reasons in circular mode the transfer complete interrupt is disabled. + /// + /// **Important:** Channel 2 has to be configured for the DAC instance! + #[cfg(all(bdma, not(dma)))] // It currently only works with BDMA-only chips (DMA should theoretically work though) + pub async fn write(&mut self, data: ValueArray<'_>, circular: bool) -> Result<(), Error> + where + Tx: DmaCh2, + { + let channel = Channel::Ch2.index(); + debug!("Writing to channel {}", channel); + + // Enable DAC and DMA + T::regs().cr().modify(|w| { + w.set_en(channel, true); + w.set_dmaen(channel, true); + }); + + let tx_request = self.dma.request(); + let dma_channel = &mut self.dma; + + let tx_options = crate::dma::TransferOptions { + circular, + half_transfer_ir: false, + complete_transfer_ir: !circular, + ..Default::default() + }; + + // Initiate the correct type of DMA transfer depending on what data is passed + let tx_f = match data { + ValueArray::Bit8(buf) => unsafe { + crate::dma::Transfer::new_write( + dma_channel, + tx_request, + buf, + T::regs().dhr8r(channel).as_ptr() as *mut u8, + tx_options, + ) + }, + ValueArray::Bit12Left(buf) => unsafe { + crate::dma::Transfer::new_write( + dma_channel, + tx_request, + buf, + T::regs().dhr12l(channel).as_ptr() as *mut u16, + tx_options, + ) + }, + ValueArray::Bit12Right(buf) => unsafe { + crate::dma::Transfer::new_write( + dma_channel, + tx_request, + buf, + T::regs().dhr12r(channel).as_ptr() as *mut u16, + tx_options, + ) + }, + }; + + tx_f.await; + + // finish dma + // TODO: Do we need to check any status registers here? + T::regs().cr().modify(|w| { + // Disable the DAC peripheral + w.set_en(channel, false); + // Disable the DMA. TODO: Is this necessary? + w.set_dmaen(channel, false); + }); + + Ok(()) + } +} + +impl<'d, T: Instance, TxCh1, TxCh2> Dac<'d, T, TxCh1, TxCh2> { + /// Create a new DAC instance with both channels. + /// + /// This is used to obtain two independent channels via `split()` for use e.g. with DMA. + pub fn new( + peri: impl Peripheral

+ 'd, + dma_ch1: impl Peripheral

+ 'd, + dma_ch2: impl Peripheral

+ 'd, + _pin_ch1: impl Peripheral

> + 'd, + _pin_ch2: impl Peripheral

> + 'd, + ) -> Self { + into_ref!(peri, dma_ch1, dma_ch2); + T::enable(); + T::reset(); + + let mut dac_ch1 = DacCh1 { + _peri: peri, + dma: dma_ch1, + }; + + let mut dac_ch2 = DacCh2 { + phantom: PhantomData, + dma: dma_ch2, + }; + + // Configure each activated channel. All results can be `unwrap`ed since they + // will only error if the channel is not configured (i.e. ch1, ch2 are false) + #[cfg(dac_v2)] + dac_ch1.set_channel_mode(0).unwrap(); + dac_ch1.enable_channel().unwrap(); + dac_ch1.set_trigger_enable(true).unwrap(); + + #[cfg(dac_v2)] + dac_ch2.set_channel_mode(0).unwrap(); + dac_ch2.enable_channel().unwrap(); + dac_ch2.set_trigger_enable(true).unwrap(); + + Self { + ch1: dac_ch1, + ch2: dac_ch2, + } + } + + /// Split the DAC into CH1 and CH2 for independent use. + pub fn split(self) -> (DacCh1<'d, T, TxCh1>, DacCh2<'d, T, TxCh2>) { + (self.ch1, self.ch2) + } + + /// Get mutable reference to CH1 + pub fn ch1_mut(&mut self) -> &mut DacCh1<'d, T, TxCh1> { + &mut self.ch1 + } + + /// Get mutable reference to CH2 + pub fn ch2_mut(&mut self) -> &mut DacCh2<'d, T, TxCh2> { + &mut self.ch2 + } + + /// Get reference to CH1 + pub fn ch1(&mut self) -> &DacCh1<'d, T, TxCh1> { + &self.ch1 + } + + /// Get reference to CH2 + pub fn ch2(&mut self) -> &DacCh2<'d, T, TxCh2> { + &self.ch2 + } +} + +impl<'d, T: Instance, Tx> DacChannel for DacCh1<'d, T, Tx> { + const CHANNEL: Channel = Channel::Ch1; +} + +impl<'d, T: Instance, Tx> DacChannel for DacCh2<'d, T, Tx> { + const CHANNEL: Channel = Channel::Ch2; +} + +pub(crate) mod sealed { + pub trait Instance { + fn regs() -> &'static crate::pac::dac::Dac; + } +} + +pub trait Instance: sealed::Instance + RccPeripheral + 'static {} +dma_trait!(DmaCh1, Instance); +dma_trait!(DmaCh2, Instance); + +/// Marks a pin that can be used with the DAC +pub trait DacPin: crate::gpio::Pin + 'static {} + +foreach_peripheral!( + (dac, $inst:ident) => { + // H7 uses single bit for both DAC1 and DAC2, this is a hack until a proper fix is implemented + #[cfg(rcc_h7)] + impl crate::rcc::sealed::RccPeripheral for peripherals::$inst { + fn frequency() -> crate::time::Hertz { + critical_section::with(|_| unsafe { crate::rcc::get_freqs().apb1 }) + } + + fn reset() { + critical_section::with(|_| { + crate::pac::RCC.apb1lrstr().modify(|w| w.set_dac12rst(true)); + crate::pac::RCC.apb1lrstr().modify(|w| w.set_dac12rst(false)); + }) + } + + fn enable() { + critical_section::with(|_| { + crate::pac::RCC.apb1lenr().modify(|w| w.set_dac12en(true)); + }) + } + + fn disable() { + critical_section::with(|_| { + crate::pac::RCC.apb1lenr().modify(|w| w.set_dac12en(false)) + }) + } + } + + #[cfg(rcc_h7)] + impl crate::rcc::RccPeripheral for peripherals::$inst {} + + impl crate::dac::sealed::Instance for peripherals::$inst { + fn regs() -> &'static crate::pac::dac::Dac { + &crate::pac::$inst + } + } + + impl crate::dac::Instance for peripherals::$inst {} + }; +); + +macro_rules! impl_dac_pin { + ($inst:ident, $pin:ident, $ch:expr) => { + impl crate::dac::DacPin for crate::peripherals::$pin {} + }; +} diff --git a/embassy-stm32/src/dcmi.rs b/embassy-stm32/src/dcmi.rs index fb9dc9d08..78b026cb6 100644 --- a/embassy-stm32/src/dcmi.rs +++ b/embassy-stm32/src/dcmi.rs @@ -1,13 +1,39 @@ +use core::future::poll_fn; +use core::marker::PhantomData; use core::task::Poll; use embassy_hal_common::{into_ref, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; -use futures::future::poll_fn; +use crate::dma::Transfer; use crate::gpio::sealed::AFType; use crate::gpio::Speed; -use crate::interrupt::{Interrupt, InterruptExt}; -use crate::Peripheral; +use crate::interrupt::typelevel::Interrupt; +use crate::{interrupt, Peripheral}; + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let ris = crate::pac::DCMI.ris().read(); + if ris.err_ris() { + trace!("DCMI IRQ: Error."); + crate::pac::DCMI.ier().modify(|ier| ier.set_err_ie(false)); + } + if ris.ovr_ris() { + trace!("DCMI IRQ: Overrun."); + crate::pac::DCMI.ier().modify(|ier| ier.set_ovr_ie(false)); + } + if ris.frame_ris() { + trace!("DCMI IRQ: Frame captured."); + crate::pac::DCMI.ier().modify(|ier| ier.set_frame_ie(false)); + } + STATE.waker.wake(); + } +} /// The level on the VSync pin when the data is not valid on the parallel interface. #[derive(Clone, Copy, PartialEq)] @@ -70,8 +96,7 @@ impl Default for Config { macro_rules! config_pins { ($($pin:ident),*) => { into_ref!($($pin),*); - // NOTE(unsafe) Exclusive access to the registers - critical_section::with(|_| unsafe { + critical_section::with(|_| { $( $pin.set_as_af($pin.af_num(), AFType::Input); $pin.set_speed(Speed::VeryHigh); @@ -93,7 +118,7 @@ where pub fn new_8bit( peri: impl Peripheral

+ 'd, dma: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, d0: impl Peripheral

> + 'd, d1: impl Peripheral

> + 'd, d2: impl Peripheral

> + 'd, @@ -107,17 +132,17 @@ where pixclk: impl Peripheral

> + 'd, config: Config, ) -> Self { - into_ref!(peri, dma, irq); + into_ref!(peri, dma); config_pins!(d0, d1, d2, d3, d4, d5, d6, d7); config_pins!(v_sync, h_sync, pixclk); - Self::new_inner(peri, dma, irq, config, false, 0b00) + Self::new_inner(peri, dma, config, false, 0b00) } pub fn new_10bit( peri: impl Peripheral

+ 'd, dma: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, d0: impl Peripheral

> + 'd, d1: impl Peripheral

> + 'd, d2: impl Peripheral

> + 'd, @@ -133,17 +158,17 @@ where pixclk: impl Peripheral

> + 'd, config: Config, ) -> Self { - into_ref!(peri, dma, irq); + into_ref!(peri, dma); config_pins!(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9); config_pins!(v_sync, h_sync, pixclk); - Self::new_inner(peri, dma, irq, config, false, 0b01) + Self::new_inner(peri, dma, config, false, 0b01) } pub fn new_12bit( peri: impl Peripheral

+ 'd, dma: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, d0: impl Peripheral

> + 'd, d1: impl Peripheral

> + 'd, d2: impl Peripheral

> + 'd, @@ -161,17 +186,17 @@ where pixclk: impl Peripheral

> + 'd, config: Config, ) -> Self { - into_ref!(peri, dma, irq); + into_ref!(peri, dma); config_pins!(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11); config_pins!(v_sync, h_sync, pixclk); - Self::new_inner(peri, dma, irq, config, false, 0b10) + Self::new_inner(peri, dma, config, false, 0b10) } pub fn new_14bit( peri: impl Peripheral

+ 'd, dma: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, d0: impl Peripheral

> + 'd, d1: impl Peripheral

> + 'd, d2: impl Peripheral

> + 'd, @@ -191,17 +216,17 @@ where pixclk: impl Peripheral

> + 'd, config: Config, ) -> Self { - into_ref!(peri, dma, irq); + into_ref!(peri, dma); config_pins!(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13); config_pins!(v_sync, h_sync, pixclk); - Self::new_inner(peri, dma, irq, config, false, 0b11) + Self::new_inner(peri, dma, config, false, 0b11) } pub fn new_es_8bit( peri: impl Peripheral

+ 'd, dma: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, d0: impl Peripheral

> + 'd, d1: impl Peripheral

> + 'd, d2: impl Peripheral

> + 'd, @@ -213,17 +238,17 @@ where pixclk: impl Peripheral

> + 'd, config: Config, ) -> Self { - into_ref!(peri, dma, irq); + into_ref!(peri, dma); config_pins!(d0, d1, d2, d3, d4, d5, d6, d7); config_pins!(pixclk); - Self::new_inner(peri, dma, irq, config, true, 0b00) + Self::new_inner(peri, dma, config, true, 0b00) } pub fn new_es_10bit( peri: impl Peripheral

+ 'd, dma: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, d0: impl Peripheral

> + 'd, d1: impl Peripheral

> + 'd, d2: impl Peripheral

> + 'd, @@ -237,17 +262,17 @@ where pixclk: impl Peripheral

> + 'd, config: Config, ) -> Self { - into_ref!(peri, dma, irq); + into_ref!(peri, dma); config_pins!(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9); config_pins!(pixclk); - Self::new_inner(peri, dma, irq, config, true, 0b01) + Self::new_inner(peri, dma, config, true, 0b01) } pub fn new_es_12bit( peri: impl Peripheral

+ 'd, dma: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, d0: impl Peripheral

> + 'd, d1: impl Peripheral

> + 'd, d2: impl Peripheral

> + 'd, @@ -263,17 +288,17 @@ where pixclk: impl Peripheral

> + 'd, config: Config, ) -> Self { - into_ref!(peri, dma, irq); + into_ref!(peri, dma); config_pins!(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11); config_pins!(pixclk); - Self::new_inner(peri, dma, irq, config, true, 0b10) + Self::new_inner(peri, dma, config, true, 0b10) } pub fn new_es_14bit( peri: impl Peripheral

+ 'd, dma: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, d0: impl Peripheral

> + 'd, d1: impl Peripheral

> + 'd, d2: impl Peripheral

> + 'd, @@ -291,17 +316,16 @@ where pixclk: impl Peripheral

> + 'd, config: Config, ) -> Self { - into_ref!(peri, dma, irq); + into_ref!(peri, dma); config_pins!(d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13); config_pins!(pixclk); - Self::new_inner(peri, dma, irq, config, true, 0b11) + Self::new_inner(peri, dma, config, true, 0b11) } fn new_inner( peri: PeripheralRef<'d, T>, dma: PeripheralRef<'d, Dma>, - irq: PeripheralRef<'d, T::Interrupt>, config: Config, use_embedded_synchronization: bool, edm: u8, @@ -309,43 +333,23 @@ where T::reset(); T::enable(); - unsafe { - peri.regs().cr().modify(|r| { - r.set_cm(true); // disable continuous mode (snapshot mode) - r.set_ess(use_embedded_synchronization); - r.set_pckpol(config.pixclk_polarity == PixelClockPolarity::RisingEdge); - r.set_vspol(config.vsync_level == VSyncDataInvalidLevel::High); - r.set_hspol(config.hsync_level == HSyncDataInvalidLevel::High); - r.set_fcrc(0x00); // capture every frame - r.set_edm(edm); // extended data mode - }); - } + peri.regs().cr().modify(|r| { + r.set_cm(true); // disable continuous mode (snapshot mode) + r.set_ess(use_embedded_synchronization); + r.set_pckpol(config.pixclk_polarity == PixelClockPolarity::RisingEdge); + r.set_vspol(config.vsync_level == VSyncDataInvalidLevel::High); + r.set_hspol(config.hsync_level == HSyncDataInvalidLevel::High); + r.set_fcrc(0x00); // capture every frame + r.set_edm(edm); // extended data mode + }); - irq.set_handler(Self::on_interrupt); - irq.unpend(); - irq.enable(); + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; Self { inner: peri, dma } } - unsafe fn on_interrupt(_: *mut ()) { - let ris = crate::pac::DCMI.ris().read(); - if ris.err_ris() { - trace!("DCMI IRQ: Error."); - crate::pac::DCMI.ier().modify(|ier| ier.set_err_ie(false)); - } - if ris.ovr_ris() { - trace!("DCMI IRQ: Overrun."); - crate::pac::DCMI.ier().modify(|ier| ier.set_ovr_ie(false)); - } - if ris.frame_ris() { - trace!("DCMI IRQ: Frame captured."); - crate::pac::DCMI.ier().modify(|ier| ier.set_frame_ie(false)); - } - STATE.waker.wake(); - } - - unsafe fn toggle(enable: bool) { + fn toggle(enable: bool) { crate::pac::DCMI.cr().modify(|r| { r.set_enable(enable); r.set_capture(enable); @@ -353,23 +357,19 @@ where } fn enable_irqs() { - unsafe { - crate::pac::DCMI.ier().modify(|r| { - r.set_err_ie(true); - r.set_ovr_ie(true); - r.set_frame_ie(true); - }); - } + crate::pac::DCMI.ier().modify(|r| { + r.set_err_ie(true); + r.set_ovr_ie(true); + r.set_frame_ie(true); + }); } fn clear_interrupt_flags() { - unsafe { - crate::pac::DCMI.icr().write(|r| { - r.set_ovr_isc(true); - r.set_err_isc(true); - r.set_frame_isc(true); - }) - } + crate::pac::DCMI.icr().write(|r| { + r.set_ovr_isc(true); + r.set_err_isc(true); + r.set_frame_isc(true); + }) } /// This method starts the capture and finishes when both the dma transfer and DCMI finish the frame transfer. @@ -387,55 +387,47 @@ where } async fn capture_small(&mut self, buffer: &mut [u32]) -> Result<(), Error> { - let channel = &mut self.dma; - let request = channel.request(); - let r = self.inner.regs(); - let src = r.dr().ptr() as *mut u32; - let dma_read = crate::dma::read(channel, request, src, buffer); + let src = r.dr().as_ptr() as *mut u32; + let request = self.dma.request(); + let dma_read = unsafe { Transfer::new_read(&mut self.dma, request, src, buffer, Default::default()) }; Self::clear_interrupt_flags(); Self::enable_irqs(); - unsafe { Self::toggle(true) }; + Self::toggle(true); let result = poll_fn(|cx| { STATE.waker.register(cx.waker()); - let ris = unsafe { crate::pac::DCMI.ris().read() }; + let ris = crate::pac::DCMI.ris().read(); if ris.err_ris() { - unsafe { - crate::pac::DCMI.icr().write(|r| { - r.set_err_isc(true); - }) - }; + crate::pac::DCMI.icr().write(|r| r.set_err_isc(true)); Poll::Ready(Err(Error::PeripheralError)) } else if ris.ovr_ris() { - unsafe { - crate::pac::DCMI.icr().write(|r| { - r.set_ovr_isc(true); - }) - }; + crate::pac::DCMI.icr().write(|r| r.set_ovr_isc(true)); Poll::Ready(Err(Error::Overrun)) } else if ris.frame_ris() { - unsafe { - crate::pac::DCMI.icr().write(|r| { - r.set_frame_isc(true); - }) - }; + crate::pac::DCMI.icr().write(|r| r.set_frame_isc(true)); Poll::Ready(Ok(())) } else { Poll::Pending } }); - let (_, result) = futures::future::join(dma_read, result).await; + let (_, result) = embassy_futures::join::join(dma_read, result).await; - unsafe { Self::toggle(false) }; + Self::toggle(false); result } + #[cfg(not(dma))] + async fn capture_giant(&mut self, _buffer: &mut [u32]) -> Result<(), Error> { + panic!("capturing to buffers larger than 0xffff is only supported on DMA for now, not on BDMA or GPDMA."); + } + + #[cfg(dma)] async fn capture_giant(&mut self, buffer: &mut [u32]) -> Result<(), Error> { use crate::dma::TransferOptions; @@ -458,18 +450,26 @@ where let request = channel.request(); let r = self.inner.regs(); - let src = r.dr().ptr() as *mut u32; + let src = r.dr().as_ptr() as *mut u32; - unsafe { - channel.start_double_buffered_read(request, src, m0ar, m1ar, chunk_size, TransferOptions::default()); - } + let mut transfer = unsafe { + crate::dma::DoubleBuffered::new_read( + &mut self.dma, + request, + src, + m0ar, + m1ar, + chunk_size, + TransferOptions::default(), + ) + }; let mut last_chunk_set_for_transfer = false; let mut buffer0_last_accessible = false; let dma_result = poll_fn(|cx| { - channel.set_waker(cx.waker()); + transfer.set_waker(cx.waker()); - let buffer0_currently_accessible = unsafe { channel.is_buffer0_accessible() }; + let buffer0_currently_accessible = transfer.is_buffer0_accessible(); // check if the accessible buffer changed since last poll if buffer0_last_accessible == buffer0_currently_accessible { @@ -480,21 +480,21 @@ where if remaining_chunks != 0 { if remaining_chunks % 2 == 0 && buffer0_currently_accessible { m0ar = unsafe { m0ar.add(2 * chunk_size) }; - unsafe { channel.set_buffer0(m0ar) } + unsafe { transfer.set_buffer0(m0ar) } remaining_chunks -= 1; } else if !buffer0_currently_accessible { m1ar = unsafe { m1ar.add(2 * chunk_size) }; - unsafe { channel.set_buffer1(m1ar) }; + unsafe { transfer.set_buffer1(m1ar) }; remaining_chunks -= 1; } } else { if buffer0_currently_accessible { - unsafe { channel.set_buffer0(buffer.as_mut_ptr()) } + unsafe { transfer.set_buffer0(buffer.as_mut_ptr()) } } else { - unsafe { channel.set_buffer1(buffer.as_mut_ptr()) } + unsafe { transfer.set_buffer1(buffer.as_mut_ptr()) } } if last_chunk_set_for_transfer { - channel.request_stop(); + transfer.request_stop(); return Poll::Ready(()); } last_chunk_set_for_transfer = true; @@ -508,38 +508,26 @@ where let result = poll_fn(|cx| { STATE.waker.register(cx.waker()); - let ris = unsafe { crate::pac::DCMI.ris().read() }; + let ris = crate::pac::DCMI.ris().read(); if ris.err_ris() { - unsafe { - crate::pac::DCMI.icr().write(|r| { - r.set_err_isc(true); - }) - }; + crate::pac::DCMI.icr().write(|r| r.set_err_isc(true)); Poll::Ready(Err(Error::PeripheralError)) } else if ris.ovr_ris() { - unsafe { - crate::pac::DCMI.icr().write(|r| { - r.set_ovr_isc(true); - }) - }; + crate::pac::DCMI.icr().write(|r| r.set_ovr_isc(true)); Poll::Ready(Err(Error::Overrun)) } else if ris.frame_ris() { - unsafe { - crate::pac::DCMI.icr().write(|r| { - r.set_frame_isc(true); - }) - }; + crate::pac::DCMI.icr().write(|r| r.set_frame_isc(true)); Poll::Ready(Ok(())) } else { Poll::Pending } }); - unsafe { Self::toggle(true) }; + Self::toggle(true); - let (_, result) = futures::future::join(dma_result, result).await; + let (_, result) = embassy_futures::join::join(dma_result, result).await; - unsafe { Self::toggle(false) }; + Self::toggle(false); result } @@ -552,7 +540,7 @@ mod sealed { } pub trait Instance: sealed::Instance + 'static { - type Interrupt: Interrupt; + type Interrupt: interrupt::typelevel::Interrupt; } pin_trait!(D0Pin, Instance); @@ -584,7 +572,7 @@ macro_rules! impl_peripheral { } impl Instance for crate::peripherals::$inst { - type Interrupt = crate::interrupt::$irq; + type Interrupt = crate::interrupt::typelevel::$irq; } }; } diff --git a/embassy-stm32/src/dma/bdma.rs b/embassy-stm32/src/dma/bdma.rs index 674255ddc..5a87888b7 100644 --- a/embassy-stm32/src/dma/bdma.rs +++ b/embassy-stm32/src/dma/bdma.rs @@ -1,16 +1,44 @@ #![macro_use] +use core::future::Future; +use core::pin::Pin; use core::sync::atomic::{fence, Ordering}; -use core::task::Waker; +use core::task::{Context, Poll, Waker}; +use atomic_polyfill::AtomicUsize; +use embassy_hal_common::{into_ref, Peripheral, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; -use super::{TransferOptions, Word, WordSize}; +use super::ringbuffer::{DmaCtrl, DmaRingBuffer, OverrunError}; +use super::word::{Word, WordSize}; +use super::Dir; use crate::_generated::BDMA_CHANNEL_COUNT; -use crate::dma::Request; -use crate::interrupt::{Interrupt, InterruptExt}; +use crate::interrupt::typelevel::Interrupt; +use crate::interrupt::Priority; use crate::pac; -use crate::pac::bdma::vals; +use crate::pac::bdma::{regs, vals}; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct TransferOptions { + /// Enable circular DMA + pub circular: bool, + /// Enable half transfer interrupt + pub half_transfer_ir: bool, + /// Enable transfer complete interrupt + pub complete_transfer_ir: bool, +} + +impl Default for TransferOptions { + fn default() -> Self { + Self { + circular: false, + half_transfer_ir: false, + complete_transfer_ir: true, + } + } +} impl From for vals::Size { fn from(raw: WordSize) -> Self { @@ -22,15 +50,27 @@ impl From for vals::Size { } } +impl From

for vals::Dir { + fn from(raw: Dir) -> Self { + match raw { + Dir::MemoryToPeripheral => Self::FROMMEMORY, + Dir::PeripheralToMemory => Self::FROMPERIPHERAL, + } + } +} + struct State { ch_wakers: [AtomicWaker; BDMA_CHANNEL_COUNT], + complete_count: [AtomicUsize; BDMA_CHANNEL_COUNT], } impl State { const fn new() -> Self { + const ZERO: AtomicUsize = AtomicUsize::new(0); const AW: AtomicWaker = AtomicWaker::new(); Self { ch_wakers: [AW; BDMA_CHANNEL_COUNT], + complete_count: [ZERO; BDMA_CHANNEL_COUNT], } } } @@ -38,10 +78,11 @@ impl State { static STATE: State = State::new(); /// safety: must be called only once -pub(crate) unsafe fn init() { +pub(crate) unsafe fn init(irq_priority: Priority) { foreach_interrupt! { ($peri:ident, bdma, $block:ident, $signal_name:ident, $irq:ident) => { - crate::interrupt::$irq::steal().enable(); + crate::interrupt::typelevel::$irq::set_priority(irq_priority); + crate::interrupt::typelevel::$irq::enable(); }; } crate::_generated::init_bdma(); @@ -52,228 +93,431 @@ foreach_dma_channel! { // BDMA1 in H7 doesn't use DMAMUX, which breaks }; ($channel_peri:ident, $dma_peri:ident, bdma, $channel_num:expr, $index:expr, $dmamux:tt) => { - impl crate::dma::sealed::Channel for crate::peripherals::$channel_peri { - - unsafe fn start_write(&mut self, _request: Request, buf: *const[W], reg_addr: *mut W, options: TransferOptions) { - let (ptr, len) = super::slice_ptr_parts(buf); - low_level_api::start_transfer( - pac::$dma_peri, - $channel_num, - #[cfg(any(bdma_v2, dmamux))] - _request, - vals::Dir::FROMMEMORY, - reg_addr as *const u32, - ptr as *mut u32, - len, - true, - vals::Size::from(W::bits()), - options, - #[cfg(dmamux)] - ::DMAMUX_REGS, - #[cfg(dmamux)] - ::DMAMUX_CH_NUM, - ); + impl sealed::Channel for crate::peripherals::$channel_peri { + fn regs(&self) -> pac::bdma::Dma { + pac::$dma_peri } - - unsafe fn start_write_repeated(&mut self, _request: Request, repeated: W, count: usize, reg_addr: *mut W, options: TransferOptions) { - let buf = [repeated]; - low_level_api::start_transfer( - pac::$dma_peri, - $channel_num, - #[cfg(any(bdma_v2, dmamux))] - _request, - vals::Dir::FROMMEMORY, - reg_addr as *const u32, - buf.as_ptr() as *mut u32, - count, - false, - vals::Size::from(W::bits()), - options, - #[cfg(dmamux)] - ::DMAMUX_REGS, - #[cfg(dmamux)] - ::DMAMUX_CH_NUM, - ) + fn num(&self) -> usize { + $channel_num } - - unsafe fn start_read(&mut self, _request: Request, reg_addr: *const W, buf: *mut [W], options: TransferOptions) { - let (ptr, len) = super::slice_ptr_parts_mut(buf); - low_level_api::start_transfer( - pac::$dma_peri, - $channel_num, - #[cfg(any(bdma_v2, dmamux))] - _request, - vals::Dir::FROMPERIPHERAL, - reg_addr as *const u32, - ptr as *mut u32, - len, - true, - vals::Size::from(W::bits()), - options, - #[cfg(dmamux)] - ::DMAMUX_REGS, - #[cfg(dmamux)] - ::DMAMUX_CH_NUM, - ); + fn index(&self) -> usize { + $index } - - unsafe fn start_double_buffered_read( - &mut self, - _request: Request, - _reg_addr: *const W, - _buffer0: *mut W, - _buffer1: *mut W, - _buffer_len: usize, - _options: TransferOptions, - ) { - panic!("Unsafe double buffered mode is unavailable on BDMA"); - } - - unsafe fn set_buffer0(&mut self, _buffer: *mut W) { - panic!("Unsafe double buffered mode is unavailable on BDMA"); - } - - unsafe fn set_buffer1(&mut self, _buffer: *mut W) { - panic!("Unsafe double buffered mode is unavailable on BDMA"); - } - - unsafe fn is_buffer0_accessible(&mut self) -> bool { - panic!("Unsafe double buffered mode is unavailable on BDMA"); - } - - fn request_stop(&mut self){ - unsafe {low_level_api::request_stop(pac::$dma_peri, $channel_num);} - } - - fn is_running(&self) -> bool { - unsafe {low_level_api::is_running(pac::$dma_peri, $channel_num)} - } - fn remaining_transfers(&mut self) -> u16 { - unsafe {low_level_api::get_remaining_transfers(pac::$dma_peri, $channel_num)} - } - - fn set_waker(&mut self, waker: &Waker) { - unsafe { low_level_api::set_waker($index, waker) } - } - fn on_irq() { - unsafe { - low_level_api::on_irq_inner(pac::$dma_peri, $channel_num, $index); - } + unsafe { on_irq_inner(pac::$dma_peri, $channel_num, $index) } } } - impl crate::dma::Channel for crate::peripherals::$channel_peri {} + impl Channel for crate::peripherals::$channel_peri {} }; } -mod low_level_api { +/// Safety: Must be called with a matching set of parameters for a valid dma channel +pub(crate) unsafe fn on_irq_inner(dma: pac::bdma::Dma, channel_num: usize, index: usize) { + let isr = dma.isr().read(); + let cr = dma.ch(channel_num).cr(); + + if isr.teif(channel_num) { + panic!("DMA: error on BDMA@{:08x} channel {}", dma.as_ptr() as u32, channel_num); + } + + if isr.htif(channel_num) && cr.read().htie() { + // Acknowledge half transfer complete interrupt + dma.ifcr().write(|w| w.set_htif(channel_num, true)); + } else if isr.tcif(channel_num) && cr.read().tcie() { + // Acknowledge transfer complete interrupt + dma.ifcr().write(|w| w.set_tcif(channel_num, true)); + STATE.complete_count[index].fetch_add(1, Ordering::Release); + } else { + return; + } + + STATE.ch_wakers[index].wake(); +} + +#[cfg(any(bdma_v2, dmamux))] +pub type Request = u8; +#[cfg(not(any(bdma_v2, dmamux)))] +pub type Request = (); + +#[cfg(dmamux)] +pub trait Channel: sealed::Channel + Peripheral

+ 'static + super::dmamux::MuxChannel {} +#[cfg(not(dmamux))] +pub trait Channel: sealed::Channel + Peripheral

+ 'static {} + +pub(crate) mod sealed { use super::*; - pub unsafe fn start_transfer( - dma: pac::bdma::Dma, - channel_number: u8, - #[cfg(any(bdma_v2, dmamux))] request: Request, - dir: vals::Dir, + pub trait Channel { + fn regs(&self) -> pac::bdma::Dma; + fn num(&self) -> usize; + fn index(&self) -> usize; + fn on_irq(); + } +} + +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Transfer<'a, C: Channel> { + channel: PeripheralRef<'a, C>, +} + +impl<'a, C: Channel> Transfer<'a, C> { + pub unsafe fn new_read( + channel: impl Peripheral

+ 'a, + request: Request, + peri_addr: *mut W, + buf: &'a mut [W], + options: TransferOptions, + ) -> Self { + Self::new_read_raw(channel, request, peri_addr, buf, options) + } + + pub unsafe fn new_read_raw( + channel: impl Peripheral

+ 'a, + request: Request, + peri_addr: *mut W, + buf: *mut [W], + options: TransferOptions, + ) -> Self { + into_ref!(channel); + + let (ptr, len) = super::slice_ptr_parts_mut(buf); + assert!(len > 0 && len <= 0xFFFF); + + Self::new_inner( + channel, + request, + Dir::PeripheralToMemory, + peri_addr as *const u32, + ptr as *mut u32, + len, + true, + W::size(), + options, + ) + } + + pub unsafe fn new_write( + channel: impl Peripheral

+ 'a, + request: Request, + buf: &'a [W], + peri_addr: *mut W, + options: TransferOptions, + ) -> Self { + Self::new_write_raw(channel, request, buf, peri_addr, options) + } + + pub unsafe fn new_write_raw( + channel: impl Peripheral

+ 'a, + request: Request, + buf: *const [W], + peri_addr: *mut W, + options: TransferOptions, + ) -> Self { + into_ref!(channel); + + let (ptr, len) = super::slice_ptr_parts(buf); + assert!(len > 0 && len <= 0xFFFF); + + Self::new_inner( + channel, + request, + Dir::MemoryToPeripheral, + peri_addr as *const u32, + ptr as *mut u32, + len, + true, + W::size(), + options, + ) + } + + pub unsafe fn new_write_repeated( + channel: impl Peripheral

+ 'a, + request: Request, + repeated: &'a W, + count: usize, + peri_addr: *mut W, + options: TransferOptions, + ) -> Self { + into_ref!(channel); + + Self::new_inner( + channel, + request, + Dir::MemoryToPeripheral, + peri_addr as *const u32, + repeated as *const W as *mut u32, + count, + false, + W::size(), + options, + ) + } + + unsafe fn new_inner( + channel: PeripheralRef<'a, C>, + _request: Request, + dir: Dir, peri_addr: *const u32, mem_addr: *mut u32, mem_len: usize, incr_mem: bool, - data_size: vals::Size, + data_size: WordSize, options: TransferOptions, - #[cfg(dmamux)] dmamux_regs: pac::dmamux::Dmamux, - #[cfg(dmamux)] dmamux_ch_num: u8, - ) { - assert!(options.mburst == crate::dma::Burst::Single, "Burst mode not supported"); - assert!(options.pburst == crate::dma::Burst::Single, "Burst mode not supported"); - assert!( - options.flow_ctrl == crate::dma::FlowControl::Dma, - "Peripheral flow control not supported" - ); - - let ch = dma.ch(channel_number as _); - - reset_status(dma, channel_number); - - #[cfg(dmamux)] - super::super::dmamux::configure_dmamux(dmamux_regs, dmamux_ch_num, request); - - #[cfg(bdma_v2)] - critical_section::with(|_| dma.cselr().modify(|w| w.set_cs(channel_number as _, request))); + ) -> Self { + let ch = channel.regs().ch(channel.num()); // "Preceding reads and writes cannot be moved past subsequent writes." fence(Ordering::SeqCst); + #[cfg(bdma_v2)] + critical_section::with(|_| channel.regs().cselr().modify(|w| w.set_cs(channel.num(), _request))); + + let mut this = Self { channel }; + this.clear_irqs(); + STATE.complete_count[this.channel.index()].store(0, Ordering::Release); + + #[cfg(dmamux)] + super::dmamux::configure_dmamux(&mut *this.channel, _request); + ch.par().write_value(peri_addr as u32); ch.mar().write_value(mem_addr as u32); ch.ndtr().write(|w| w.set_ndt(mem_len as u16)); ch.cr().write(|w| { - w.set_psize(data_size); - w.set_msize(data_size); + w.set_psize(data_size.into()); + w.set_msize(data_size.into()); if incr_mem { w.set_minc(vals::Inc::ENABLED); } else { w.set_minc(vals::Inc::DISABLED); } - w.set_dir(dir); + w.set_dir(dir.into()); w.set_teie(true); - w.set_tcie(true); + w.set_tcie(options.complete_transfer_ir); + w.set_htie(options.half_transfer_ir); + if options.circular { + w.set_circ(vals::Circ::ENABLED); + debug!("Setting circular mode"); + } else { + w.set_circ(vals::Circ::DISABLED); + } + w.set_pl(vals::Pl::VERYHIGH); w.set_en(true); }); + + this + } + + fn clear_irqs(&mut self) { + self.channel.regs().ifcr().write(|w| { + w.set_tcif(self.channel.num(), true); + w.set_teif(self.channel.num(), true); + }); } - pub unsafe fn request_stop(dma: pac::bdma::Dma, channel_number: u8) { - reset_status(dma, channel_number); + pub fn request_stop(&mut self) { + let ch = self.channel.regs().ch(self.channel.num()); - let ch = dma.ch(channel_number as _); - - // Disable the channel and interrupts with the default value. - ch.cr().write(|_| ()); - - // "Subsequent reads and writes cannot be moved ahead of preceding reads." - fence(Ordering::SeqCst); + // Disable the channel. Keep the IEs enabled so the irqs still fire. + ch.cr().write(|w| { + w.set_teie(true); + w.set_tcie(true); + }); } - pub unsafe fn is_running(dma: pac::bdma::Dma, ch: u8) -> bool { - let ch = dma.ch(ch as _); - ch.cr().read().en() + pub fn is_running(&mut self) -> bool { + let ch = self.channel.regs().ch(self.channel.num()); + let en = ch.cr().read().en(); + let circular = ch.cr().read().circ() == vals::Circ::ENABLED; + let tcif = STATE.complete_count[self.channel.index()].load(Ordering::Acquire) != 0; + en && (circular || !tcif) } /// Gets the total remaining transfers for the channel /// Note: this will be zero for transfers that completed without cancellation. - pub unsafe fn get_remaining_transfers(dma: pac::bdma::Dma, ch: u8) -> u16 { - // get a handle on the channel itself - let ch = dma.ch(ch as _); - // read the remaining transfer count. If this is zero, the transfer completed fully. - ch.ndtr().read().ndt() as u16 + pub fn get_remaining_transfers(&self) -> u16 { + let ch = self.channel.regs().ch(self.channel.num()); + ch.ndtr().read().ndt() } - /// Sets the waker for the specified DMA channel - pub unsafe fn set_waker(state_number: usize, waker: &Waker) { - STATE.ch_wakers[state_number].register(waker); + pub fn blocking_wait(mut self) { + while self.is_running() {} + self.request_stop(); + + // "Subsequent reads and writes cannot be moved ahead of preceding reads." + fence(Ordering::SeqCst); + + core::mem::forget(self); } +} - pub unsafe fn reset_status(dma: pac::bdma::Dma, channel_number: u8) { - dma.ifcr().write(|w| { - w.set_tcif(channel_number as _, true); - w.set_teif(channel_number as _, true); - }); +impl<'a, C: Channel> Drop for Transfer<'a, C> { + fn drop(&mut self) { + self.request_stop(); + while self.is_running() {} + + // "Subsequent reads and writes cannot be moved ahead of preceding reads." + fence(Ordering::SeqCst); } +} - /// Safety: Must be called with a matching set of parameters for a valid dma channel - pub unsafe fn on_irq_inner(dma: pac::bdma::Dma, channel_num: u8, index: u8) { - let channel_num = channel_num as usize; - let index = index as usize; +impl<'a, C: Channel> Unpin for Transfer<'a, C> {} +impl<'a, C: Channel> Future for Transfer<'a, C> { + type Output = (); + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + STATE.ch_wakers[self.channel.index()].register(cx.waker()); - let isr = dma.isr().read(); - let cr = dma.ch(channel_num).cr(); - - if isr.teif(channel_num) { - panic!("DMA: error on BDMA@{:08x} channel {}", dma.0 as u32, channel_num); - } - if isr.tcif(channel_num) && cr.read().tcie() { - cr.write(|_| ()); // Disable channel interrupts with the default value. - STATE.ch_wakers[index].wake(); + if self.is_running() { + Poll::Pending + } else { + Poll::Ready(()) } } } + +// ============================== + +struct DmaCtrlImpl<'a, C: Channel>(PeripheralRef<'a, C>); + +impl<'a, C: Channel> DmaCtrl for DmaCtrlImpl<'a, C> { + fn get_remaining_transfers(&self) -> usize { + let ch = self.0.regs().ch(self.0.num()); + ch.ndtr().read().ndt() as usize + } + + fn get_complete_count(&self) -> usize { + STATE.complete_count[self.0.index()].load(Ordering::Acquire) + } + + fn reset_complete_count(&mut self) -> usize { + STATE.complete_count[self.0.index()].swap(0, Ordering::AcqRel) + } +} + +pub struct RingBuffer<'a, C: Channel, W: Word> { + cr: regs::Cr, + channel: PeripheralRef<'a, C>, + ringbuf: DmaRingBuffer<'a, W>, +} + +impl<'a, C: Channel, W: Word> RingBuffer<'a, C, W> { + pub unsafe fn new_read( + channel: impl Peripheral

+ 'a, + _request: Request, + peri_addr: *mut W, + buffer: &'a mut [W], + _options: TransferOptions, + ) -> Self { + into_ref!(channel); + + let len = buffer.len(); + assert!(len > 0 && len <= 0xFFFF); + + let dir = Dir::PeripheralToMemory; + let data_size = W::size(); + + let channel_number = channel.num(); + let dma = channel.regs(); + + // "Preceding reads and writes cannot be moved past subsequent writes." + fence(Ordering::SeqCst); + + #[cfg(bdma_v2)] + critical_section::with(|_| channel.regs().cselr().modify(|w| w.set_cs(channel.num(), _request))); + + let mut w = regs::Cr(0); + w.set_psize(data_size.into()); + w.set_msize(data_size.into()); + w.set_minc(vals::Inc::ENABLED); + w.set_dir(dir.into()); + w.set_teie(true); + w.set_htie(true); + w.set_tcie(true); + w.set_circ(vals::Circ::ENABLED); + w.set_pl(vals::Pl::VERYHIGH); + w.set_en(true); + + let buffer_ptr = buffer.as_mut_ptr(); + let mut this = Self { + channel, + cr: w, + ringbuf: DmaRingBuffer::new(buffer), + }; + this.clear_irqs(); + + #[cfg(dmamux)] + super::dmamux::configure_dmamux(&mut *this.channel, _request); + + let ch = dma.ch(channel_number); + ch.par().write_value(peri_addr as u32); + ch.mar().write_value(buffer_ptr as u32); + ch.ndtr().write(|w| w.set_ndt(len as u16)); + + this + } + + pub fn start(&mut self) { + let ch = self.channel.regs().ch(self.channel.num()); + ch.cr().write_value(self.cr) + } + + pub fn clear(&mut self) { + self.ringbuf.clear(DmaCtrlImpl(self.channel.reborrow())); + } + + /// Read bytes from the ring buffer + /// Return a tuple of the length read and the length remaining in the buffer + /// If not all of the bytes were read, then there will be some bytes in the buffer remaining + /// The length remaining is the capacity, ring_buf.len(), less the bytes remaining after the read + /// OverrunError is returned if the portion to be read was overwritten by the DMA controller. + pub fn read(&mut self, buf: &mut [W]) -> Result<(usize, usize), OverrunError> { + self.ringbuf.read(DmaCtrlImpl(self.channel.reborrow()), buf) + } + + /// The capacity of the ringbuffer + pub fn cap(&self) -> usize { + self.ringbuf.cap() + } + + pub fn set_waker(&mut self, waker: &Waker) { + STATE.ch_wakers[self.channel.index()].register(waker); + } + + fn clear_irqs(&mut self) { + let dma = self.channel.regs(); + dma.ifcr().write(|w| { + w.set_htif(self.channel.num(), true); + w.set_tcif(self.channel.num(), true); + w.set_teif(self.channel.num(), true); + }); + } + + pub fn request_stop(&mut self) { + let ch = self.channel.regs().ch(self.channel.num()); + + // Disable the channel. Keep the IEs enabled so the irqs still fire. + // If the channel is enabled and transfer is not completed, we need to perform + // two separate write access to the CR register to disable the channel. + ch.cr().write(|w| { + w.set_teie(true); + w.set_htie(true); + w.set_tcie(true); + }); + } + + pub fn is_running(&mut self) -> bool { + let ch = self.channel.regs().ch(self.channel.num()); + ch.cr().read().en() + } +} + +impl<'a, C: Channel, W: Word> Drop for RingBuffer<'a, C, W> { + fn drop(&mut self) { + self.request_stop(); + while self.is_running() {} + + // "Subsequent reads and writes cannot be moved ahead of preceding reads." + fence(Ordering::SeqCst); + } +} diff --git a/embassy-stm32/src/dma/dma.rs b/embassy-stm32/src/dma/dma.rs index a45b8780b..58d438af8 100644 --- a/embassy-stm32/src/dma/dma.rs +++ b/embassy-stm32/src/dma/dma.rs @@ -1,14 +1,46 @@ -use core::sync::atomic::{fence, Ordering}; -use core::task::Waker; +use core::future::Future; +use core::marker::PhantomData; +use core::pin::Pin; +use core::sync::atomic::{fence, AtomicUsize, Ordering}; +use core::task::{Context, Poll, Waker}; +use embassy_hal_common::{into_ref, Peripheral, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; -use super::{Burst, FlowControl, Request, TransferOptions, Word, WordSize}; +use super::ringbuffer::{DmaCtrl, DmaRingBuffer, OverrunError}; +use super::word::{Word, WordSize}; +use super::Dir; use crate::_generated::DMA_CHANNEL_COUNT; -use crate::interrupt::{Interrupt, InterruptExt}; +use crate::interrupt::typelevel::Interrupt; +use crate::interrupt::Priority; use crate::pac::dma::{regs, vals}; use crate::{interrupt, pac}; +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct TransferOptions { + /// Peripheral burst transfer configuration + pub pburst: Burst, + /// Memory burst transfer configuration + pub mburst: Burst, + /// Flow control configuration + pub flow_ctrl: FlowControl, + /// FIFO threshold for DMA FIFO mode. If none, direct mode is used. + pub fifo_threshold: Option, +} + +impl Default for TransferOptions { + fn default() -> Self { + Self { + pburst: Burst::Single, + mburst: Burst::Single, + flow_ctrl: FlowControl::Dma, + fifo_threshold: None, + } + } +} + impl From for vals::Size { fn from(raw: WordSize) -> Self { match raw { @@ -19,6 +51,28 @@ impl From for vals::Size { } } +impl From

for vals::Dir { + fn from(raw: Dir) -> Self { + match raw { + Dir::MemoryToPeripheral => Self::MEMORYTOPERIPHERAL, + Dir::PeripheralToMemory => Self::PERIPHERALTOMEMORY, + } + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Burst { + /// Single transfer + Single, + /// Incremental burst of 4 beats + Incr4, + /// Incremental burst of 8 beats + Incr8, + /// Incremental burst of 16 beats + Incr16, +} + impl From for vals::Burst { fn from(burst: Burst) -> Self { match burst { @@ -30,6 +84,15 @@ impl From for vals::Burst { } } +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum FlowControl { + /// Flow control by DMA + Dma, + /// Flow control by peripheral + Peripheral, +} + impl From for vals::Pfctrl { fn from(flow: FlowControl) -> Self { match flow { @@ -39,27 +102,42 @@ impl From for vals::Pfctrl { } } -struct ChannelState { - waker: AtomicWaker, +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum FifoThreshold { + /// 1/4 full FIFO + Quarter, + /// 1/2 full FIFO + Half, + /// 3/4 full FIFO + ThreeQuarters, + /// Full FIFO + Full, } -impl ChannelState { - const fn new() -> Self { - Self { - waker: AtomicWaker::new(), +impl From for vals::Fth { + fn from(value: FifoThreshold) -> Self { + match value { + FifoThreshold::Quarter => vals::Fth::QUARTER, + FifoThreshold::Half => vals::Fth::HALF, + FifoThreshold::ThreeQuarters => vals::Fth::THREEQUARTERS, + FifoThreshold::Full => vals::Fth::FULL, } } } struct State { - channels: [ChannelState; DMA_CHANNEL_COUNT], + ch_wakers: [AtomicWaker; DMA_CHANNEL_COUNT], + complete_count: [AtomicUsize; DMA_CHANNEL_COUNT], } impl State { const fn new() -> Self { - const CH: ChannelState = ChannelState::new(); + const ZERO: AtomicUsize = AtomicUsize::new(0); + const AW: AtomicWaker = AtomicWaker::new(); Self { - channels: [CH; DMA_CHANNEL_COUNT], + ch_wakers: [AW; DMA_CHANNEL_COUNT], + complete_count: [ZERO; DMA_CHANNEL_COUNT], } } } @@ -67,10 +145,11 @@ impl State { static STATE: State = State::new(); /// safety: must be called only once -pub(crate) unsafe fn init() { +pub(crate) unsafe fn init(irq_priority: Priority) { foreach_interrupt! { ($peri:ident, dma, $block:ident, $signal_name:ident, $irq:ident) => { - interrupt::$irq::steal().enable(); + interrupt::typelevel::$irq::set_priority(irq_priority); + interrupt::typelevel::$irq::enable(); }; } crate::_generated::init_dma(); @@ -78,172 +157,212 @@ pub(crate) unsafe fn init() { foreach_dma_channel! { ($channel_peri:ident, $dma_peri:ident, dma, $channel_num:expr, $index:expr, $dmamux:tt) => { - impl crate::dma::sealed::Channel for crate::peripherals::$channel_peri { - unsafe fn start_write(&mut self, request: Request, buf: *const [W], reg_addr: *mut W, options: TransferOptions) { - let (ptr, len) = super::slice_ptr_parts(buf); - low_level_api::start_transfer( - pac::$dma_peri, - $channel_num, - request, - vals::Dir::MEMORYTOPERIPHERAL, - reg_addr as *const u32, - ptr as *mut u32, - len, - true, - vals::Size::from(W::bits()), - options, - #[cfg(dmamux)] - ::DMAMUX_REGS, - #[cfg(dmamux)] - ::DMAMUX_CH_NUM, - ) + impl sealed::Channel for crate::peripherals::$channel_peri { + fn regs(&self) -> pac::dma::Dma { + pac::$dma_peri } - - unsafe fn start_write_repeated(&mut self, request: Request, repeated: W, count: usize, reg_addr: *mut W, options: TransferOptions) { - let buf = [repeated]; - low_level_api::start_transfer( - pac::$dma_peri, - $channel_num, - request, - vals::Dir::MEMORYTOPERIPHERAL, - reg_addr as *const u32, - buf.as_ptr() as *mut u32, - count, - false, - vals::Size::from(W::bits()), - options, - #[cfg(dmamux)] - ::DMAMUX_REGS, - #[cfg(dmamux)] - ::DMAMUX_CH_NUM, - ) + fn num(&self) -> usize { + $channel_num } - - unsafe fn start_read(&mut self, request: Request, reg_addr: *const W, buf: *mut [W], options: TransferOptions) { - let (ptr, len) = super::slice_ptr_parts_mut(buf); - low_level_api::start_transfer( - pac::$dma_peri, - $channel_num, - request, - vals::Dir::PERIPHERALTOMEMORY, - reg_addr as *const u32, - ptr as *mut u32, - len, - true, - vals::Size::from(W::bits()), - options, - #[cfg(dmamux)] - ::DMAMUX_REGS, - #[cfg(dmamux)] - ::DMAMUX_CH_NUM, - ); + fn index(&self) -> usize { + $index } - - unsafe fn start_double_buffered_read( - &mut self, - request: Request, - reg_addr: *const W, - buffer0: *mut W, - buffer1: *mut W, - buffer_len: usize, - options: TransferOptions, - ) { - low_level_api::start_dbm_transfer( - pac::$dma_peri, - $channel_num, - request, - vals::Dir::PERIPHERALTOMEMORY, - reg_addr as *const u32, - buffer0 as *mut u32, - buffer1 as *mut u32, - buffer_len, - true, - vals::Size::from(W::bits()), - options, - #[cfg(dmamux)] - ::DMAMUX_REGS, - #[cfg(dmamux)] - ::DMAMUX_CH_NUM, - ); - } - - unsafe fn set_buffer0(&mut self, buffer: *mut W) { - low_level_api::set_dbm_buffer0(pac::$dma_peri, $channel_num, buffer as *mut u32); - } - - unsafe fn set_buffer1(&mut self, buffer: *mut W) { - low_level_api::set_dbm_buffer1(pac::$dma_peri, $channel_num, buffer as *mut u32); - } - - unsafe fn is_buffer0_accessible(&mut self) -> bool { - low_level_api::is_buffer0_accessible(pac::$dma_peri, $channel_num) - } - - fn request_stop(&mut self) { - unsafe {low_level_api::request_stop(pac::$dma_peri, $channel_num);} - } - - fn is_running(&self) -> bool { - unsafe {low_level_api::is_running(pac::$dma_peri, $channel_num)} - } - - fn remaining_transfers(&mut self) -> u16 { - unsafe {low_level_api::get_remaining_transfers(pac::$dma_peri, $channel_num)} - } - - fn set_waker(&mut self, waker: &Waker) { - unsafe {low_level_api::set_waker($index, waker )} - } - fn on_irq() { - unsafe { - low_level_api::on_irq_inner(pac::$dma_peri, $channel_num, $index); - } + unsafe { on_irq_inner(pac::$dma_peri, $channel_num, $index) } } } - impl crate::dma::Channel for crate::peripherals::$channel_peri { } + + impl Channel for crate::peripherals::$channel_peri {} }; } -mod low_level_api { +/// Safety: Must be called with a matching set of parameters for a valid dma channel +pub(crate) unsafe fn on_irq_inner(dma: pac::dma::Dma, channel_num: usize, index: usize) { + let cr = dma.st(channel_num).cr(); + let isr = dma.isr(channel_num / 4).read(); + + if isr.teif(channel_num % 4) { + panic!("DMA: error on DMA@{:08x} channel {}", dma.as_ptr() as u32, channel_num); + } + + if isr.htif(channel_num % 4) && cr.read().htie() { + // Acknowledge half transfer complete interrupt + dma.ifcr(channel_num / 4).write(|w| w.set_htif(channel_num % 4, true)); + } else if isr.tcif(channel_num % 4) && cr.read().tcie() { + // Acknowledge transfer complete interrupt + dma.ifcr(channel_num / 4).write(|w| w.set_tcif(channel_num % 4, true)); + STATE.complete_count[index].fetch_add(1, Ordering::Release); + } else { + return; + } + + STATE.ch_wakers[index].wake(); +} + +#[cfg(any(dma_v2, dmamux))] +pub type Request = u8; +#[cfg(not(any(dma_v2, dmamux)))] +pub type Request = (); + +#[cfg(dmamux)] +pub trait Channel: sealed::Channel + Peripheral

+ 'static + super::dmamux::MuxChannel {} +#[cfg(not(dmamux))] +pub trait Channel: sealed::Channel + Peripheral

+ 'static {} + +pub(crate) mod sealed { use super::*; - pub unsafe fn start_transfer( - dma: pac::dma::Dma, - channel_number: u8, + pub trait Channel { + fn regs(&self) -> pac::dma::Dma; + fn num(&self) -> usize; + fn index(&self) -> usize; + fn on_irq(); + } +} + +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Transfer<'a, C: Channel> { + channel: PeripheralRef<'a, C>, +} + +impl<'a, C: Channel> Transfer<'a, C> { + pub unsafe fn new_read( + channel: impl Peripheral

+ 'a, request: Request, - dir: vals::Dir, + peri_addr: *mut W, + buf: &'a mut [W], + options: TransferOptions, + ) -> Self { + Self::new_read_raw(channel, request, peri_addr, buf, options) + } + + pub unsafe fn new_read_raw( + channel: impl Peripheral

+ 'a, + request: Request, + peri_addr: *mut W, + buf: *mut [W], + options: TransferOptions, + ) -> Self { + into_ref!(channel); + + let (ptr, len) = super::slice_ptr_parts_mut(buf); + assert!(len > 0 && len <= 0xFFFF); + + Self::new_inner( + channel, + request, + Dir::PeripheralToMemory, + peri_addr as *const u32, + ptr as *mut u32, + len, + true, + W::size(), + options, + ) + } + + pub unsafe fn new_write( + channel: impl Peripheral

+ 'a, + request: Request, + buf: &'a [W], + peri_addr: *mut W, + options: TransferOptions, + ) -> Self { + Self::new_write_raw(channel, request, buf, peri_addr, options) + } + + pub unsafe fn new_write_raw( + channel: impl Peripheral

+ 'a, + request: Request, + buf: *const [W], + peri_addr: *mut W, + options: TransferOptions, + ) -> Self { + into_ref!(channel); + + let (ptr, len) = super::slice_ptr_parts(buf); + assert!(len > 0 && len <= 0xFFFF); + + Self::new_inner( + channel, + request, + Dir::MemoryToPeripheral, + peri_addr as *const u32, + ptr as *mut u32, + len, + true, + W::size(), + options, + ) + } + + pub unsafe fn new_write_repeated( + channel: impl Peripheral

+ 'a, + request: Request, + repeated: &'a W, + count: usize, + peri_addr: *mut W, + options: TransferOptions, + ) -> Self { + into_ref!(channel); + + Self::new_inner( + channel, + request, + Dir::MemoryToPeripheral, + peri_addr as *const u32, + repeated as *const W as *mut u32, + count, + false, + W::size(), + options, + ) + } + + unsafe fn new_inner( + channel: PeripheralRef<'a, C>, + _request: Request, + dir: Dir, peri_addr: *const u32, mem_addr: *mut u32, mem_len: usize, incr_mem: bool, - data_size: vals::Size, + data_size: WordSize, options: TransferOptions, - #[cfg(dmamux)] dmamux_regs: pac::dmamux::Dmamux, - #[cfg(dmamux)] dmamux_ch_num: u8, - ) { - #[cfg(dmamux)] - super::super::dmamux::configure_dmamux(dmamux_regs, dmamux_ch_num, request); + ) -> Self { + let ch = channel.regs().st(channel.num()); // "Preceding reads and writes cannot be moved past subsequent writes." fence(Ordering::SeqCst); - reset_status(dma, channel_number); + let mut this = Self { channel }; + this.clear_irqs(); + + #[cfg(dmamux)] + super::dmamux::configure_dmamux(&mut *this.channel, _request); - let ch = dma.st(channel_number as _); ch.par().write_value(peri_addr as u32); ch.m0ar().write_value(mem_addr as u32); ch.ndtr().write_value(regs::Ndtr(mem_len as _)); - ch.cr().write(|w| { - w.set_dir(dir); - w.set_msize(data_size); - w.set_psize(data_size); - w.set_pl(vals::Pl::VERYHIGH); - if incr_mem { - w.set_minc(vals::Inc::INCREMENTED); + ch.fcr().write(|w| { + if let Some(fth) = options.fifo_threshold { + // FIFO mode + w.set_dmdis(vals::Dmdis::DISABLED); + w.set_fth(fth.into()); } else { - w.set_minc(vals::Inc::FIXED); + // Direct mode + w.set_dmdis(vals::Dmdis::ENABLED); } + }); + ch.cr().write(|w| { + w.set_dir(dir.into()); + w.set_msize(data_size.into()); + w.set_psize(data_size.into()); + w.set_pl(vals::Pl::VERYHIGH); + w.set_minc(match incr_mem { + true => vals::Inc::INCREMENTED, + false => vals::Inc::FIXED, + }); w.set_pinc(vals::Inc::FIXED); w.set_teie(true); w.set_tcie(true); @@ -251,7 +370,7 @@ mod low_level_api { w.set_trbuff(true); #[cfg(dma_v2)] - w.set_chsel(request); + w.set_chsel(_request); w.set_pburst(options.pburst.into()); w.set_mburst(options.mburst.into()); @@ -259,163 +378,378 @@ mod low_level_api { w.set_en(true); }); + + this } - pub unsafe fn start_dbm_transfer( - dma: pac::dma::Dma, - channel_number: u8, - request: Request, - dir: vals::Dir, - peri_addr: *const u32, - mem0_addr: *mut u32, - mem1_addr: *mut u32, - mem_len: usize, - incr_mem: bool, - data_size: vals::Size, - options: TransferOptions, - #[cfg(dmamux)] dmamux_regs: pac::dmamux::Dmamux, - #[cfg(dmamux)] dmamux_ch_num: u8, - ) { - #[cfg(dmamux)] - super::super::dmamux::configure_dmamux(dmamux_regs, dmamux_ch_num, request); + fn clear_irqs(&mut self) { + let isrn = self.channel.num() / 4; + let isrbit = self.channel.num() % 4; - trace!( - "Starting DBM transfer with 0: 0x{:x}, 1: 0x{:x}, len: 0x{:x}", - mem0_addr as u32, - mem1_addr as u32, - mem_len - ); - - // "Preceding reads and writes cannot be moved past subsequent writes." - fence(Ordering::SeqCst); - - reset_status(dma, channel_number); - - let ch = dma.st(channel_number as _); - ch.par().write_value(peri_addr as u32); - ch.m0ar().write_value(mem0_addr as u32); - // configures the second buffer for DBM - ch.m1ar().write_value(mem1_addr as u32); - ch.ndtr().write_value(regs::Ndtr(mem_len as _)); - ch.cr().write(|w| { - w.set_dir(dir); - w.set_msize(data_size); - w.set_psize(data_size); - w.set_pl(vals::Pl::VERYHIGH); - if incr_mem { - w.set_minc(vals::Inc::INCREMENTED); - } else { - w.set_minc(vals::Inc::FIXED); - } - w.set_pinc(vals::Inc::FIXED); - w.set_teie(true); - w.set_tcie(true); - - #[cfg(dma_v1)] - w.set_trbuff(true); - - #[cfg(dma_v2)] - w.set_chsel(request); - - // enable double buffered mode - w.set_dbm(vals::Dbm::ENABLED); - - w.set_pburst(options.pburst.into()); - w.set_mburst(options.mburst.into()); - w.set_pfctrl(options.flow_ctrl.into()); - - w.set_en(true); + self.channel.regs().ifcr(isrn).write(|w| { + w.set_tcif(isrbit, true); + w.set_teif(isrbit, true); }); } - pub unsafe fn set_dbm_buffer0(dma: pac::dma::Dma, channel_number: u8, mem_addr: *mut u32) { - // get a handle on the channel itself - let ch = dma.st(channel_number as _); - // change M0AR to the new address - ch.m0ar().write_value(mem_addr as _); - } - - pub unsafe fn set_dbm_buffer1(dma: pac::dma::Dma, channel_number: u8, mem_addr: *mut u32) { - // get a handle on the channel itself - let ch = dma.st(channel_number as _); - // change M1AR to the new address - ch.m1ar().write_value(mem_addr as _); - } - - pub unsafe fn is_buffer0_accessible(dma: pac::dma::Dma, channel_number: u8) -> bool { - // get a handle on the channel itself - let ch = dma.st(channel_number as _); - // check the current target register value - ch.cr().read().ct() == vals::Ct::MEMORY1 - } - - /// Stops the DMA channel. - pub unsafe fn request_stop(dma: pac::dma::Dma, channel_number: u8) { - // get a handle on the channel itself - let ch = dma.st(channel_number as _); + pub fn request_stop(&mut self) { + let ch = self.channel.regs().st(self.channel.num()); // Disable the channel. Keep the IEs enabled so the irqs still fire. ch.cr().write(|w| { w.set_teie(true); w.set_tcie(true); }); - - // "Subsequent reads and writes cannot be moved ahead of preceding reads." - fence(Ordering::SeqCst); } - /// Gets the running status of the channel - pub unsafe fn is_running(dma: pac::dma::Dma, ch: u8) -> bool { - // get a handle on the channel itself - let ch = dma.st(ch as _); - // Get whether it's enabled (running) + pub fn is_running(&mut self) -> bool { + let ch = self.channel.regs().st(self.channel.num()); ch.cr().read().en() } /// Gets the total remaining transfers for the channel /// Note: this will be zero for transfers that completed without cancellation. - pub unsafe fn get_remaining_transfers(dma: pac::dma::Dma, ch: u8) -> u16 { - // get a handle on the channel itself - let ch = dma.st(ch as _); - // read the remaining transfer count. If this is zero, the transfer completed fully. + pub fn get_remaining_transfers(&self) -> u16 { + let ch = self.channel.regs().st(self.channel.num()); ch.ndtr().read().ndt() } - /// Sets the waker for the specified DMA channel - pub unsafe fn set_waker(state_number: usize, waker: &Waker) { - STATE.channels[state_number].waker.register(waker); + pub fn blocking_wait(mut self) { + while self.is_running() {} + + // "Subsequent reads and writes cannot be moved ahead of preceding reads." + fence(Ordering::SeqCst); + + core::mem::forget(self); + } +} + +impl<'a, C: Channel> Drop for Transfer<'a, C> { + fn drop(&mut self) { + self.request_stop(); + while self.is_running() {} + + // "Subsequent reads and writes cannot be moved ahead of preceding reads." + fence(Ordering::SeqCst); + } +} + +impl<'a, C: Channel> Unpin for Transfer<'a, C> {} +impl<'a, C: Channel> Future for Transfer<'a, C> { + type Output = (); + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + STATE.ch_wakers[self.channel.index()].register(cx.waker()); + + if self.is_running() { + Poll::Pending + } else { + Poll::Ready(()) + } + } +} + +// ================================== + +pub struct DoubleBuffered<'a, C: Channel, W: Word> { + channel: PeripheralRef<'a, C>, + _phantom: PhantomData, +} + +impl<'a, C: Channel, W: Word> DoubleBuffered<'a, C, W> { + pub unsafe fn new_read( + channel: impl Peripheral

+ 'a, + _request: Request, + peri_addr: *mut W, + buf0: *mut W, + buf1: *mut W, + len: usize, + options: TransferOptions, + ) -> Self { + into_ref!(channel); + assert!(len > 0 && len <= 0xFFFF); + + let dir = Dir::PeripheralToMemory; + let data_size = W::size(); + + let channel_number = channel.num(); + let dma = channel.regs(); + + // "Preceding reads and writes cannot be moved past subsequent writes." + fence(Ordering::SeqCst); + + let mut this = Self { + channel, + _phantom: PhantomData, + }; + this.clear_irqs(); + + #[cfg(dmamux)] + super::dmamux::configure_dmamux(&mut *this.channel, _request); + + let ch = dma.st(channel_number); + ch.par().write_value(peri_addr as u32); + ch.m0ar().write_value(buf0 as u32); + ch.m1ar().write_value(buf1 as u32); + ch.ndtr().write_value(regs::Ndtr(len as _)); + ch.fcr().write(|w| { + if let Some(fth) = options.fifo_threshold { + // FIFO mode + w.set_dmdis(vals::Dmdis::DISABLED); + w.set_fth(fth.into()); + } else { + // Direct mode + w.set_dmdis(vals::Dmdis::ENABLED); + } + }); + ch.cr().write(|w| { + w.set_dir(dir.into()); + w.set_msize(data_size.into()); + w.set_psize(data_size.into()); + w.set_pl(vals::Pl::VERYHIGH); + w.set_minc(vals::Inc::INCREMENTED); + w.set_pinc(vals::Inc::FIXED); + w.set_teie(true); + w.set_tcie(true); + #[cfg(dma_v1)] + w.set_trbuff(true); + + #[cfg(dma_v2)] + w.set_chsel(_request); + + w.set_pburst(options.pburst.into()); + w.set_mburst(options.mburst.into()); + w.set_pfctrl(options.flow_ctrl.into()); + + w.set_en(true); + }); + + this } - pub unsafe fn reset_status(dma: pac::dma::Dma, channel_number: u8) { - let isrn = channel_number as usize / 4; - let isrbit = channel_number as usize % 4; + fn clear_irqs(&mut self) { + let channel_number = self.channel.num(); + let dma = self.channel.regs(); + let isrn = channel_number / 4; + let isrbit = channel_number % 4; dma.ifcr(isrn).write(|w| { + w.set_htif(isrbit, true); w.set_tcif(isrbit, true); w.set_teif(isrbit, true); }); } - /// Safety: Must be called with a matching set of parameters for a valid dma channel - pub unsafe fn on_irq_inner(dma: pac::dma::Dma, channel_num: u8, state_index: u8) { - let channel_num = channel_num as usize; - let state_index = state_index as usize; + pub unsafe fn set_buffer0(&mut self, buffer: *mut W) { + let ch = self.channel.regs().st(self.channel.num()); + ch.m0ar().write_value(buffer as _); + } - let cr = dma.st(channel_num).cr(); - let isr = dma.isr(channel_num / 4).read(); + pub unsafe fn set_buffer1(&mut self, buffer: *mut W) { + let ch = self.channel.regs().st(self.channel.num()); + ch.m1ar().write_value(buffer as _); + } - if isr.teif(channel_num % 4) { - panic!("DMA: error on DMA@{:08x} channel {}", dma.0 as u32, channel_num); - } + pub fn is_buffer0_accessible(&mut self) -> bool { + let ch = self.channel.regs().st(self.channel.num()); + ch.cr().read().ct() == vals::Ct::MEMORY1 + } - if isr.tcif(channel_num % 4) && cr.read().tcie() { - if cr.read().dbm() == vals::Dbm::DISABLED { - cr.write(|_| ()); // Disable channel with the default value. - } else { - // for double buffered mode, clear TCIF flag but do not stop the transfer - dma.ifcr(channel_num / 4).write(|w| w.set_tcif(channel_num % 4, true)); - } - STATE.channels[state_index].waker.wake(); - } + pub fn set_waker(&mut self, waker: &Waker) { + STATE.ch_wakers[self.channel.index()].register(waker); + } + + pub fn request_stop(&mut self) { + let ch = self.channel.regs().st(self.channel.num()); + + // Disable the channel. Keep the IEs enabled so the irqs still fire. + ch.cr().write(|w| { + w.set_teie(true); + w.set_tcie(true); + }); + } + + pub fn is_running(&mut self) -> bool { + let ch = self.channel.regs().st(self.channel.num()); + ch.cr().read().en() + } + + /// Gets the total remaining transfers for the channel + /// Note: this will be zero for transfers that completed without cancellation. + pub fn get_remaining_transfers(&self) -> u16 { + let ch = self.channel.regs().st(self.channel.num()); + ch.ndtr().read().ndt() + } +} + +impl<'a, C: Channel, W: Word> Drop for DoubleBuffered<'a, C, W> { + fn drop(&mut self) { + self.request_stop(); + while self.is_running() {} + + // "Subsequent reads and writes cannot be moved ahead of preceding reads." + fence(Ordering::SeqCst); + } +} + +// ============================== + +struct DmaCtrlImpl<'a, C: Channel>(PeripheralRef<'a, C>); + +impl<'a, C: Channel> DmaCtrl for DmaCtrlImpl<'a, C> { + fn get_remaining_transfers(&self) -> usize { + let ch = self.0.regs().st(self.0.num()); + ch.ndtr().read().ndt() as usize + } + + fn get_complete_count(&self) -> usize { + STATE.complete_count[self.0.index()].load(Ordering::Acquire) + } + + fn reset_complete_count(&mut self) -> usize { + STATE.complete_count[self.0.index()].swap(0, Ordering::AcqRel) + } +} + +pub struct RingBuffer<'a, C: Channel, W: Word> { + cr: regs::Cr, + channel: PeripheralRef<'a, C>, + ringbuf: DmaRingBuffer<'a, W>, +} + +impl<'a, C: Channel, W: Word> RingBuffer<'a, C, W> { + pub unsafe fn new_read( + channel: impl Peripheral

+ 'a, + _request: Request, + peri_addr: *mut W, + buffer: &'a mut [W], + options: TransferOptions, + ) -> Self { + into_ref!(channel); + + let len = buffer.len(); + assert!(len > 0 && len <= 0xFFFF); + + let dir = Dir::PeripheralToMemory; + let data_size = W::size(); + + let channel_number = channel.num(); + let dma = channel.regs(); + + // "Preceding reads and writes cannot be moved past subsequent writes." + fence(Ordering::SeqCst); + + let mut w = regs::Cr(0); + w.set_dir(dir.into()); + w.set_msize(data_size.into()); + w.set_psize(data_size.into()); + w.set_pl(vals::Pl::VERYHIGH); + w.set_minc(vals::Inc::INCREMENTED); + w.set_pinc(vals::Inc::FIXED); + w.set_teie(true); + w.set_htie(true); + w.set_tcie(true); + w.set_circ(vals::Circ::ENABLED); + #[cfg(dma_v1)] + w.set_trbuff(true); + #[cfg(dma_v2)] + w.set_chsel(_request); + w.set_pburst(options.pburst.into()); + w.set_mburst(options.mburst.into()); + w.set_pfctrl(options.flow_ctrl.into()); + w.set_en(true); + + let buffer_ptr = buffer.as_mut_ptr(); + let mut this = Self { + channel, + cr: w, + ringbuf: DmaRingBuffer::new(buffer), + }; + this.clear_irqs(); + + #[cfg(dmamux)] + super::dmamux::configure_dmamux(&mut *this.channel, _request); + + let ch = dma.st(channel_number); + ch.par().write_value(peri_addr as u32); + ch.m0ar().write_value(buffer_ptr as u32); + ch.ndtr().write_value(regs::Ndtr(len as _)); + ch.fcr().write(|w| { + if let Some(fth) = options.fifo_threshold { + // FIFO mode + w.set_dmdis(vals::Dmdis::DISABLED); + w.set_fth(fth.into()); + } else { + // Direct mode + w.set_dmdis(vals::Dmdis::ENABLED); + } + }); + + this + } + + pub fn start(&mut self) { + let ch = self.channel.regs().st(self.channel.num()); + ch.cr().write_value(self.cr); + } + + pub fn clear(&mut self) { + self.ringbuf.clear(DmaCtrlImpl(self.channel.reborrow())); + } + + /// Read bytes from the ring buffer + /// Return a tuple of the length read and the length remaining in the buffer + /// If not all of the bytes were read, then there will be some bytes in the buffer remaining + /// The length remaining is the capacity, ring_buf.len(), less the bytes remaining after the read + /// OverrunError is returned if the portion to be read was overwritten by the DMA controller. + pub fn read(&mut self, buf: &mut [W]) -> Result<(usize, usize), OverrunError> { + self.ringbuf.read(DmaCtrlImpl(self.channel.reborrow()), buf) + } + + // The capacity of the ringbuffer + pub fn cap(&self) -> usize { + self.ringbuf.cap() + } + + pub fn set_waker(&mut self, waker: &Waker) { + STATE.ch_wakers[self.channel.index()].register(waker); + } + + fn clear_irqs(&mut self) { + let channel_number = self.channel.num(); + let dma = self.channel.regs(); + let isrn = channel_number / 4; + let isrbit = channel_number % 4; + + dma.ifcr(isrn).write(|w| { + w.set_htif(isrbit, true); + w.set_tcif(isrbit, true); + w.set_teif(isrbit, true); + }); + } + + pub fn request_stop(&mut self) { + let ch = self.channel.regs().st(self.channel.num()); + + // Disable the channel. Keep the IEs enabled so the irqs still fire. + ch.cr().write(|w| { + w.set_teie(true); + w.set_htie(true); + w.set_tcie(true); + }); + } + + pub fn is_running(&mut self) -> bool { + let ch = self.channel.regs().st(self.channel.num()); + ch.cr().read().en() + } +} + +impl<'a, C: Channel, W: Word> Drop for RingBuffer<'a, C, W> { + fn drop(&mut self) { + self.request_stop(); + while self.is_running() {} + + // "Subsequent reads and writes cannot be moved ahead of preceding reads." + fence(Ordering::SeqCst); } } diff --git a/embassy-stm32/src/dma/dmamux.rs b/embassy-stm32/src/dma/dmamux.rs index e9967e349..36fc03403 100644 --- a/embassy-stm32/src/dma/dmamux.rs +++ b/embassy-stm32/src/dma/dmamux.rs @@ -2,8 +2,8 @@ use crate::{pac, peripherals}; -pub(crate) unsafe fn configure_dmamux(dmamux_regs: pac::dmamux::Dmamux, dmamux_ch_num: u8, request: u8) { - let ch_mux_regs = dmamux_regs.ccr(dmamux_ch_num as _); +pub(crate) fn configure_dmamux(channel: &mut M, request: u8) { + let ch_mux_regs = channel.mux_regs().ccr(channel.mux_num()); ch_mux_regs.write(|reg| { reg.set_nbreq(0); reg.set_dmareq_id(request); @@ -14,11 +14,11 @@ pub(crate) unsafe fn configure_dmamux(dmamux_regs: pac::dmamux::Dmamux, dmamux_c }); } -pub(crate) mod sealed { +pub(crate) mod dmamux_sealed { use super::*; pub trait MuxChannel { - const DMAMUX_CH_NUM: u8; - const DMAMUX_REGS: pac::dmamux::Dmamux; + fn mux_regs(&self) -> pac::dmamux::Dmamux; + fn mux_num(&self) -> usize; } } @@ -26,15 +26,19 @@ pub struct DMAMUX1; #[cfg(stm32h7)] pub struct DMAMUX2; -pub trait MuxChannel: sealed::MuxChannel + super::Channel { +pub trait MuxChannel: dmamux_sealed::MuxChannel { type Mux; } foreach_dma_channel! { ($channel_peri:ident, $dma_peri:ident, $version:ident, $channel_num:expr, $index:expr, {dmamux: $dmamux:ident, dmamux_channel: $dmamux_channel:expr}) => { - impl sealed::MuxChannel for peripherals::$channel_peri { - const DMAMUX_CH_NUM: u8 = $dmamux_channel; - const DMAMUX_REGS: pac::dmamux::Dmamux = pac::$dmamux; + impl dmamux_sealed::MuxChannel for peripherals::$channel_peri { + fn mux_regs(&self) -> pac::dmamux::Dmamux { + pac::$dmamux + } + fn mux_num(&self) -> usize { + $dmamux_channel + } } impl MuxChannel for peripherals::$channel_peri { type Mux = $dmamux; diff --git a/embassy-stm32/src/dma/gpdma.rs b/embassy-stm32/src/dma/gpdma.rs index bde8c3ef3..b7bcf7795 100644 --- a/embassy-stm32/src/dma/gpdma.rs +++ b/embassy-stm32/src/dma/gpdma.rs @@ -1,13 +1,31 @@ -use core::sync::atomic::{fence, Ordering}; -use core::task::Waker; +#![macro_use] +use core::future::Future; +use core::pin::Pin; +use core::sync::atomic::{fence, Ordering}; +use core::task::{Context, Poll}; + +use embassy_hal_common::{into_ref, Peripheral, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; -use super::{Request, TransferOptions, Word, WordSize}; +use super::word::{Word, WordSize}; +use super::Dir; use crate::_generated::GPDMA_CHANNEL_COUNT; -use crate::interrupt::{Interrupt, InterruptExt}; -use crate::pac::gpdma::{vals, Gpdma}; -use crate::{interrupt, pac}; +use crate::interrupt::typelevel::Interrupt; +use crate::interrupt::Priority; +use crate::pac; +use crate::pac::gpdma::vals; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct TransferOptions {} + +impl Default for TransferOptions { + fn default() -> Self { + Self {} + } +} impl From for vals::ChTr1Dw { fn from(raw: WordSize) -> Self { @@ -19,27 +37,15 @@ impl From for vals::ChTr1Dw { } } -struct ChannelState { - waker: AtomicWaker, -} - -impl ChannelState { - const fn new() -> Self { - Self { - waker: AtomicWaker::new(), - } - } -} - struct State { - channels: [ChannelState; GPDMA_CHANNEL_COUNT], + ch_wakers: [AtomicWaker; GPDMA_CHANNEL_COUNT], } impl State { const fn new() -> Self { - const CH: ChannelState = ChannelState::new(); + const AW: AtomicWaker = AtomicWaker::new(); Self { - channels: [CH; GPDMA_CHANNEL_COUNT], + ch_wakers: [AW; GPDMA_CHANNEL_COUNT], } } } @@ -47,10 +53,11 @@ impl State { static STATE: State = State::new(); /// safety: must be called only once -pub(crate) unsafe fn init() { +pub(crate) unsafe fn init(irq_priority: Priority) { foreach_interrupt! { ($peri:ident, gpdma, $block:ident, $signal_name:ident, $irq:ident) => { - interrupt::$irq::steal().enable(); + crate::interrupt::typelevel::$irq::set_priority(irq_priority); + crate::interrupt::typelevel::$irq::enable(); }; } crate::_generated::init_gpdma(); @@ -58,118 +65,173 @@ pub(crate) unsafe fn init() { foreach_dma_channel! { ($channel_peri:ident, $dma_peri:ident, gpdma, $channel_num:expr, $index:expr, $dmamux:tt) => { - impl crate::dma::sealed::Channel for crate::peripherals::$channel_peri { - unsafe fn start_write(&mut self, request: Request, buf: *const [W], reg_addr: *mut W, options: TransferOptions) { - let (ptr, len) = super::slice_ptr_parts(buf); - low_level_api::start_transfer( - pac::$dma_peri, - $channel_num, - request, - low_level_api::Dir::MemoryToPeripheral, - reg_addr as *const u32, - ptr as *mut u32, - len, - true, - W::bits(), - options, - ) + impl sealed::Channel for crate::peripherals::$channel_peri { + fn regs(&self) -> pac::gpdma::Gpdma { + pac::$dma_peri } - - unsafe fn start_write_repeated(&mut self, request: Request, repeated: W, count: usize, reg_addr: *mut W, options: TransferOptions) { - let buf = [repeated]; - low_level_api::start_transfer( - pac::$dma_peri, - $channel_num, - request, - low_level_api::Dir::MemoryToPeripheral, - reg_addr as *const u32, - buf.as_ptr() as *mut u32, - count, - false, - W::bits(), - options, - ) + fn num(&self) -> usize { + $channel_num } - - unsafe fn start_read(&mut self, request: Request, reg_addr: *const W, buf: *mut [W], options: TransferOptions) { - let (ptr, len) = super::slice_ptr_parts_mut(buf); - low_level_api::start_transfer( - pac::$dma_peri, - $channel_num, - request, - low_level_api::Dir::PeripheralToMemory, - reg_addr as *const u32, - ptr as *mut u32, - len, - true, - W::bits(), - options, - ); + fn index(&self) -> usize { + $index } - - unsafe fn start_double_buffered_read( - &mut self, - _request: Request, - _reg_addr: *const W, - _buffer0: *mut W, - _buffer1: *mut W, - _buffer_len: usize, - _options: TransferOptions, - ) { - panic!("Unsafe double buffered mode is unavailable on GPBDMA"); - } - - unsafe fn set_buffer0(&mut self, _buffer: *mut W) { - panic!("Unsafe double buffered mode is unavailable on GPBDMA"); - } - - unsafe fn set_buffer1(&mut self, _buffer: *mut W) { - panic!("Unsafe double buffered mode is unavailable on GPBDMA"); - } - - unsafe fn is_buffer0_accessible(&mut self) -> bool { - panic!("Unsafe double buffered mode is unavailable on GPBDMA"); - } - - fn request_stop(&mut self) { - unsafe {low_level_api::request_stop(pac::$dma_peri, $channel_num);} - } - - fn is_running(&self) -> bool { - unsafe {low_level_api::is_running(pac::$dma_peri, $channel_num)} - } - - fn remaining_transfers(&mut self) -> u16 { - unsafe {low_level_api::get_remaining_transfers(pac::$dma_peri, $channel_num)} - } - - fn set_waker(&mut self, waker: &Waker) { - unsafe {low_level_api::set_waker($index, waker )} - } - fn on_irq() { - unsafe { - low_level_api::on_irq_inner(pac::$dma_peri, $channel_num, $index); - } + unsafe { on_irq_inner(pac::$dma_peri, $channel_num, $index) } } } - impl crate::dma::Channel for crate::peripherals::$channel_peri { } + + impl Channel for crate::peripherals::$channel_peri {} }; } -mod low_level_api { - use super::*; +/// Safety: Must be called with a matching set of parameters for a valid dma channel +pub(crate) unsafe fn on_irq_inner(dma: pac::gpdma::Gpdma, channel_num: usize, index: usize) { + let ch = dma.ch(channel_num); + let sr = ch.sr().read(); - #[derive(Debug, Copy, Clone, PartialEq, Eq)] - #[cfg_attr(feature = "defmt", derive(defmt::Format))] - pub enum Dir { - MemoryToPeripheral, - PeripheralToMemory, + if sr.dtef() { + panic!( + "DMA: data transfer error on DMA@{:08x} channel {}", + dma.as_ptr() as u32, + channel_num + ); + } + if sr.usef() { + panic!( + "DMA: user settings error on DMA@{:08x} channel {}", + dma.as_ptr() as u32, + channel_num + ); } - pub unsafe fn start_transfer( - dma: Gpdma, - channel_number: u8, + if sr.suspf() || sr.tcf() { + // disable all xxIEs to prevent the irq from firing again. + ch.cr().write(|_| {}); + + // Wake the future. It'll look at tcf and see it's set. + STATE.ch_wakers[index].wake(); + } +} + +pub type Request = u8; + +#[cfg(dmamux)] +pub trait Channel: sealed::Channel + Peripheral

+ 'static + super::dmamux::MuxChannel {} +#[cfg(not(dmamux))] +pub trait Channel: sealed::Channel + Peripheral

+ 'static {} + +pub(crate) mod sealed { + use super::*; + + pub trait Channel { + fn regs(&self) -> pac::gpdma::Gpdma; + fn num(&self) -> usize; + fn index(&self) -> usize; + fn on_irq(); + } +} + +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Transfer<'a, C: Channel> { + channel: PeripheralRef<'a, C>, +} + +impl<'a, C: Channel> Transfer<'a, C> { + pub unsafe fn new_read( + channel: impl Peripheral

+ 'a, + request: Request, + peri_addr: *mut W, + buf: &'a mut [W], + options: TransferOptions, + ) -> Self { + Self::new_read_raw(channel, request, peri_addr, buf, options) + } + + pub unsafe fn new_read_raw( + channel: impl Peripheral

+ 'a, + request: Request, + peri_addr: *mut W, + buf: *mut [W], + options: TransferOptions, + ) -> Self { + into_ref!(channel); + + let (ptr, len) = super::slice_ptr_parts_mut(buf); + assert!(len > 0 && len <= 0xFFFF); + + Self::new_inner( + channel, + request, + Dir::PeripheralToMemory, + peri_addr as *const u32, + ptr as *mut u32, + len, + true, + W::size(), + options, + ) + } + + pub unsafe fn new_write( + channel: impl Peripheral

+ 'a, + request: Request, + buf: &'a [W], + peri_addr: *mut W, + options: TransferOptions, + ) -> Self { + Self::new_write_raw(channel, request, buf, peri_addr, options) + } + + pub unsafe fn new_write_raw( + channel: impl Peripheral

+ 'a, + request: Request, + buf: *const [W], + peri_addr: *mut W, + options: TransferOptions, + ) -> Self { + into_ref!(channel); + + let (ptr, len) = super::slice_ptr_parts(buf); + assert!(len > 0 && len <= 0xFFFF); + + Self::new_inner( + channel, + request, + Dir::MemoryToPeripheral, + peri_addr as *const u32, + ptr as *mut u32, + len, + true, + W::size(), + options, + ) + } + + pub unsafe fn new_write_repeated( + channel: impl Peripheral

+ 'a, + request: Request, + repeated: &'a W, + count: usize, + peri_addr: *mut W, + options: TransferOptions, + ) -> Self { + into_ref!(channel); + + Self::new_inner( + channel, + request, + Dir::MemoryToPeripheral, + peri_addr as *const u32, + repeated as *const W as *mut u32, + count, + false, + W::size(), + options, + ) + } + + unsafe fn new_inner( + channel: PeripheralRef<'a, C>, request: Request, dir: Dir, peri_addr: *const u32, @@ -178,11 +240,19 @@ mod low_level_api { incr_mem: bool, data_size: WordSize, _options: TransferOptions, - ) { + ) -> Self { + let ch = channel.regs().ch(channel.num()); + // "Preceding reads and writes cannot be moved past subsequent writes." fence(Ordering::SeqCst); - let ch = dma.ch(channel_number as _); + let this = Self { channel }; + + #[cfg(dmamux)] + super::dmamux::configure_dmamux(&mut *this.channel, request); + + ch.cr().write(|w| w.set_reset(true)); + ch.fcr().write(|w| w.0 = 0xFFFF_FFFF); // clear all irqs ch.llr().write(|_| {}); // no linked list ch.tr1().write(|w| { w.set_sdw(data_size.into()); @@ -223,12 +293,12 @@ mod low_level_api { // Start it w.set_en(true); }); + + this } - /// Stops the DMA channel. - pub unsafe fn request_stop(dma: Gpdma, channel_number: u8) { - // get a handle on the channel itself - let ch = dma.ch(channel_number as _); + pub fn request_stop(&mut self) { + let ch = self.channel.regs().ch(self.channel.num()); // Disable the channel. Keep the IEs enabled so the irqs still fire. ch.cr().write(|w| { @@ -236,56 +306,51 @@ mod low_level_api { w.set_useie(true); w.set_dteie(true); w.set_suspie(true); - }); - - // "Subsequent reads and writes cannot be moved ahead of preceding reads." - fence(Ordering::SeqCst); + }) } - /// Gets the running status of the channel - pub unsafe fn is_running(dma: Gpdma, ch: u8) -> bool { - let ch = dma.ch(ch as _); - !ch.sr().read().idlef() + pub fn is_running(&mut self) -> bool { + let ch = self.channel.regs().ch(self.channel.num()); + !ch.sr().read().tcf() } /// Gets the total remaining transfers for the channel /// Note: this will be zero for transfers that completed without cancellation. - pub unsafe fn get_remaining_transfers(dma: Gpdma, ch: u8) -> u16 { - // get a handle on the channel itself - let ch = dma.ch(ch as _); - // read the remaining transfer count. If this is zero, the transfer completed fully. + pub fn get_remaining_transfers(&self) -> u16 { + let ch = self.channel.regs().ch(self.channel.num()); ch.br1().read().bndt() } - /// Sets the waker for the specified DMA channel - pub unsafe fn set_waker(state_number: usize, waker: &Waker) { - STATE.channels[state_number].waker.register(waker); + pub fn blocking_wait(mut self) { + while self.is_running() {} + + // "Subsequent reads and writes cannot be moved ahead of preceding reads." + fence(Ordering::SeqCst); + + core::mem::forget(self); } +} - /// Safety: Must be called with a matching set of parameters for a valid dma channel - pub unsafe fn on_irq_inner(dma: Gpdma, channel_num: u8, state_index: u8) { - let channel_num = channel_num as usize; - let state_index = state_index as usize; +impl<'a, C: Channel> Drop for Transfer<'a, C> { + fn drop(&mut self) { + self.request_stop(); + while self.is_running() {} - let ch = dma.ch(channel_num); - let sr = ch.sr().read(); + // "Subsequent reads and writes cannot be moved ahead of preceding reads." + fence(Ordering::SeqCst); + } +} - if sr.dtef() { - panic!( - "DMA: data transfer error on DMA@{:08x} channel {}", - dma.0 as u32, channel_num - ); - } - if sr.usef() { - panic!( - "DMA: user settings error on DMA@{:08x} channel {}", - dma.0 as u32, channel_num - ); - } +impl<'a, C: Channel> Unpin for Transfer<'a, C> {} +impl<'a, C: Channel> Future for Transfer<'a, C> { + type Output = (); + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + STATE.ch_wakers[self.channel.index()].register(cx.waker()); - if sr.suspf() || sr.tcf() { - ch.cr().write(|w| w.set_reset(true)); - STATE.channels[state_index].waker.wake(); + if self.is_running() { + Poll::Pending + } else { + Poll::Ready(()) } } } diff --git a/embassy-stm32/src/dma/mod.rs b/embassy-stm32/src/dma/mod.rs index cc030a93e..0858587bd 100644 --- a/embassy-stm32/src/dma/mod.rs +++ b/embassy-stm32/src/dma/mod.rs @@ -1,310 +1,48 @@ -#[cfg(bdma)] -pub(crate) mod bdma; #[cfg(dma)] pub(crate) mod dma; +#[cfg(dma)] +pub use dma::*; + +// stm32h7 has both dma and bdma. In that case, we export dma as "main" dma, +// and bdma as "secondary", under `embassy_stm32::dma::bdma`. +#[cfg(all(bdma, dma))] +pub mod bdma; + +#[cfg(all(bdma, not(dma)))] +pub(crate) mod bdma; +#[cfg(all(bdma, not(dma)))] +pub use bdma::*; + +#[cfg(gpdma)] +pub(crate) mod gpdma; +#[cfg(gpdma)] +pub use gpdma::*; + #[cfg(dmamux)] mod dmamux; -#[cfg(gpdma)] -mod gpdma; -use core::future::Future; +pub(crate) mod ringbuffer; +pub mod word; + use core::mem; -use core::pin::Pin; -use core::task::{Context, Poll, Waker}; -use embassy_hal_common::{impl_peripheral, into_ref}; +use embassy_hal_common::impl_peripheral; #[cfg(dmamux)] pub use self::dmamux::*; -use crate::Peripheral; - -#[cfg(feature = "unstable-pac")] -pub mod low_level { - pub use super::transfers::*; -} - -pub(crate) use transfers::*; - -#[cfg(any(bdma_v2, dma_v2, dmamux, gpdma))] -pub type Request = u8; -#[cfg(not(any(bdma_v2, dma_v2, dmamux, gpdma)))] -pub type Request = (); - -pub(crate) mod sealed { - use super::*; - - pub trait Word {} - - pub trait Channel { - /// Starts this channel for writing a stream of words. - /// - /// Safety: - /// - `buf` must point to a valid buffer for DMA reading. - /// - `buf` must be alive for the entire duration of the DMA transfer. - /// - `reg_addr` must be a valid peripheral register address to write to. - unsafe fn start_write( - &mut self, - request: Request, - buf: *const [W], - reg_addr: *mut W, - options: TransferOptions, - ); - - /// Starts this channel for writing a word repeatedly. - /// - /// Safety: - /// - `reg_addr` must be a valid peripheral register address to write to. - unsafe fn start_write_repeated( - &mut self, - request: Request, - repeated: W, - count: usize, - reg_addr: *mut W, - options: TransferOptions, - ); - - /// Starts this channel for reading a stream of words. - /// - /// Safety: - /// - `buf` must point to a valid buffer for DMA writing. - /// - `buf` must be alive for the entire duration of the DMA transfer. - /// - `reg_addr` must be a valid peripheral register address to read from. - unsafe fn start_read( - &mut self, - request: Request, - reg_addr: *const W, - buf: *mut [W], - options: TransferOptions, - ); - - /// DMA double-buffered mode is unsafe as UB can happen when the hardware writes to a buffer currently owned by the software - /// more information can be found here: https://github.com/embassy-rs/embassy/issues/702 - /// This feature is now used solely for the purposes of implementing giant DMA transfers required for DCMI - unsafe fn start_double_buffered_read( - &mut self, - request: Request, - reg_addr: *const W, - buffer0: *mut W, - buffer1: *mut W, - buffer_len: usize, - options: TransferOptions, - ); - - unsafe fn set_buffer0(&mut self, buffer: *mut W); - - unsafe fn set_buffer1(&mut self, buffer: *mut W); - - unsafe fn is_buffer0_accessible(&mut self) -> bool; - - /// Requests the channel to stop. - /// NOTE: The channel does not immediately stop, you have to wait - /// for `is_running() = false`. - fn request_stop(&mut self); - - /// Returns whether this channel is running or stopped. - /// - /// The channel stops running when it either completes or is manually stopped. - fn is_running(&self) -> bool; - - /// Returns the total number of remaining transfers. - fn remaining_transfers(&mut self) -> u16; - - /// Sets the waker that is called when this channel stops (either completed or manually stopped) - fn set_waker(&mut self, waker: &Waker); - - /// This is called when this channel triggers an interrupt. - /// Note: Because some channels share an interrupt, this function might be - /// called for a channel that didn't trigger an interrupt. - fn on_irq(); - } -} +use crate::interrupt::Priority; #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum WordSize { - OneByte, - TwoBytes, - FourBytes, +enum Dir { + MemoryToPeripheral, + PeripheralToMemory, } -impl WordSize { - pub fn bytes(&self) -> usize { - match self { - Self::OneByte => 1, - Self::TwoBytes => 2, - Self::FourBytes => 4, - } - } -} - -pub trait Word: sealed::Word { - fn bits() -> WordSize; -} - -impl sealed::Word for u8 {} -impl Word for u8 { - fn bits() -> WordSize { - WordSize::OneByte - } -} - -impl sealed::Word for u16 {} -impl Word for u16 { - fn bits() -> WordSize { - WordSize::TwoBytes - } -} - -impl sealed::Word for u32 {} -impl Word for u32 { - fn bits() -> WordSize { - WordSize::FourBytes - } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Burst { - /// Single transfer - Single, - /// Incremental burst of 4 beats - Incr4, - /// Incremental burst of 8 beats - Incr8, - /// Incremental burst of 16 beats - Incr16, -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum FlowControl { - /// Flow control by DMA - Dma, - /// Flow control by peripheral - Peripheral, -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct TransferOptions { - /// Peripheral burst transfer configuration - pub pburst: Burst, - /// Memory burst transfer configuration - pub mburst: Burst, - /// Flow control configuration - pub flow_ctrl: FlowControl, -} - -impl Default for TransferOptions { - fn default() -> Self { - Self { - pburst: Burst::Single, - mburst: Burst::Single, - flow_ctrl: FlowControl::Dma, - } - } -} - -mod transfers { - use embassy_hal_common::PeripheralRef; - - use super::*; - - #[allow(unused)] - pub fn read<'a, W: Word>( - channel: impl Peripheral

+ 'a, - request: Request, - reg_addr: *mut W, - buf: &'a mut [W], - ) -> impl Future + 'a { - assert!(buf.len() > 0 && buf.len() <= 0xFFFF); - into_ref!(channel); - - unsafe { channel.start_read::(request, reg_addr, buf, Default::default()) }; - - Transfer::new(channel) - } - - #[allow(unused)] - pub fn write<'a, W: Word>( - channel: impl Peripheral

+ 'a, - request: Request, - buf: &'a [W], - reg_addr: *mut W, - ) -> impl Future + 'a { - assert!(buf.len() > 0 && buf.len() <= 0xFFFF); - into_ref!(channel); - - unsafe { channel.start_write::(request, buf, reg_addr, Default::default()) }; - - Transfer::new(channel) - } - - #[allow(unused)] - pub fn write_repeated<'a, W: Word>( - channel: impl Peripheral

+ 'a, - request: Request, - repeated: W, - count: usize, - reg_addr: *mut W, - ) -> impl Future + 'a { - into_ref!(channel); - - unsafe { channel.start_write_repeated::(request, repeated, count, reg_addr, Default::default()) }; - - Transfer::new(channel) - } - - pub(crate) struct Transfer<'a, C: Channel> { - channel: PeripheralRef<'a, C>, - } - - impl<'a, C: Channel> Transfer<'a, C> { - pub(crate) fn new(channel: impl Peripheral

+ 'a) -> Self { - into_ref!(channel); - Self { channel } - } - } - - impl<'a, C: Channel> Drop for Transfer<'a, C> { - fn drop(&mut self) { - self.channel.request_stop(); - while self.channel.is_running() {} - } - } - - impl<'a, C: Channel> Unpin for Transfer<'a, C> {} - impl<'a, C: Channel> Future for Transfer<'a, C> { - type Output = (); - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - self.channel.set_waker(cx.waker()); - if self.channel.is_running() { - Poll::Pending - } else { - Poll::Ready(()) - } - } - } -} - -pub trait Channel: sealed::Channel + Peripheral

+ 'static {} - pub struct NoDma; impl_peripheral!(NoDma); -// safety: must be called only once at startup -pub(crate) unsafe fn init() { - #[cfg(bdma)] - bdma::init(); - #[cfg(dma)] - dma::init(); - #[cfg(dmamux)] - dmamux::init(); - #[cfg(gpdma)] - gpdma::init(); -} - // TODO: replace transmutes with core::ptr::metadata once it's stable #[allow(unused)] pub(crate) fn slice_ptr_parts(slice: *const [T]) -> (usize, usize) { @@ -315,3 +53,19 @@ pub(crate) fn slice_ptr_parts(slice: *const [T]) -> (usize, usize) { pub(crate) fn slice_ptr_parts_mut(slice: *mut [T]) -> (usize, usize) { unsafe { mem::transmute(slice) } } + +// safety: must be called only once at startup +pub(crate) unsafe fn init( + #[cfg(bdma)] bdma_priority: Priority, + #[cfg(dma)] dma_priority: Priority, + #[cfg(gpdma)] gpdma_priority: Priority, +) { + #[cfg(bdma)] + bdma::init(bdma_priority); + #[cfg(dma)] + dma::init(dma_priority); + #[cfg(gpdma)] + gpdma::init(gpdma_priority); + #[cfg(dmamux)] + dmamux::init(); +} diff --git a/embassy-stm32/src/dma/ringbuffer.rs b/embassy-stm32/src/dma/ringbuffer.rs new file mode 100644 index 000000000..a2bde986f --- /dev/null +++ b/embassy-stm32/src/dma/ringbuffer.rs @@ -0,0 +1,485 @@ +#![cfg_attr(gpdma, allow(unused))] + +use core::ops::Range; +use core::sync::atomic::{compiler_fence, Ordering}; + +use super::word::Word; + +/// A "read-only" ring-buffer to be used together with the DMA controller which +/// writes in a circular way, "uncontrolled" to the buffer. +/// +/// A snapshot of the ring buffer state can be attained by setting the `ndtr` field +/// to the current register value. `ndtr` describes the current position of the DMA +/// write. +/// +/// # Buffer layout +/// +/// ```text +/// Without wraparound: With wraparound: +/// +/// + buf +--- NDTR ---+ + buf +---------- NDTR ----------+ +/// | | | | | | +/// v v v v v v +/// +-----------------------------------------+ +-----------------------------------------+ +/// |oooooooooooXXXXXXXXXXXXXXXXoooooooooooooo| |XXXXXXXXXXXXXooooooooooooXXXXXXXXXXXXXXXX| +/// +-----------------------------------------+ +-----------------------------------------+ +/// ^ ^ ^ ^ ^ ^ +/// | | | | | | +/// +- start --+ | +- end ------+ | +/// | | | | +/// +- end --------------------+ +- start ----------------+ +/// ``` +pub struct DmaRingBuffer<'a, W: Word> { + pub(crate) dma_buf: &'a mut [W], + start: usize, +} + +#[derive(Debug, PartialEq)] +pub struct OverrunError; + +pub trait DmaCtrl { + /// Get the NDTR register value, i.e. the space left in the underlying + /// buffer until the dma writer wraps. + fn get_remaining_transfers(&self) -> usize; + + /// Get the transfer completed counter. + /// This counter is incremented by the dma controller when NDTR is reloaded, + /// i.e. when the writing wraps. + fn get_complete_count(&self) -> usize; + + /// Reset the transfer completed counter to 0 and return the value just prior to the reset. + fn reset_complete_count(&mut self) -> usize; +} + +impl<'a, W: Word> DmaRingBuffer<'a, W> { + pub fn new(dma_buf: &'a mut [W]) -> Self { + Self { dma_buf, start: 0 } + } + + /// Reset the ring buffer to its initial state + pub fn clear(&mut self, mut dma: impl DmaCtrl) { + self.start = 0; + dma.reset_complete_count(); + } + + /// The capacity of the ringbuffer + pub const fn cap(&self) -> usize { + self.dma_buf.len() + } + + /// The current position of the ringbuffer + fn pos(&self, remaining_transfers: usize) -> usize { + self.cap() - remaining_transfers + } + + /// Read bytes from the ring buffer + /// Return a tuple of the length read and the length remaining in the buffer + /// If not all of the bytes were read, then there will be some bytes in the buffer remaining + /// The length remaining is the capacity, ring_buf.len(), less the bytes remaining after the read + /// OverrunError is returned if the portion to be read was overwritten by the DMA controller. + pub fn read(&mut self, mut dma: impl DmaCtrl, buf: &mut [W]) -> Result<(usize, usize), OverrunError> { + /* + This algorithm is optimistic: we assume we haven't overrun more than a full buffer and then check + after we've done our work to see we have. This is because on stm32, an interrupt is not guaranteed + to fire in the same clock cycle that a register is read, so checking get_complete_count early does + not yield relevant information. + + Therefore, the only variable we really need to know is ndtr. If the dma has overrun by more than a full + buffer, we will do a bit more work than we have to, but algorithms should not be optimized for error + conditions. + + After we've done our work, we confirm that we haven't overrun more than a full buffer, and also that + the dma has not overrun within the data we could have copied. We check the data we could have copied + rather than the data we actually copied because it costs nothing and confirms an error condition + earlier. + */ + let end = self.pos(dma.get_remaining_transfers()); + if self.start == end && dma.get_complete_count() == 0 { + // No bytes are available in the buffer + Ok((0, self.cap())) + } else if self.start < end { + // The available, unread portion in the ring buffer DOES NOT wrap + // Copy out the bytes from the dma buffer + let len = self.copy_to(buf, self.start..end); + + compiler_fence(Ordering::SeqCst); + + /* + first, check if the dma has wrapped at all if it's after end + or more than once if it's before start + + this is in a critical section to try to reduce mushy behavior. + it's not ideal but it's the best we can do + + then, get the current position of of the dma write and check + if it's inside data we could have copied + */ + let (pos, complete_count) = + critical_section::with(|_| (self.pos(dma.get_remaining_transfers()), dma.get_complete_count())); + if (pos >= self.start && pos < end) || (complete_count > 0 && pos >= end) || complete_count > 1 { + Err(OverrunError) + } else { + self.start = (self.start + len) % self.cap(); + + Ok((len, self.cap() - self.start)) + } + } else if self.start + buf.len() < self.cap() { + // The available, unread portion in the ring buffer DOES wrap + // The DMA writer has wrapped since we last read and is currently + // writing (or the next byte added will be) in the beginning of the ring buffer. + + // The provided read buffer is not large enough to include all bytes from the tail of the dma buffer. + + // Copy out from the dma buffer + let len = self.copy_to(buf, self.start..self.cap()); + + compiler_fence(Ordering::SeqCst); + + /* + first, check if the dma has wrapped around more than once + + then, get the current position of of the dma write and check + if it's inside data we could have copied + */ + let pos = self.pos(dma.get_remaining_transfers()); + if pos > self.start || pos < end || dma.get_complete_count() > 1 { + Err(OverrunError) + } else { + self.start = (self.start + len) % self.cap(); + + Ok((len, self.start + end)) + } + } else { + // The available, unread portion in the ring buffer DOES wrap + // The DMA writer has wrapped since we last read and is currently + // writing (or the next byte added will be) in the beginning of the ring buffer. + + // The provided read buffer is large enough to include all bytes from the tail of the dma buffer, + // so the next read will not have any unread tail bytes in the ring buffer. + + // Copy out from the dma buffer + let tail = self.copy_to(buf, self.start..self.cap()); + let head = self.copy_to(&mut buf[tail..], 0..end); + + compiler_fence(Ordering::SeqCst); + + /* + first, check if the dma has wrapped around more than once + + then, get the current position of of the dma write and check + if it's inside data we could have copied + */ + let pos = self.pos(dma.get_remaining_transfers()); + if pos > self.start || pos < end || dma.reset_complete_count() > 1 { + Err(OverrunError) + } else { + self.start = head; + Ok((tail + head, self.cap() - self.start)) + } + } + } + /// Copy from the dma buffer at `data_range` into `buf` + fn copy_to(&mut self, buf: &mut [W], data_range: Range) -> usize { + // Limit the number of bytes that can be copied + let length = usize::min(data_range.len(), buf.len()); + + // Copy from dma buffer into read buffer + // We need to do it like this instead of a simple copy_from_slice() because + // reading from a part of memory that may be simultaneously written to is unsafe + unsafe { + let dma_buf = self.dma_buf.as_ptr(); + + for i in 0..length { + buf[i] = core::ptr::read_volatile(dma_buf.offset((data_range.start + i) as isize)); + } + } + + length + } +} +#[cfg(test)] +mod tests { + use core::array; + use std::{cell, vec}; + + use super::*; + + #[allow(dead_code)] + #[derive(PartialEq, Debug)] + enum TestCircularTransferRequest { + GetCompleteCount(usize), + ResetCompleteCount(usize), + PositionRequest(usize), + } + + struct TestCircularTransfer { + len: usize, + requests: cell::RefCell>, + } + + impl DmaCtrl for &mut TestCircularTransfer { + fn get_remaining_transfers(&self) -> usize { + match self.requests.borrow_mut().pop().unwrap() { + TestCircularTransferRequest::PositionRequest(pos) => { + let len = self.len; + + assert!(len >= pos); + + len - pos + } + _ => unreachable!(), + } + } + + fn get_complete_count(&self) -> usize { + match self.requests.borrow_mut().pop().unwrap() { + TestCircularTransferRequest::GetCompleteCount(complete_count) => complete_count, + _ => unreachable!(), + } + } + + fn reset_complete_count(&mut self) -> usize { + match self.requests.get_mut().pop().unwrap() { + TestCircularTransferRequest::ResetCompleteCount(complete_count) => complete_count, + _ => unreachable!(), + } + } + } + + impl TestCircularTransfer { + pub fn new(len: usize) -> Self { + Self { + requests: cell::RefCell::new(vec![]), + len: len, + } + } + + pub fn setup(&self, mut requests: vec::Vec) { + requests.reverse(); + self.requests.replace(requests); + } + } + + #[test] + fn empty_and_read_not_started() { + let mut dma_buf = [0u8; 16]; + let ringbuf = DmaRingBuffer::new(&mut dma_buf); + + assert_eq!(0, ringbuf.start); + } + + #[test] + fn can_read() { + let mut dma = TestCircularTransfer::new(16); + + let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15 + let mut ringbuf = DmaRingBuffer::new(&mut dma_buf); + + assert_eq!(0, ringbuf.start); + assert_eq!(16, ringbuf.cap()); + + dma.setup(vec![ + TestCircularTransferRequest::PositionRequest(8), + TestCircularTransferRequest::PositionRequest(10), + TestCircularTransferRequest::GetCompleteCount(0), + ]); + let mut buf = [0; 2]; + assert_eq!(2, ringbuf.read(&mut dma, &mut buf).unwrap().0); + assert_eq!([0, 1], buf); + assert_eq!(2, ringbuf.start); + + dma.setup(vec![ + TestCircularTransferRequest::PositionRequest(10), + TestCircularTransferRequest::PositionRequest(12), + TestCircularTransferRequest::GetCompleteCount(0), + ]); + let mut buf = [0; 2]; + assert_eq!(2, ringbuf.read(&mut dma, &mut buf).unwrap().0); + assert_eq!([2, 3], buf); + assert_eq!(4, ringbuf.start); + + dma.setup(vec![ + TestCircularTransferRequest::PositionRequest(12), + TestCircularTransferRequest::PositionRequest(14), + TestCircularTransferRequest::GetCompleteCount(0), + ]); + let mut buf = [0; 8]; + assert_eq!(8, ringbuf.read(&mut dma, &mut buf).unwrap().0); + assert_eq!([4, 5, 6, 7, 8, 9], buf[..6]); + assert_eq!(12, ringbuf.start); + } + + #[test] + fn can_read_with_wrap() { + let mut dma = TestCircularTransfer::new(16); + + let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15 + let mut ringbuf = DmaRingBuffer::new(&mut dma_buf); + + assert_eq!(0, ringbuf.start); + assert_eq!(16, ringbuf.cap()); + + /* + Read to close to the end of the buffer + */ + dma.setup(vec![ + TestCircularTransferRequest::PositionRequest(14), + TestCircularTransferRequest::PositionRequest(16), + TestCircularTransferRequest::GetCompleteCount(0), + ]); + let mut buf = [0; 14]; + assert_eq!(14, ringbuf.read(&mut dma, &mut buf).unwrap().0); + assert_eq!(14, ringbuf.start); + + /* + Now, read around the buffer + */ + dma.setup(vec![ + TestCircularTransferRequest::PositionRequest(6), + TestCircularTransferRequest::PositionRequest(8), + TestCircularTransferRequest::ResetCompleteCount(1), + ]); + let mut buf = [0; 6]; + assert_eq!(6, ringbuf.read(&mut dma, &mut buf).unwrap().0); + assert_eq!(4, ringbuf.start); + } + + #[test] + fn can_read_when_dma_writer_is_wrapped_and_read_does_not_wrap() { + let mut dma = TestCircularTransfer::new(16); + + let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15 + let mut ringbuf = DmaRingBuffer::new(&mut dma_buf); + + assert_eq!(0, ringbuf.start); + assert_eq!(16, ringbuf.cap()); + + /* + Read to close to the end of the buffer + */ + dma.setup(vec![ + TestCircularTransferRequest::PositionRequest(14), + TestCircularTransferRequest::PositionRequest(16), + TestCircularTransferRequest::GetCompleteCount(0), + ]); + let mut buf = [0; 14]; + assert_eq!(14, ringbuf.read(&mut dma, &mut buf).unwrap().0); + assert_eq!(14, ringbuf.start); + + /* + Now, read to the end of the buffer + */ + dma.setup(vec![ + TestCircularTransferRequest::PositionRequest(6), + TestCircularTransferRequest::PositionRequest(8), + TestCircularTransferRequest::ResetCompleteCount(1), + ]); + let mut buf = [0; 2]; + assert_eq!(2, ringbuf.read(&mut dma, &mut buf).unwrap().0); + assert_eq!(0, ringbuf.start); + } + + #[test] + fn can_read_when_dma_writer_wraps_once_with_same_ndtr() { + let mut dma = TestCircularTransfer::new(16); + + let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15 + let mut ringbuf = DmaRingBuffer::new(&mut dma_buf); + + assert_eq!(0, ringbuf.start); + assert_eq!(16, ringbuf.cap()); + + /* + Read to about the middle of the buffer + */ + dma.setup(vec![ + TestCircularTransferRequest::PositionRequest(6), + TestCircularTransferRequest::PositionRequest(6), + TestCircularTransferRequest::GetCompleteCount(0), + ]); + let mut buf = [0; 6]; + assert_eq!(6, ringbuf.read(&mut dma, &mut buf).unwrap().0); + assert_eq!(6, ringbuf.start); + + /* + Now, wrap the DMA controller around + */ + dma.setup(vec![ + TestCircularTransferRequest::PositionRequest(6), + TestCircularTransferRequest::GetCompleteCount(1), + TestCircularTransferRequest::PositionRequest(6), + TestCircularTransferRequest::GetCompleteCount(1), + ]); + let mut buf = [0; 6]; + assert_eq!(6, ringbuf.read(&mut dma, &mut buf).unwrap().0); + assert_eq!(12, ringbuf.start); + } + + #[test] + fn cannot_read_when_dma_writer_overwrites_during_not_wrapping_read() { + let mut dma = TestCircularTransfer::new(16); + + let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15 + let mut ringbuf = DmaRingBuffer::new(&mut dma_buf); + + assert_eq!(0, ringbuf.start); + assert_eq!(16, ringbuf.cap()); + + /* + Read a few bytes + */ + dma.setup(vec![ + TestCircularTransferRequest::PositionRequest(2), + TestCircularTransferRequest::PositionRequest(2), + TestCircularTransferRequest::GetCompleteCount(0), + ]); + let mut buf = [0; 6]; + assert_eq!(2, ringbuf.read(&mut dma, &mut buf).unwrap().0); + assert_eq!(2, ringbuf.start); + + /* + Now, overtake the reader + */ + dma.setup(vec![ + TestCircularTransferRequest::PositionRequest(4), + TestCircularTransferRequest::PositionRequest(6), + TestCircularTransferRequest::GetCompleteCount(1), + ]); + let mut buf = [0; 6]; + assert_eq!(OverrunError, ringbuf.read(&mut dma, &mut buf).unwrap_err()); + } + + #[test] + fn cannot_read_when_dma_writer_overwrites_during_wrapping_read() { + let mut dma = TestCircularTransfer::new(16); + + let mut dma_buf: [u8; 16] = array::from_fn(|idx| idx as u8); // 0, 1, ..., 15 + let mut ringbuf = DmaRingBuffer::new(&mut dma_buf); + + assert_eq!(0, ringbuf.start); + assert_eq!(16, ringbuf.cap()); + + /* + Read to close to the end of the buffer + */ + dma.setup(vec![ + TestCircularTransferRequest::PositionRequest(14), + TestCircularTransferRequest::PositionRequest(16), + TestCircularTransferRequest::GetCompleteCount(0), + ]); + let mut buf = [0; 14]; + assert_eq!(14, ringbuf.read(&mut dma, &mut buf).unwrap().0); + assert_eq!(14, ringbuf.start); + + /* + Now, overtake the reader + */ + dma.setup(vec![ + TestCircularTransferRequest::PositionRequest(8), + TestCircularTransferRequest::PositionRequest(10), + TestCircularTransferRequest::ResetCompleteCount(2), + ]); + let mut buf = [0; 6]; + assert_eq!(OverrunError, ringbuf.read(&mut dma, &mut buf).unwrap_err()); + } +} diff --git a/embassy-stm32/src/dma/word.rs b/embassy-stm32/src/dma/word.rs new file mode 100644 index 000000000..aef6e9700 --- /dev/null +++ b/embassy-stm32/src/dma/word.rs @@ -0,0 +1,79 @@ +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum WordSize { + OneByte, + TwoBytes, + FourBytes, +} + +impl WordSize { + pub fn bytes(&self) -> usize { + match self { + Self::OneByte => 1, + Self::TwoBytes => 2, + Self::FourBytes => 4, + } + } +} + +mod sealed { + pub trait Word {} +} + +pub trait Word: sealed::Word + Default + Copy + 'static { + fn size() -> WordSize; + fn bits() -> usize; +} + +macro_rules! impl_word { + (_, $T:ident, $bits:literal, $size:ident) => { + impl sealed::Word for $T {} + impl Word for $T { + fn bits() -> usize { + $bits + } + fn size() -> WordSize { + WordSize::$size + } + } + }; + ($T:ident, $uX:ident, $bits:literal, $size:ident) => { + #[repr(transparent)] + #[derive(Copy, Clone, Default)] + pub struct $T(pub $uX); + impl_word!(_, $T, $bits, $size); + }; +} + +impl_word!(U1, u8, 1, OneByte); +impl_word!(U2, u8, 2, OneByte); +impl_word!(U3, u8, 3, OneByte); +impl_word!(U4, u8, 4, OneByte); +impl_word!(U5, u8, 5, OneByte); +impl_word!(U6, u8, 6, OneByte); +impl_word!(U7, u8, 7, OneByte); +impl_word!(_, u8, 8, OneByte); +impl_word!(U9, u16, 9, TwoBytes); +impl_word!(U10, u16, 10, TwoBytes); +impl_word!(U11, u16, 11, TwoBytes); +impl_word!(U12, u16, 12, TwoBytes); +impl_word!(U13, u16, 13, TwoBytes); +impl_word!(U14, u16, 14, TwoBytes); +impl_word!(U15, u16, 15, TwoBytes); +impl_word!(_, u16, 16, TwoBytes); +impl_word!(U17, u32, 17, FourBytes); +impl_word!(U18, u32, 18, FourBytes); +impl_word!(U19, u32, 19, FourBytes); +impl_word!(U20, u32, 20, FourBytes); +impl_word!(U21, u32, 21, FourBytes); +impl_word!(U22, u32, 22, FourBytes); +impl_word!(U23, u32, 23, FourBytes); +impl_word!(U24, u32, 24, FourBytes); +impl_word!(U25, u32, 25, FourBytes); +impl_word!(U26, u32, 26, FourBytes); +impl_word!(U27, u32, 27, FourBytes); +impl_word!(U28, u32, 28, FourBytes); +impl_word!(U29, u32, 29, FourBytes); +impl_word!(U30, u32, 30, FourBytes); +impl_word!(U31, u32, 31, FourBytes); +impl_word!(_, u32, 32, FourBytes); diff --git a/embassy-stm32/src/eth/generic_smi.rs b/embassy-stm32/src/eth/generic_smi.rs index 968256046..90631b175 100644 --- a/embassy-stm32/src/eth/generic_smi.rs +++ b/embassy-stm32/src/eth/generic_smi.rs @@ -1,5 +1,11 @@ //! Generic SMI Ethernet PHY +#[cfg(feature = "time")] +use embassy_time::{Duration, Timer}; +use futures::task::Context; +#[cfg(feature = "time")] +use futures::FutureExt; + use super::{StationManagement, PHY}; #[allow(dead_code)] @@ -36,25 +42,48 @@ mod phy_consts { use self::phy_consts::*; /// Generic SMI Ethernet PHY -pub struct GenericSMI; +pub struct GenericSMI { + #[cfg(feature = "time")] + poll_interval: Duration, +} + +impl GenericSMI { + #[cfg(feature = "time")] + pub fn new() -> Self { + Self { + poll_interval: Duration::from_millis(500), + } + } + + #[cfg(not(feature = "time"))] + pub fn new() -> Self { + Self {} + } +} unsafe impl PHY for GenericSMI { /// Reset PHY and wait for it to come out of reset. - fn phy_reset(sm: &mut S) { + fn phy_reset(&mut self, sm: &mut S) { sm.smi_write(PHY_REG_BCR, PHY_REG_BCR_RESET); while sm.smi_read(PHY_REG_BCR) & PHY_REG_BCR_RESET == PHY_REG_BCR_RESET {} } /// PHY initialisation. - fn phy_init(sm: &mut S) { + fn phy_init(&mut self, sm: &mut S) { // Clear WU CSR - Self::smi_write_ext(sm, PHY_REG_WUCSR, 0); + self.smi_write_ext(sm, PHY_REG_WUCSR, 0); // Enable auto-negotiation sm.smi_write(PHY_REG_BCR, PHY_REG_BCR_AN | PHY_REG_BCR_ANRST | PHY_REG_BCR_100M); } - fn poll_link(sm: &mut S) -> bool { + fn poll_link(&mut self, sm: &mut S, cx: &mut Context) -> bool { + #[cfg(not(feature = "time"))] + cx.waker().wake_by_ref(); + + #[cfg(feature = "time")] + let _ = Timer::after(self.poll_interval).poll_unpin(cx); + let bsr = sm.smi_read(PHY_REG_BSR); // No link without autonegotiate @@ -73,8 +102,12 @@ unsafe impl PHY for GenericSMI { /// Public functions for the PHY impl GenericSMI { + pub fn set_poll_interval(&mut self, poll_interval: Duration) { + self.poll_interval = poll_interval + } + // Writes a value to an extended PHY register in MMD address space - fn smi_write_ext(sm: &mut S, reg_addr: u16, reg_data: u16) { + fn smi_write_ext(&mut self, sm: &mut S, reg_addr: u16, reg_data: u16) { sm.smi_write(PHY_REG_CTL, 0x0003); // set address sm.smi_write(PHY_REG_ADDAR, reg_addr); sm.smi_write(PHY_REG_CTL, 0x4003); // set data diff --git a/embassy-stm32/src/eth/mod.rs b/embassy-stm32/src/eth/mod.rs index 76a3dfab4..1687cb319 100644 --- a/embassy-stm32/src/eth/mod.rs +++ b/embassy-stm32/src/eth/mod.rs @@ -1,13 +1,131 @@ #![macro_use] -#[cfg(feature = "net")] #[cfg_attr(any(eth_v1a, eth_v1b, eth_v1c), path = "v1/mod.rs")] #[cfg_attr(eth_v2, path = "v2/mod.rs")] mod _version; pub mod generic_smi; -#[cfg(feature = "net")] -pub use _version::*; +use core::mem::MaybeUninit; +use core::task::Context; + +use embassy_net_driver::{Capabilities, LinkState}; +use embassy_sync::waitqueue::AtomicWaker; + +pub use self::_version::{InterruptHandler, *}; + +#[allow(unused)] +const MTU: usize = 1514; +const TX_BUFFER_SIZE: usize = 1514; +const RX_BUFFER_SIZE: usize = 1536; + +#[repr(C, align(8))] +#[derive(Copy, Clone)] +pub(crate) struct Packet([u8; N]); + +pub struct PacketQueue { + tx_desc: [TDes; TX], + rx_desc: [RDes; RX], + tx_buf: [Packet; TX], + rx_buf: [Packet; RX], +} + +impl PacketQueue { + pub const fn new() -> Self { + const NEW_TDES: TDes = TDes::new(); + const NEW_RDES: RDes = RDes::new(); + Self { + tx_desc: [NEW_TDES; TX], + rx_desc: [NEW_RDES; RX], + tx_buf: [Packet([0; TX_BUFFER_SIZE]); TX], + rx_buf: [Packet([0; RX_BUFFER_SIZE]); RX], + } + } + + // Allow to initialize a Self without requiring it to go on the stack + pub fn init(this: &mut MaybeUninit) { + unsafe { + this.as_mut_ptr().write_bytes(0u8, 1); + } + } +} + +static WAKER: AtomicWaker = AtomicWaker::new(); + +impl<'d, T: Instance, P: PHY> embassy_net_driver::Driver for Ethernet<'d, T, P> { + type RxToken<'a> = RxToken<'a, 'd> where Self: 'a; + type TxToken<'a> = TxToken<'a, 'd> where Self: 'a; + + fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + WAKER.register(cx.waker()); + if self.rx.available().is_some() && self.tx.available().is_some() { + Some((RxToken { rx: &mut self.rx }, TxToken { tx: &mut self.tx })) + } else { + None + } + } + + fn transmit(&mut self, cx: &mut Context) -> Option> { + WAKER.register(cx.waker()); + if self.tx.available().is_some() { + Some(TxToken { tx: &mut self.tx }) + } else { + None + } + } + + fn capabilities(&self) -> Capabilities { + let mut caps = Capabilities::default(); + caps.max_transmission_unit = MTU; + caps.max_burst_size = Some(self.tx.len()); + caps + } + + fn link_state(&mut self, cx: &mut Context) -> LinkState { + if self.phy.poll_link(&mut self.station_management, cx) { + LinkState::Up + } else { + LinkState::Down + } + } + + fn ethernet_address(&self) -> [u8; 6] { + self.mac_addr + } +} + +pub struct RxToken<'a, 'd> { + rx: &'a mut RDesRing<'d>, +} + +impl<'a, 'd> embassy_net_driver::RxToken for RxToken<'a, 'd> { + fn consume(self, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + // NOTE(unwrap): we checked the queue wasn't full when creating the token. + let pkt = unwrap!(self.rx.available()); + let r = f(pkt); + self.rx.pop_packet(); + r + } +} + +pub struct TxToken<'a, 'd> { + tx: &'a mut TDesRing<'d>, +} + +impl<'a, 'd> embassy_net_driver::TxToken for TxToken<'a, 'd> { + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + // NOTE(unwrap): we checked the queue wasn't full when creating the token. + let pkt = unwrap!(self.tx.available()); + let r = f(&mut pkt[..len]); + self.tx.transmit(len); + r + } +} /// Station Management Interface (SMI) on an ethernet PHY /// @@ -28,11 +146,11 @@ pub unsafe trait StationManagement { /// The methods cannot move S pub unsafe trait PHY { /// Reset PHY and wait for it to come out of reset. - fn phy_reset(sm: &mut S); + fn phy_reset(&mut self, sm: &mut S); /// PHY initialisation. - fn phy_init(sm: &mut S); + fn phy_init(&mut self, sm: &mut S); /// Poll link to see if it is up and FD with 100Mbps - fn poll_link(sm: &mut S) -> bool; + fn poll_link(&mut self, sm: &mut S, cx: &mut Context) -> bool; } pub(crate) mod sealed { diff --git a/embassy-stm32/src/eth/v1/descriptors.rs b/embassy-stm32/src/eth/v1/descriptors.rs deleted file mode 100644 index 25f21ce19..000000000 --- a/embassy-stm32/src/eth/v1/descriptors.rs +++ /dev/null @@ -1,21 +0,0 @@ -use crate::eth::_version::rx_desc::RDesRing; -use crate::eth::_version::tx_desc::TDesRing; - -pub struct DescriptorRing { - pub(crate) tx: TDesRing, - pub(crate) rx: RDesRing, -} - -impl DescriptorRing { - pub const fn new() -> Self { - Self { - tx: TDesRing::new(), - rx: RDesRing::new(), - } - } - - pub fn init(&mut self) { - self.tx.init(); - self.rx.init(); - } -} diff --git a/embassy-stm32/src/eth/v1/mod.rs b/embassy-stm32/src/eth/v1/mod.rs index 1ab0438ad..2a6ea35ff 100644 --- a/embassy-stm32/src/eth/v1/mod.rs +++ b/embassy-stm32/src/eth/v1/mod.rs @@ -1,52 +1,62 @@ // The v1c ethernet driver was ported to embassy from the awesome stm32-eth project (https://github.com/stm32-rs/stm32-eth). +mod rx_desc; +mod tx_desc; + use core::marker::PhantomData; use core::sync::atomic::{fence, Ordering}; -use core::task::Waker; -use embassy_cortex_m::peripheral::{PeripheralMutex, PeripheralState, StateStorage}; use embassy_hal_common::{into_ref, PeripheralRef}; -use embassy_net::{Device, DeviceCapabilities, LinkState, PacketBuf, MTU}; -use embassy_sync::waitqueue::AtomicWaker; +use stm32_metapac::eth::vals::{Apcs, Cr, Dm, DmaomrSr, Fes, Ftf, Ifg, MbProgress, Mw, Pbl, Rsf, St, Tsf}; +pub(crate) use self::rx_desc::{RDes, RDesRing}; +pub(crate) use self::tx_desc::{TDes, TDesRing}; +use super::*; use crate::gpio::sealed::{AFType, Pin as __GpioPin}; -use crate::gpio::{AnyPin, Speed}; +use crate::gpio::AnyPin; +use crate::interrupt::InterruptExt; #[cfg(eth_v1a)] use crate::pac::AFIO; #[cfg(any(eth_v1b, eth_v1c))] use crate::pac::SYSCFG; use crate::pac::{ETH, RCC}; -use crate::Peripheral; +use crate::{interrupt, Peripheral}; -mod descriptors; -mod rx_desc; -mod tx_desc; +/// Interrupt handler. +pub struct InterruptHandler {} -use descriptors::DescriptorRing; -use stm32_metapac::eth::vals::{Apcs, Cr, Dm, DmaomrSr, Fes, Ftf, Ifg, MbProgress, Mw, Pbl, Rsf, St, Tsf}; +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + WAKER.wake(); -use super::*; + // TODO: Check and clear more flags + let dma = ETH.ethernet_dma(); -pub struct State<'d, T: Instance, const TX: usize, const RX: usize>(StateStorage>); -impl<'d, T: Instance, const TX: usize, const RX: usize> State<'d, T, TX, RX> { - pub fn new() -> Self { - Self(StateStorage::new()) + dma.dmasr().modify(|w| { + w.set_ts(true); + w.set_rs(true); + w.set_nis(true); + }); + // Delay two peripheral's clock + dma.dmasr().read(); + dma.dmasr().read(); } } -pub struct Ethernet<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> { - state: PeripheralMutex<'d, Inner<'d, T, TX, RX>>, +pub struct Ethernet<'d, T: Instance, P: PHY> { + _peri: PeripheralRef<'d, T>, + pub(crate) tx: TDesRing<'d>, + pub(crate) rx: RDesRing<'d>, + pins: [PeripheralRef<'d, AnyPin>; 9], - _phy: P, - clock_range: Cr, - phy_addr: u8, - mac_addr: [u8; 6], + pub(crate) phy: P, + pub(crate) station_management: EthernetStationManagement, + pub(crate) mac_addr: [u8; 6], } #[cfg(eth_v1a)] macro_rules! config_in_pins { ($($pin:ident),*) => { - // NOTE(unsafe) Exclusive access to the registers critical_section::with(|_| { $( // TODO properly create a set_as_input function @@ -59,7 +69,6 @@ macro_rules! config_in_pins { #[cfg(eth_v1a)] macro_rules! config_af_pins { ($($pin:ident),*) => { - // NOTE(unsafe) Exclusive access to the registers critical_section::with(|_| { $( // We are lucky here, this configures to max speed (50MHz) @@ -72,22 +81,21 @@ macro_rules! config_af_pins { #[cfg(any(eth_v1b, eth_v1c))] macro_rules! config_pins { ($($pin:ident),*) => { - // NOTE(unsafe) Exclusive access to the registers critical_section::with(|_| { $( $pin.set_as_af($pin.af_num(), AFType::OutputPushPull); - $pin.set_speed(Speed::VeryHigh); + $pin.set_speed(crate::gpio::Speed::VeryHigh); )* }) }; } -impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Ethernet<'d, T, P, TX, RX> { +impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> { /// safety: the returned instance is not leak-safe - pub unsafe fn new( - state: &'d mut State<'d, T, TX, RX>, + pub fn new( + queue: &'d mut PacketQueue, peri: impl Peripheral

+ 'd, - interrupt: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding + 'd, ref_clk: impl Peripheral

> + 'd, mdio: impl Peripheral

> + 'd, mdc: impl Peripheral

> + 'd, @@ -101,10 +109,9 @@ impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Ethernet<'d, T, mac_addr: [u8; 6], phy_addr: u8, ) -> Self { - into_ref!(interrupt, ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en); + into_ref!(peri, ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en); // Enable the necessary Clocks - // NOTE(unsafe) We have exclusive access to the registers #[cfg(eth_v1a)] critical_section::with(|_| { RCC.apb2enr().modify(|w| w.set_afioen(true)); @@ -142,10 +149,6 @@ impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Ethernet<'d, T, #[cfg(any(eth_v1b, eth_v1c))] config_pins!(ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en); - // NOTE(unsafe) We are ourselves not leak-safe. - let state = PeripheralMutex::new(interrupt, &mut state.0, || Inner::new(peri)); - - // NOTE(unsafe) We have exclusive access to the registers let dma = ETH.ethernet_dma(); let mac = ETH.ethernet_mac(); @@ -190,7 +193,7 @@ impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Ethernet<'d, T, // TODO MTU size setting not found for v1 ethernet, check if correct // NOTE(unsafe) We got the peripheral singleton, which means that `rcc::init` was called - let hclk = crate::rcc::get_freqs().ahb1; + let hclk = unsafe { crate::rcc::get_freqs() }.ahb1; let hclk_mhz = hclk.0 / 1_000_000; // Set the MDC clock frequency in the range 1MHz - 2.5MHz @@ -219,188 +222,109 @@ impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Ethernet<'d, T, ]; let mut this = Self { - state, + _peri: peri, pins, - _phy: phy, - clock_range, - phy_addr, + phy: phy, + station_management: EthernetStationManagement { + peri: PhantomData, + clock_range: clock_range, + phy_addr: phy_addr, + }, mac_addr, + tx: TDesRing::new(&mut queue.tx_desc, &mut queue.tx_buf), + rx: RDesRing::new(&mut queue.rx_desc, &mut queue.rx_buf), }; - this.state.with(|s| { - s.desc_ring.init(); + fence(Ordering::SeqCst); - fence(Ordering::SeqCst); + let mac = ETH.ethernet_mac(); + let dma = ETH.ethernet_dma(); - let mac = ETH.ethernet_mac(); - let dma = ETH.ethernet_dma(); - - mac.maccr().modify(|w| { - w.set_re(true); - w.set_te(true); - }); - dma.dmaomr().modify(|w| { - w.set_ftf(Ftf::FLUSH); // flush transmit fifo (queue) - w.set_st(St::STARTED); // start transmitting channel - w.set_sr(DmaomrSr::STARTED); // start receiving channel - }); - - // Enable interrupts - dma.dmaier().modify(|w| { - w.set_nise(true); - w.set_rie(true); - w.set_tie(true); - }); + mac.maccr().modify(|w| { + w.set_re(true); + w.set_te(true); }); - P::phy_reset(&mut this); - P::phy_init(&mut this); + dma.dmaomr().modify(|w| { + w.set_ftf(Ftf::FLUSH); // flush transmit fifo (queue) + w.set_st(St::STARTED); // start transmitting channel + w.set_sr(DmaomrSr::STARTED); // start receiving channel + }); + + this.rx.demand_poll(); + + // Enable interrupts + dma.dmaier().modify(|w| { + w.set_nise(true); + w.set_rie(true); + w.set_tie(true); + }); + + this.phy.phy_reset(&mut this.station_management); + this.phy.phy_init(&mut this.station_management); + + interrupt::ETH.unpend(); + unsafe { interrupt::ETH.enable() }; this } } -unsafe impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> StationManagement - for Ethernet<'d, T, P, TX, RX> -{ - fn smi_read(&mut self, reg: u8) -> u16 { - // NOTE(unsafe) These registers aren't used in the interrupt and we have `&mut self` - unsafe { - let mac = ETH.ethernet_mac(); +pub struct EthernetStationManagement { + peri: PhantomData, + clock_range: Cr, + phy_addr: u8, +} - mac.macmiiar().modify(|w| { - w.set_pa(self.phy_addr); - w.set_mr(reg); - w.set_mw(Mw::READ); // read operation - w.set_cr(self.clock_range); - w.set_mb(MbProgress::BUSY); // indicate that operation is in progress - }); - while mac.macmiiar().read().mb() == MbProgress::BUSY {} - mac.macmiidr().read().md() - } +unsafe impl StationManagement for EthernetStationManagement { + fn smi_read(&mut self, reg: u8) -> u16 { + let mac = ETH.ethernet_mac(); + + mac.macmiiar().modify(|w| { + w.set_pa(self.phy_addr); + w.set_mr(reg); + w.set_mw(Mw::READ); // read operation + w.set_cr(self.clock_range); + w.set_mb(MbProgress::BUSY); // indicate that operation is in progress + }); + while mac.macmiiar().read().mb() == MbProgress::BUSY {} + mac.macmiidr().read().md() } fn smi_write(&mut self, reg: u8, val: u16) { - // NOTE(unsafe) These registers aren't used in the interrupt and we have `&mut self` - unsafe { - let mac = ETH.ethernet_mac(); + let mac = ETH.ethernet_mac(); - mac.macmiidr().write(|w| w.set_md(val)); - mac.macmiiar().modify(|w| { - w.set_pa(self.phy_addr); - w.set_mr(reg); - w.set_mw(Mw::WRITE); // write - w.set_cr(self.clock_range); - w.set_mb(MbProgress::BUSY); - }); - while mac.macmiiar().read().mb() == MbProgress::BUSY {} - } + mac.macmiidr().write(|w| w.set_md(val)); + mac.macmiiar().modify(|w| { + w.set_pa(self.phy_addr); + w.set_mr(reg); + w.set_mw(Mw::WRITE); // write + w.set_cr(self.clock_range); + w.set_mb(MbProgress::BUSY); + }); + while mac.macmiiar().read().mb() == MbProgress::BUSY {} } } -impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Device for Ethernet<'d, T, P, TX, RX> { - fn is_transmit_ready(&mut self) -> bool { - self.state.with(|s| s.desc_ring.tx.available()) - } - - fn transmit(&mut self, pkt: PacketBuf) { - self.state.with(|s| unwrap!(s.desc_ring.tx.transmit(pkt))); - } - - fn receive(&mut self) -> Option { - self.state.with(|s| s.desc_ring.rx.pop_packet()) - } - - fn register_waker(&mut self, waker: &Waker) { - WAKER.register(waker); - } - - fn capabilities(&self) -> DeviceCapabilities { - let mut caps = DeviceCapabilities::default(); - caps.max_transmission_unit = MTU; - caps.max_burst_size = Some(TX.min(RX)); - caps - } - - fn link_state(&mut self) -> LinkState { - if P::poll_link(self) { - LinkState::Up - } else { - LinkState::Down - } - } - - fn ethernet_address(&self) -> [u8; 6] { - self.mac_addr - } -} - -impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Drop for Ethernet<'d, T, P, TX, RX> { +impl<'d, T: Instance, P: PHY> Drop for Ethernet<'d, T, P> { fn drop(&mut self) { - // NOTE(unsafe) We have `&mut self` and the interrupt doesn't use this registers - unsafe { - let dma = ETH.ethernet_dma(); - let mac = ETH.ethernet_mac(); + let dma = ETH.ethernet_dma(); + let mac = ETH.ethernet_mac(); - // Disable the TX DMA and wait for any previous transmissions to be completed - dma.dmaomr().modify(|w| w.set_st(St::STOPPED)); + // Disable the TX DMA and wait for any previous transmissions to be completed + dma.dmaomr().modify(|w| w.set_st(St::STOPPED)); - // Disable MAC transmitter and receiver - mac.maccr().modify(|w| { - w.set_re(false); - w.set_te(false); - }); + // Disable MAC transmitter and receiver + mac.maccr().modify(|w| { + w.set_re(false); + w.set_te(false); + }); - dma.dmaomr().modify(|w| w.set_sr(DmaomrSr::STOPPED)); - } + dma.dmaomr().modify(|w| w.set_sr(DmaomrSr::STOPPED)); - // NOTE(unsafe) Exclusive access to the regs - critical_section::with(|_| unsafe { + critical_section::with(|_| { for pin in self.pins.iter_mut() { pin.set_as_disconnected(); } }) } } - -//---------------------------------------------------------------------- - -struct Inner<'d, T: Instance, const TX: usize, const RX: usize> { - _peri: PhantomData<&'d mut T>, - desc_ring: DescriptorRing, -} - -impl<'d, T: Instance, const TX: usize, const RX: usize> Inner<'d, T, TX, RX> { - pub fn new(_peri: impl Peripheral

+ 'd) -> Self { - Self { - _peri: PhantomData, - desc_ring: DescriptorRing::new(), - } - } -} - -impl<'d, T: Instance, const TX: usize, const RX: usize> PeripheralState for Inner<'d, T, TX, RX> { - type Interrupt = crate::interrupt::ETH; - - fn on_interrupt(&mut self) { - unwrap!(self.desc_ring.tx.on_interrupt()); - self.desc_ring.rx.on_interrupt(); - - WAKER.wake(); - - // TODO: Check and clear more flags - unsafe { - let dma = ETH.ethernet_dma(); - - dma.dmasr().modify(|w| { - w.set_ts(true); - w.set_rs(true); - w.set_nis(true); - }); - // Delay two peripheral's clock - dma.dmasr().read(); - dma.dmasr().read(); - } - } -} - -static WAKER: AtomicWaker = AtomicWaker::new(); diff --git a/embassy-stm32/src/eth/v1/rx_desc.rs b/embassy-stm32/src/eth/v1/rx_desc.rs index d482590a1..668378bea 100644 --- a/embassy-stm32/src/eth/v1/rx_desc.rs +++ b/embassy-stm32/src/eth/v1/rx_desc.rs @@ -1,9 +1,9 @@ use core::sync::atomic::{compiler_fence, fence, Ordering}; -use embassy_net::{Packet, PacketBox, PacketBoxExt, PacketBuf}; -use stm32_metapac::eth::vals::{DmaomrSr, Rpd, Rps}; +use stm32_metapac::eth::vals::{Rpd, Rps}; use vcell::VolatileCell; +use crate::eth::RX_BUFFER_SIZE; use crate::pac::ETH; mod rx_consts { @@ -28,6 +28,8 @@ mod rx_consts { use rx_consts::*; +use super::Packet; + /// Receive Descriptor representation /// /// * rdes0: OWN and Status @@ -35,7 +37,7 @@ use rx_consts::*; /// * rdes2: data buffer address /// * rdes3: next descriptor address #[repr(C)] -struct RDes { +pub(crate) struct RDes { rdes0: VolatileCell, rdes1: VolatileCell, rdes2: VolatileCell, @@ -54,7 +56,7 @@ impl RDes { /// Return true if this RDes is acceptable to us #[inline(always)] - pub fn valid(&self) -> bool { + fn valid(&self) -> bool { // Write-back descriptor is valid if: // // Contains first buffer of packet AND contains last buf of @@ -64,15 +66,16 @@ impl RDes { /// Return true if this RDes is not currently owned by the DMA #[inline(always)] - pub fn available(&self) -> bool { + fn available(&self) -> bool { self.rdes0.get() & RXDESC_0_OWN == 0 // Owned by us } /// Configures the reception buffer address and length and passed descriptor ownership to the DMA #[inline(always)] - pub fn set_ready(&mut self, buf_addr: u32, buf_len: usize) { - self.rdes1.set(self.rdes1.get() | (buf_len as u32) & RXDESC_1_RBS_MASK); - self.rdes2.set(buf_addr); + fn set_ready(&self, buf: *mut u8) { + self.rdes1 + .set(self.rdes1.get() | (RX_BUFFER_SIZE as u32) & RXDESC_1_RBS_MASK); + self.rdes2.set(buf as u32); // "Preceding reads and writes cannot be moved past subsequent writes." fence(Ordering::Release); @@ -88,12 +91,12 @@ impl RDes { // points to next descriptor (RCH) #[inline(always)] - fn set_buffer2(&mut self, buffer: *const u8) { + fn set_buffer2(&self, buffer: *const u8) { self.rdes3.set(buffer as u32); } #[inline(always)] - fn set_end_of_ring(&mut self) { + fn set_end_of_ring(&self) { self.rdes1.set(self.rdes1.get() | RXDESC_1_RER); } @@ -102,7 +105,7 @@ impl RDes { ((self.rdes0.get() >> RXDESC_0_FL_SHIFT) & RXDESC_0_FL_MASK) as usize } - pub fn setup(&mut self, next: Option<&Self>) { + fn setup(&self, next: Option<&Self>, buf: *mut u8) { // Defer this initialization to this function, so we can have `RingEntry` on bss. self.rdes1.set(self.rdes1.get() | RXDESC_1_RCH); @@ -113,8 +116,11 @@ impl RDes { self.set_end_of_ring(); } } + + self.set_ready(buf); } } + /// Running state of the `RxRing` #[derive(PartialEq, Eq, Debug)] pub enum RunningState { @@ -123,119 +129,42 @@ pub enum RunningState { Running, } -impl RunningState { - /// whether self equals to `RunningState::Running` - pub fn is_running(&self) -> bool { - *self == RunningState::Running - } -} - /// Rx ring of descriptors and packets -/// -/// This ring has three major locations that work in lock-step. The DMA will never write to the tail -/// index, so the `read_index` must never pass the tail index. The `next_tail_index` is always 1 -/// slot ahead of the real tail index, and it must never pass the `read_index` or it could overwrite -/// a packet still to be passed to the application. -/// -/// nt can't pass r (no alloc) -/// +---+---+---+---+ Read ok +---+---+---+---+ No Read +---+---+---+---+ -/// | | | | | ------------> | | | | | ------------> | | | | | -/// +---+---+---+---+ Allocation ok +---+---+---+---+ +---+---+---+---+ -/// ^ ^t ^t ^ ^t ^ -/// |r |r |r -/// |nt |nt |nt -/// -/// -/// +---+---+---+---+ Read ok +---+---+---+---+ Can't read +---+---+---+---+ -/// | | | | | ------------> | | | | | ------------> | | | | | -/// +---+---+---+---+ Allocation fail +---+---+---+---+ Allocation ok +---+---+---+---+ -/// ^ ^t ^ ^t ^ ^ ^ ^t -/// |r | |r | | |r -/// |nt |nt |nt -/// -pub(crate) struct RDesRing { - descriptors: [RDes; N], - buffers: [Option; N], - read_index: usize, - next_tail_index: usize, +pub(crate) struct RDesRing<'a> { + descriptors: &'a mut [RDes], + buffers: &'a mut [Packet], + index: usize, } -impl RDesRing { - pub const fn new() -> Self { - const RDES: RDes = RDes::new(); - const BUFFERS: Option = None; +impl<'a> RDesRing<'a> { + pub(crate) fn new(descriptors: &'a mut [RDes], buffers: &'a mut [Packet]) -> Self { + assert!(descriptors.len() > 1); + assert!(descriptors.len() == buffers.len()); - Self { - descriptors: [RDES; N], - buffers: [BUFFERS; N], - read_index: 0, - next_tail_index: 0, - } - } - - pub(crate) fn init(&mut self) { - assert!(N > 1); - let mut last_index = 0; - for (index, buf) in self.buffers.iter_mut().enumerate() { - let pkt = match PacketBox::new(Packet::new()) { - Some(p) => p, - None => { - if index == 0 { - panic!("Could not allocate at least one buffer for Ethernet receiving"); - } else { - break; - } - } - }; - self.descriptors[index].set_ready(pkt.as_ptr() as u32, pkt.len()); - *buf = Some(pkt); - last_index = index; - } - self.next_tail_index = (last_index + 1) % N; - - // not sure if this is supposed to span all of the descriptor or just those that contain buffers - { - let mut previous: Option<&mut RDes> = None; - for entry in self.descriptors.iter_mut() { - if let Some(prev) = &mut previous { - prev.setup(Some(entry)); - } - previous = Some(entry); - } - - if let Some(entry) = &mut previous { - entry.setup(None); - } + for (i, entry) in descriptors.iter().enumerate() { + entry.setup(descriptors.get(i + 1), buffers[i].0.as_mut_ptr()); } - // Register txdescriptor start - // NOTE (unsafe) Used for atomic writes - unsafe { - ETH.ethernet_dma() - .dmardlar() - .write(|w| w.0 = &self.descriptors as *const _ as u32); - }; + // Register rx descriptor start + ETH.ethernet_dma() + .dmardlar() + .write(|w| w.0 = descriptors.as_ptr() as u32); // We already have fences in `set_owned`, which is called in `setup` - // Start receive - unsafe { ETH.ethernet_dma().dmaomr().modify(|w| w.set_sr(DmaomrSr::STARTED)) }; - - self.demand_poll(); + Self { + descriptors, + buffers, + index: 0, + } } - fn demand_poll(&self) { - unsafe { ETH.ethernet_dma().dmarpdr().write(|w| w.set_rpd(Rpd::POLL)) }; - } - - pub(crate) fn on_interrupt(&mut self) { - // XXX: Do we need to do anything here ? Maybe we should try to advance the tail ptr, but it - // would soon hit the read ptr anyway, and we will wake smoltcp's stack on the interrupt - // which should try to pop a packet... + pub(crate) fn demand_poll(&self) { + ETH.ethernet_dma().dmarpdr().write(|w| w.set_rpd(Rpd::POLL)); } /// Get current `RunningState` fn running_state(&self) -> RunningState { - match unsafe { ETH.ethernet_dma().dmasr().read().rps() } { + match ETH.ethernet_dma().dmasr().read().rps() { // Reset or Stop Receive Command issued Rps::STOPPED => RunningState::Stopped, // Fetching receive transfer descriptor @@ -245,59 +174,59 @@ impl RDesRing { // Receive descriptor unavailable Rps::SUSPENDED => RunningState::Stopped, // Closing receive descriptor - Rps(0b101) => RunningState::Running, + Rps::_RESERVED_5 => RunningState::Running, // Transferring the receive packet data from receive buffer to host memory Rps::RUNNINGWRITING => RunningState::Running, _ => RunningState::Unknown, } } - pub(crate) fn pop_packet(&mut self) -> Option { - if !self.running_state().is_running() { + /// Get a received packet if any, or None. + pub(crate) fn available(&mut self) -> Option<&mut [u8]> { + if self.running_state() != RunningState::Running { self.demand_poll(); } + // Not sure if the contents of the write buffer on the M7 can affects reads, so we are using // a DMB here just in case, it also serves as a hint to the compiler that we're syncing the // buffer (I think .-.) fence(Ordering::SeqCst); - let read_available = self.descriptors[self.read_index].available(); - let tail_index = (self.next_tail_index + N - 1) % N; - - let pkt = if read_available && self.read_index != tail_index { - let pkt = self.buffers[self.read_index].take(); - let len = self.descriptors[self.read_index].packet_len(); - - assert!(pkt.is_some()); - let valid = self.descriptors[self.read_index].valid(); - - self.read_index = (self.read_index + 1) % N; - if valid { - pkt.map(|p| p.slice(0..len)) - } else { - None + // We might have to process many packets, in case some have been rx'd but are invalid. + loop { + let descriptor = &mut self.descriptors[self.index]; + if !descriptor.available() { + return None; } - } else { - None - }; - // Try to advance the tail_index - if self.next_tail_index != self.read_index { - match PacketBox::new(Packet::new()) { - Some(b) => { - let addr = b.as_ptr() as u32; - let buffer_len = b.len(); - self.buffers[self.next_tail_index].replace(b); - self.descriptors[self.next_tail_index].set_ready(addr, buffer_len); - - // "Preceding reads and writes cannot be moved past subsequent writes." - fence(Ordering::Release); - - self.next_tail_index = (self.next_tail_index + 1) % N; - } - None => {} + // If packet is invalid, pop it and try again. + if !descriptor.valid() { + warn!("invalid packet: {:08x}", descriptor.rdes0.get()); + self.pop_packet(); + continue; } + + break; + } + + let descriptor = &mut self.descriptors[self.index]; + let len = descriptor.packet_len(); + return Some(&mut self.buffers[self.index].0[..len]); + } + + /// Pop the packet previously returned by `available`. + pub(crate) fn pop_packet(&mut self) { + let descriptor = &mut self.descriptors[self.index]; + assert!(descriptor.available()); + + self.descriptors[self.index].set_ready(self.buffers[self.index].0.as_mut_ptr()); + + self.demand_poll(); + + // Increment index. + self.index += 1; + if self.index == self.descriptors.len() { + self.index = 0 } - pkt } } diff --git a/embassy-stm32/src/eth/v1/tx_desc.rs b/embassy-stm32/src/eth/v1/tx_desc.rs index f2889b550..1317d20f4 100644 --- a/embassy-stm32/src/eth/v1/tx_desc.rs +++ b/embassy-stm32/src/eth/v1/tx_desc.rs @@ -1,20 +1,10 @@ use core::sync::atomic::{compiler_fence, fence, Ordering}; -use embassy_net::PacketBuf; -use stm32_metapac::eth::vals::St; use vcell::VolatileCell; +use crate::eth::TX_BUFFER_SIZE; use crate::pac::ETH; -#[non_exhaustive] -#[derive(Debug, Copy, Clone)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Error { - NoBufferAvailable, - // TODO: Break down this error into several others - TransmissionError, -} - /// Transmit and Receive Descriptor fields #[allow(dead_code)] mod tx_consts { @@ -37,6 +27,8 @@ mod tx_consts { } use tx_consts::*; +use super::Packet; + /// Transmit Descriptor representation /// /// * tdes0: control @@ -44,7 +36,7 @@ use tx_consts::*; /// * tdes2: data buffer address /// * tdes3: next descriptor address #[repr(C)] -struct TDes { +pub(crate) struct TDes { tdes0: VolatileCell, tdes1: VolatileCell, tdes2: VolatileCell, @@ -62,7 +54,7 @@ impl TDes { } /// Return true if this TDes is not currently owned by the DMA - pub fn available(&self) -> bool { + fn available(&self) -> bool { (self.tdes0.get() & TXDESC_0_OWN) == 0 } @@ -79,26 +71,26 @@ impl TDes { fence(Ordering::SeqCst); } - fn set_buffer1(&mut self, buffer: *const u8) { + fn set_buffer1(&self, buffer: *const u8) { self.tdes2.set(buffer as u32); } - fn set_buffer1_len(&mut self, len: usize) { + fn set_buffer1_len(&self, len: usize) { self.tdes1 .set((self.tdes1.get() & !TXDESC_1_TBS_MASK) | ((len as u32) << TXDESC_1_TBS_SHIFT)); } // points to next descriptor (RCH) - fn set_buffer2(&mut self, buffer: *const u8) { + fn set_buffer2(&self, buffer: *const u8) { self.tdes3.set(buffer as u32); } - fn set_end_of_ring(&mut self) { + fn set_end_of_ring(&self) { self.tdes0.set(self.tdes0.get() | TXDESC_0_TER); } // set up as a part fo the ring buffer - configures the tdes - pub fn setup(&mut self, next: Option<&Self>) { + fn setup(&self, next: Option<&Self>) { // Defer this initialization to this function, so we can have `RingEntry` on bss. self.tdes0.set(TXDESC_0_TCH | TXDESC_0_IOC | TXDESC_0_FS | TXDESC_0_LS); match next { @@ -111,85 +103,55 @@ impl TDes { } } -pub(crate) struct TDesRing { - descriptors: [TDes; N], - buffers: [Option; N], - next_entry: usize, +pub(crate) struct TDesRing<'a> { + descriptors: &'a mut [TDes], + buffers: &'a mut [Packet], + index: usize, } -impl TDesRing { - pub const fn new() -> Self { - const TDES: TDes = TDes::new(); - const BUFFERS: Option = None; - - Self { - descriptors: [TDES; N], - buffers: [BUFFERS; N], - next_entry: 0, - } - } - +impl<'a> TDesRing<'a> { /// Initialise this TDesRing. Assume TDesRing is corrupt - /// - /// The current memory address of the buffers inside this TDesRing - /// will be stored in the descriptors, so ensure the TDesRing is - /// not moved after initialisation. - pub(crate) fn init(&mut self) { - assert!(N > 0); + pub(crate) fn new(descriptors: &'a mut [TDes], buffers: &'a mut [Packet]) -> Self { + assert!(descriptors.len() > 0); + assert!(descriptors.len() == buffers.len()); - { - let mut previous: Option<&mut TDes> = None; - for entry in self.descriptors.iter_mut() { - if let Some(prev) = &mut previous { - prev.setup(Some(entry)); - } - previous = Some(entry); - } - - if let Some(entry) = &mut previous { - entry.setup(None); - } + for (i, entry) in descriptors.iter().enumerate() { + entry.setup(descriptors.get(i + 1)); } - self.next_entry = 0; // Register txdescriptor start - // NOTE (unsafe) Used for atomic writes - unsafe { - ETH.ethernet_dma() - .dmatdlar() - .write(|w| w.0 = &self.descriptors as *const _ as u32); + ETH.ethernet_dma() + .dmatdlar() + .write(|w| w.0 = descriptors.as_ptr() as u32); + + Self { + descriptors, + buffers, + index: 0, } - - // "Preceding reads and writes cannot be moved past subsequent writes." - #[cfg(feature = "fence")] - fence(Ordering::Release); - - // We don't need a compiler fence here because all interactions with `Descriptor` are - // volatiles - - // Start transmission - unsafe { ETH.ethernet_dma().dmaomr().modify(|w| w.set_st(St::STARTED)) }; } - /// Return true if a TDes is available for use - pub(crate) fn available(&self) -> bool { - self.descriptors[self.next_entry].available() + pub(crate) fn len(&self) -> usize { + self.descriptors.len() } - pub(crate) fn transmit(&mut self, pkt: PacketBuf) -> Result<(), Error> { - if !self.available() { - return Err(Error::NoBufferAvailable); + /// Return the next available packet buffer for transmitting, or None + pub(crate) fn available(&mut self) -> Option<&mut [u8]> { + let descriptor = &mut self.descriptors[self.index]; + if descriptor.available() { + Some(&mut self.buffers[self.index].0) + } else { + None } + } - let descriptor = &mut self.descriptors[self.next_entry]; + /// Transmit the packet written in a buffer returned by `available`. + pub(crate) fn transmit(&mut self, len: usize) { + let descriptor = &mut self.descriptors[self.index]; + assert!(descriptor.available()); - let pkt_len = pkt.len(); - let address = pkt.as_ptr() as *const u8; - - descriptor.set_buffer1(address); - descriptor.set_buffer1_len(pkt_len); - - self.buffers[self.next_entry].replace(pkt); + descriptor.set_buffer1(self.buffers[self.index].0.as_ptr()); + descriptor.set_buffer1_len(len); descriptor.set_owned(); @@ -198,36 +160,12 @@ impl TDesRing { // "Preceding reads and writes cannot be moved past subsequent writes." fence(Ordering::Release); - // Move the tail pointer (TPR) to the next descriptor - self.next_entry = (self.next_entry + 1) % N; - + // Move the index to the next descriptor + self.index += 1; + if self.index == self.descriptors.len() { + self.index = 0 + } // Request the DMA engine to poll the latest tx descriptor - unsafe { ETH.ethernet_dma().dmatpdr().modify(|w| w.0 = 1) } - Ok(()) - } - - pub(crate) fn on_interrupt(&mut self) -> Result<(), Error> { - let previous = (self.next_entry + N - 1) % N; - let td = &self.descriptors[previous]; - - // DMB to ensure that we are reading an updated value, probably not needed at the hardware - // level, but this is also a hint to the compiler that we're syncing on the buffer. - fence(Ordering::SeqCst); - - let tdes0 = td.tdes0.get(); - - if tdes0 & TXDESC_0_OWN != 0 { - // Transmission isn't done yet, probably a receive interrupt that fired this - return Ok(()); - } - - // Release the buffer - self.buffers[previous].take(); - - if tdes0 & TXDESC_0_ES != 0 { - Err(Error::TransmissionError) - } else { - Ok(()) - } + ETH.ethernet_dma().dmatpdr().modify(|w| w.0 = 1) } } diff --git a/embassy-stm32/src/eth/v2/descriptors.rs b/embassy-stm32/src/eth/v2/descriptors.rs index c6c06a9ce..e9799adf1 100644 --- a/embassy-stm32/src/eth/v2/descriptors.rs +++ b/embassy-stm32/src/eth/v2/descriptors.rs @@ -1,19 +1,10 @@ use core::sync::atomic::{fence, Ordering}; -use embassy_net::{Packet, PacketBox, PacketBoxExt, PacketBuf}; use vcell::VolatileCell; +use crate::eth::{Packet, RX_BUFFER_SIZE, TX_BUFFER_SIZE}; use crate::pac::ETH; -#[non_exhaustive] -#[derive(Debug, Copy, Clone)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Error { - NoBufferAvailable, - // TODO: Break down this error into several others - TransmissionError, -} - /// Transmit and Receive Descriptor fields #[allow(dead_code)] mod emac_consts { @@ -41,7 +32,7 @@ use emac_consts::*; /// * tdes2: buffer lengths /// * tdes3: control and payload/frame length #[repr(C)] -struct TDes { +pub(crate) struct TDes { tdes0: VolatileCell, tdes1: VolatileCell, tdes2: VolatileCell, @@ -59,122 +50,82 @@ impl TDes { } /// Return true if this TDes is not currently owned by the DMA - pub fn available(&self) -> bool { + fn available(&self) -> bool { self.tdes3.get() & EMAC_DES3_OWN == 0 } } -pub(crate) struct TDesRing { - td: [TDes; N], - buffers: [Option; N], - tdidx: usize, +pub(crate) struct TDesRing<'a> { + descriptors: &'a mut [TDes], + buffers: &'a mut [Packet], + index: usize, } -impl TDesRing { - pub const fn new() -> Self { - const TDES: TDes = TDes::new(); - const BUFFERS: Option = None; +impl<'a> TDesRing<'a> { + /// Initialise this TDesRing. Assume TDesRing is corrupt. + pub fn new(descriptors: &'a mut [TDes], buffers: &'a mut [Packet]) -> Self { + assert!(descriptors.len() > 0); + assert!(descriptors.len() == buffers.len()); - Self { - td: [TDES; N], - buffers: [BUFFERS; N], - tdidx: 0, - } - } - - /// Initialise this TDesRing. Assume TDesRing is corrupt - /// - /// The current memory address of the buffers inside this TDesRing - /// will be stored in the descriptors, so ensure the TDesRing is - /// not moved after initialisation. - pub(crate) fn init(&mut self) { - assert!(N > 0); - - for td in self.td.iter_mut() { + for td in descriptors.iter_mut() { *td = TDes::new(); } - self.tdidx = 0; // Initialize the pointers in the DMA engine. (There will be a memory barrier later // before the DMA engine is enabled.) - // NOTE (unsafe) Used for atomic writes - unsafe { - let dma = ETH.ethernet_dma(); + let dma = ETH.ethernet_dma(); + dma.dmactx_dlar().write(|w| w.0 = descriptors.as_mut_ptr() as u32); + dma.dmactx_rlr().write(|w| w.set_tdrl((descriptors.len() as u16) - 1)); + dma.dmactx_dtpr().write(|w| w.0 = 0); - dma.dmactx_dlar().write(|w| w.0 = &self.td as *const _ as u32); - dma.dmactx_rlr().write(|w| w.set_tdrl((N as u16) - 1)); - dma.dmactx_dtpr().write(|w| w.0 = &self.td[0] as *const _ as u32); + Self { + descriptors, + buffers, + index: 0, } } - /// Return true if a TDes is available for use - pub(crate) fn available(&self) -> bool { - self.td[self.tdidx].available() + pub(crate) fn len(&self) -> usize { + self.descriptors.len() } - pub(crate) fn transmit(&mut self, pkt: PacketBuf) -> Result<(), Error> { - if !self.available() { - return Err(Error::NoBufferAvailable); + /// Return the next available packet buffer for transmitting, or None + pub(crate) fn available(&mut self) -> Option<&mut [u8]> { + let d = &mut self.descriptors[self.index]; + if d.available() { + Some(&mut self.buffers[self.index].0) + } else { + None } - let x = self.tdidx; - let td = &mut self.td[x]; + } - let pkt_len = pkt.len(); - assert!(pkt_len as u32 <= EMAC_TDES2_B1L); - let address = pkt.as_ptr() as u32; + /// Transmit the packet written in a buffer returned by `available`. + pub(crate) fn transmit(&mut self, len: usize) { + let td = &mut self.descriptors[self.index]; + assert!(td.available()); + assert!(len as u32 <= EMAC_TDES2_B1L); // Read format - td.tdes0.set(address); - td.tdes2.set(pkt_len as u32 & EMAC_TDES2_B1L | EMAC_TDES2_IOC); + td.tdes0.set(self.buffers[self.index].0.as_ptr() as u32); + td.tdes2.set(len as u32 & EMAC_TDES2_B1L | EMAC_TDES2_IOC); // FD: Contains first buffer of packet // LD: Contains last buffer of packet // Give the DMA engine ownership td.tdes3.set(EMAC_DES3_FD | EMAC_DES3_LD | EMAC_DES3_OWN); - self.buffers[x].replace(pkt); - // Ensure changes to the descriptor are committed before DMA engine sees tail pointer store. // This will generate an DMB instruction. // "Preceding reads and writes cannot be moved past subsequent writes." fence(Ordering::Release); - // Move the tail pointer (TPR) to the next descriptor - let x = (x + 1) % N; - // NOTE(unsafe) Atomic write - unsafe { - ETH.ethernet_dma() - .dmactx_dtpr() - .write(|w| w.0 = &self.td[x] as *const _ as u32); + self.index = self.index + 1; + if self.index == self.descriptors.len() { + self.index = 0; } - self.tdidx = x; - Ok(()) - } - pub(crate) fn on_interrupt(&mut self) -> Result<(), Error> { - let previous = (self.tdidx + N - 1) % N; - let td = &self.td[previous]; - - // DMB to ensure that we are reading an updated value, probably not needed at the hardware - // level, but this is also a hint to the compiler that we're syncing on the buffer. - fence(Ordering::SeqCst); - - let tdes3 = td.tdes3.get(); - - if tdes3 & EMAC_DES3_OWN != 0 { - // Transmission isn't done yet, probably a receive interrupt that fired this - return Ok(()); - } - assert!(tdes3 & EMAC_DES3_CTXT == 0); - - // Release the buffer - self.buffers[previous].take(); - - if tdes3 & EMAC_DES3_ES != 0 { - Err(Error::TransmissionError) - } else { - Ok(()) - } + // signal DMA it can try again. + ETH.ethernet_dma().dmactx_dtpr().write(|w| w.0 = 0) } } @@ -185,7 +136,7 @@ impl TDesRing { /// * rdes2: /// * rdes3: OWN and Status #[repr(C)] -struct RDes { +pub(crate) struct RDes { rdes0: VolatileCell, rdes1: VolatileCell, rdes2: VolatileCell, @@ -204,7 +155,7 @@ impl RDes { /// Return true if this RDes is acceptable to us #[inline(always)] - pub fn valid(&self) -> bool { + fn valid(&self) -> bool { // Write-back descriptor is valid if: // // Contains first buffer of packet AND contains last buf of @@ -215,177 +166,92 @@ impl RDes { /// Return true if this RDes is not currently owned by the DMA #[inline(always)] - pub fn available(&self) -> bool { + fn available(&self) -> bool { self.rdes3.get() & EMAC_DES3_OWN == 0 // Owned by us } #[inline(always)] - pub fn set_ready(&mut self, buf_addr: u32) { - self.rdes0.set(buf_addr); + fn set_ready(&mut self, buf: *mut u8) { + self.rdes0.set(buf as u32); self.rdes3.set(EMAC_RDES3_BUF1V | EMAC_RDES3_IOC | EMAC_DES3_OWN); } } /// Rx ring of descriptors and packets -/// -/// This ring has three major locations that work in lock-step. The DMA will never write to the tail -/// index, so the `read_index` must never pass the tail index. The `next_tail_index` is always 1 -/// slot ahead of the real tail index, and it must never pass the `read_index` or it could overwrite -/// a packet still to be passed to the application. -/// -/// nt can't pass r (no alloc) -/// +---+---+---+---+ Read ok +---+---+---+---+ No Read +---+---+---+---+ -/// | | | | | ------------> | | | | | ------------> | | | | | -/// +---+---+---+---+ Allocation ok +---+---+---+---+ +---+---+---+---+ -/// ^ ^t ^t ^ ^t ^ -/// |r |r |r -/// |nt |nt |nt -/// -/// -/// +---+---+---+---+ Read ok +---+---+---+---+ Can't read +---+---+---+---+ -/// | | | | | ------------> | | | | | ------------> | | | | | -/// +---+---+---+---+ Allocation fail +---+---+---+---+ Allocation ok +---+---+---+---+ -/// ^ ^t ^ ^t ^ ^ ^ ^t -/// |r | |r | | |r -/// |nt |nt |nt -/// -pub(crate) struct RDesRing { - rd: [RDes; N], - buffers: [Option; N], - read_idx: usize, - next_tail_idx: usize, +pub(crate) struct RDesRing<'a> { + descriptors: &'a mut [RDes], + buffers: &'a mut [Packet], + index: usize, } -impl RDesRing { - pub const fn new() -> Self { - const RDES: RDes = RDes::new(); - const BUFFERS: Option = None; +impl<'a> RDesRing<'a> { + pub(crate) fn new(descriptors: &'a mut [RDes], buffers: &'a mut [Packet]) -> Self { + assert!(descriptors.len() > 1); + assert!(descriptors.len() == buffers.len()); + + for (i, desc) in descriptors.iter_mut().enumerate() { + *desc = RDes::new(); + desc.set_ready(buffers[i].0.as_mut_ptr()); + } + + let dma = ETH.ethernet_dma(); + dma.dmacrx_dlar().write(|w| w.0 = descriptors.as_mut_ptr() as u32); + dma.dmacrx_rlr().write(|w| w.set_rdrl((descriptors.len() as u16) - 1)); + dma.dmacrx_dtpr().write(|w| w.0 = 0); Self { - rd: [RDES; N], - buffers: [BUFFERS; N], - read_idx: 0, - next_tail_idx: 0, + descriptors, + buffers, + index: 0, } } - pub(crate) fn init(&mut self) { - assert!(N > 1); - - for desc in self.rd.iter_mut() { - *desc = RDes::new(); - } - - let mut last_index = 0; - for (index, buf) in self.buffers.iter_mut().enumerate() { - let pkt = match PacketBox::new(Packet::new()) { - Some(p) => p, - None => { - if index == 0 { - panic!("Could not allocate at least one buffer for Ethernet receiving"); - } else { - break; - } - } - }; - let addr = pkt.as_ptr() as u32; - *buf = Some(pkt); - self.rd[index].set_ready(addr); - last_index = index; - } - self.next_tail_idx = (last_index + 1) % N; - - unsafe { - let dma = ETH.ethernet_dma(); - - dma.dmacrx_dlar().write(|w| w.0 = self.rd.as_ptr() as u32); - dma.dmacrx_rlr().write(|w| w.set_rdrl((N as u16) - 1)); - - // We manage to allocate all buffers, set the index to the last one, that means - // that the DMA won't consider the last one as ready, because it (unfortunately) - // stops at the tail ptr and wraps at the end of the ring, which means that we - // can't tell it to stop after the last buffer. - let tail_ptr = &self.rd[last_index] as *const _ as u32; - fence(Ordering::Release); - - dma.dmacrx_dtpr().write(|w| w.0 = tail_ptr); - } - } - - pub(crate) fn on_interrupt(&mut self) { - // XXX: Do we need to do anything here ? Maybe we should try to advance the tail ptr, but it - // would soon hit the read ptr anyway, and we will wake smoltcp's stack on the interrupt - // which should try to pop a packet... - } - - pub(crate) fn pop_packet(&mut self) -> Option { + /// Get a received packet if any, or None. + pub(crate) fn available(&mut self) -> Option<&mut [u8]> { // Not sure if the contents of the write buffer on the M7 can affects reads, so we are using // a DMB here just in case, it also serves as a hint to the compiler that we're syncing the // buffer (I think .-.) fence(Ordering::SeqCst); - let read_available = self.rd[self.read_idx].available(); - let tail_index = (self.next_tail_idx + N - 1) % N; - - let pkt = if read_available && self.read_idx != tail_index { - let pkt = self.buffers[self.read_idx].take(); - let len = (self.rd[self.read_idx].rdes3.get() & EMAC_RDES3_PKTLEN) as usize; - - assert!(pkt.is_some()); - let valid = self.rd[self.read_idx].valid(); - - self.read_idx = (self.read_idx + 1) % N; - if valid { - pkt.map(|p| p.slice(0..len)) - } else { - None + // We might have to process many packets, in case some have been rx'd but are invalid. + loop { + let descriptor = &mut self.descriptors[self.index]; + if !descriptor.available() { + return None; } - } else { - None - }; - // Try to advance the tail_idx - if self.next_tail_idx != self.read_idx { - match PacketBox::new(Packet::new()) { - Some(b) => { - let addr = b.as_ptr() as u32; - self.buffers[self.next_tail_idx].replace(b); - self.rd[self.next_tail_idx].set_ready(addr); - - // "Preceding reads and writes cannot be moved past subsequent writes." - fence(Ordering::Release); - - // NOTE(unsafe) atomic write - unsafe { - ETH.ethernet_dma() - .dmacrx_dtpr() - .write(|w| w.0 = &self.rd[self.next_tail_idx] as *const _ as u32); - } - - self.next_tail_idx = (self.next_tail_idx + 1) % N; - } - None => {} + // If packet is invalid, pop it and try again. + if !descriptor.valid() { + warn!("invalid packet: {:08x}", descriptor.rdes0.get()); + self.pop_packet(); + continue; } + + break; } - pkt + + let descriptor = &mut self.descriptors[self.index]; + let len = (descriptor.rdes3.get() & EMAC_RDES3_PKTLEN) as usize; + return Some(&mut self.buffers[self.index].0[..len]); } -} -pub struct DescriptorRing { - pub(crate) tx: TDesRing, - pub(crate) rx: RDesRing, -} + /// Pop the packet previously returned by `available`. + pub(crate) fn pop_packet(&mut self) { + let descriptor = &mut self.descriptors[self.index]; + assert!(descriptor.available()); -impl DescriptorRing { - pub const fn new() -> Self { - Self { - tx: TDesRing::new(), - rx: RDesRing::new(), + self.descriptors[self.index].set_ready(self.buffers[self.index].0.as_mut_ptr()); + + // "Preceding reads and writes cannot be moved past subsequent writes." + fence(Ordering::Release); + + // signal DMA it can try again. + ETH.ethernet_dma().dmacrx_dtpr().write(|w| w.0 = 0); + + // Increment index. + self.index += 1; + if self.index == self.descriptors.len() { + self.index = 0 } } - - pub fn init(&mut self) { - self.tx.init(); - self.rx.init(); - } } diff --git a/embassy-stm32/src/eth/v2/mod.rs b/embassy-stm32/src/eth/v2/mod.rs index d67c3c5e4..bb681c42b 100644 --- a/embassy-stm32/src/eth/v2/mod.rs +++ b/embassy-stm32/src/eth/v2/mod.rs @@ -1,40 +1,53 @@ +mod descriptors; + use core::marker::PhantomData; use core::sync::atomic::{fence, Ordering}; -use core::task::Waker; -use embassy_cortex_m::peripheral::{PeripheralMutex, PeripheralState, StateStorage}; use embassy_hal_common::{into_ref, PeripheralRef}; -use embassy_net::{Device, DeviceCapabilities, LinkState, PacketBuf, MTU}; -use embassy_sync::waitqueue::AtomicWaker; +pub(crate) use self::descriptors::{RDes, RDesRing, TDes, TDesRing}; +use super::*; use crate::gpio::sealed::{AFType, Pin as _}; use crate::gpio::{AnyPin, Speed}; -use crate::pac::{ETH, RCC, SYSCFG}; -use crate::Peripheral; +use crate::interrupt::InterruptExt; +use crate::pac::ETH; +use crate::{interrupt, Peripheral}; -mod descriptors; -use descriptors::DescriptorRing; +/// Interrupt handler. +pub struct InterruptHandler {} -use super::*; +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + WAKER.wake(); -pub struct State<'d, T: Instance, const TX: usize, const RX: usize>(StateStorage>); -impl<'d, T: Instance, const TX: usize, const RX: usize> State<'d, T, TX, RX> { - pub fn new() -> Self { - Self(StateStorage::new()) + // TODO: Check and clear more flags + let dma = ETH.ethernet_dma(); + + dma.dmacsr().modify(|w| { + w.set_ti(true); + w.set_ri(true); + w.set_nis(true); + }); + // Delay two peripheral's clock + dma.dmacsr().read(); + dma.dmacsr().read(); } } -pub struct Ethernet<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> { - state: PeripheralMutex<'d, Inner<'d, T, TX, RX>>, + +const MTU: usize = 1514; // 14 Ethernet header + 1500 IP packet + +pub struct Ethernet<'d, T: Instance, P: PHY> { + _peri: PeripheralRef<'d, T>, + pub(crate) tx: TDesRing<'d>, + pub(crate) rx: RDesRing<'d>, pins: [PeripheralRef<'d, AnyPin>; 9], - _phy: P, - clock_range: u8, - phy_addr: u8, - mac_addr: [u8; 6], + pub(crate) phy: P, + pub(crate) station_management: EthernetStationManagement, + pub(crate) mac_addr: [u8; 6], } macro_rules! config_pins { ($($pin:ident),*) => { - // NOTE(unsafe) Exclusive access to the registers critical_section::with(|_| { $( $pin.set_as_af($pin.af_num(), AFType::OutputPushPull); @@ -44,12 +57,11 @@ macro_rules! config_pins { }; } -impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Ethernet<'d, T, P, TX, RX> { - /// safety: the returned instance is not leak-safe - pub unsafe fn new( - state: &'d mut State<'d, T, TX, RX>, +impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> { + pub fn new( + queue: &'d mut PacketQueue, peri: impl Peripheral

+ 'd, - interrupt: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding + 'd, ref_clk: impl Peripheral

> + 'd, mdio: impl Peripheral

> + 'd, mdc: impl Peripheral

> + 'd, @@ -63,28 +75,40 @@ impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Ethernet<'d, T, mac_addr: [u8; 6], phy_addr: u8, ) -> Self { - into_ref!(interrupt, ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en); + into_ref!(peri, ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en); // Enable the necessary Clocks - // NOTE(unsafe) We have exclusive access to the registers + #[cfg(not(rcc_h5))] critical_section::with(|_| { - RCC.apb4enr().modify(|w| w.set_syscfgen(true)); - RCC.ahb1enr().modify(|w| { + crate::pac::RCC.apb4enr().modify(|w| w.set_syscfgen(true)); + crate::pac::RCC.ahb1enr().modify(|w| { w.set_eth1macen(true); w.set_eth1txen(true); w.set_eth1rxen(true); }); // RMII - SYSCFG.pmcr().modify(|w| w.set_epis(0b100)); + crate::pac::SYSCFG.pmcr().modify(|w| w.set_epis(0b100)); + }); + + #[cfg(rcc_h5)] + critical_section::with(|_| { + crate::pac::RCC.apb3enr().modify(|w| w.set_sbsen(true)); + + crate::pac::RCC.ahb1enr().modify(|w| { + w.set_ethen(true); + w.set_ethtxen(true); + w.set_ethrxen(true); + }); + + // RMII + crate::pac::SBS + .pmcr() + .modify(|w| w.set_eth_sel_phy(crate::pac::sbs::vals::EthSelPhy::B_0X4)); }); config_pins!(ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en); - // NOTE(unsafe) We are ourselves not leak-safe. - let state = PeripheralMutex::new(interrupt, &mut state.0, || Inner::new(peri)); - - // NOTE(unsafe) We have exclusive access to the registers let dma = ETH.ethernet_dma(); let mac = ETH.ethernet_mac(); let mtl = ETH.ethernet_mtl(); @@ -116,6 +140,24 @@ impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Ethernet<'d, T, mac.macqtx_fcr().modify(|w| w.set_pt(0x100)); + // disable all MMC RX interrupts + mac.mmc_rx_interrupt_mask().write(|w| { + w.set_rxcrcerpim(true); + w.set_rxalgnerpim(true); + w.set_rxucgpim(true); + w.set_rxlpiuscim(true); + w.set_rxlpitrcim(true) + }); + + // disable all MMC TX interrupts + mac.mmc_tx_interrupt_mask().write(|w| { + w.set_txscolgpim(true); + w.set_txmcolgpim(true); + w.set_txgpktim(true); + w.set_txlpiuscim(true); + w.set_txlpitrcim(true); + }); + mtl.mtlrx_qomr().modify(|w| w.set_rsf(true)); mtl.mtltx_qomr().modify(|w| w.set_tsf(true)); @@ -126,7 +168,7 @@ impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Ethernet<'d, T, }); // NOTE(unsafe) We got the peripheral singleton, which means that `rcc::init` was called - let hclk = crate::rcc::get_freqs().ahb1; + let hclk = unsafe { crate::rcc::get_freqs() }.ahb1; let hclk_mhz = hclk.0 / 1_000_000; // Set the MDC clock frequency in the range 1MHz - 2.5MHz @@ -155,198 +197,117 @@ impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Ethernet<'d, T, ]; let mut this = Self { - state, + _peri: peri, + tx: TDesRing::new(&mut queue.tx_desc, &mut queue.tx_buf), + rx: RDesRing::new(&mut queue.rx_desc, &mut queue.rx_buf), pins, - _phy: phy, - clock_range, - phy_addr, + phy: phy, + station_management: EthernetStationManagement { + peri: PhantomData, + clock_range: clock_range, + phy_addr: phy_addr, + }, mac_addr, }; - this.state.with(|s| { - s.desc_ring.init(); + fence(Ordering::SeqCst); - fence(Ordering::SeqCst); + let mac = ETH.ethernet_mac(); + let mtl = ETH.ethernet_mtl(); + let dma = ETH.ethernet_dma(); - let mac = ETH.ethernet_mac(); - let mtl = ETH.ethernet_mtl(); - let dma = ETH.ethernet_dma(); - - mac.maccr().modify(|w| { - w.set_re(true); - w.set_te(true); - }); - mtl.mtltx_qomr().modify(|w| w.set_ftq(true)); - - dma.dmactx_cr().modify(|w| w.set_st(true)); - dma.dmacrx_cr().modify(|w| w.set_sr(true)); - - // Enable interrupts - dma.dmacier().modify(|w| { - w.set_nie(true); - w.set_rie(true); - w.set_tie(true); - }); + mac.maccr().modify(|w| { + w.set_re(true); + w.set_te(true); }); - P::phy_reset(&mut this); - P::phy_init(&mut this); + mtl.mtltx_qomr().modify(|w| w.set_ftq(true)); + + dma.dmactx_cr().modify(|w| w.set_st(true)); + dma.dmacrx_cr().modify(|w| w.set_sr(true)); + + // Enable interrupts + dma.dmacier().modify(|w| { + w.set_nie(true); + w.set_rie(true); + w.set_tie(true); + }); + + this.phy.phy_reset(&mut this.station_management); + this.phy.phy_init(&mut this.station_management); + + interrupt::ETH.unpend(); + unsafe { interrupt::ETH.enable() }; this } } -unsafe impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> StationManagement - for Ethernet<'d, T, P, TX, RX> -{ - fn smi_read(&mut self, reg: u8) -> u16 { - // NOTE(unsafe) These registers aren't used in the interrupt and we have `&mut self` - unsafe { - let mac = ETH.ethernet_mac(); +pub struct EthernetStationManagement { + peri: PhantomData, + clock_range: u8, + phy_addr: u8, +} - mac.macmdioar().modify(|w| { - w.set_pa(self.phy_addr); - w.set_rda(reg); - w.set_goc(0b11); // read - w.set_cr(self.clock_range); - w.set_mb(true); - }); - while mac.macmdioar().read().mb() {} - mac.macmdiodr().read().md() - } +unsafe impl StationManagement for EthernetStationManagement { + fn smi_read(&mut self, reg: u8) -> u16 { + let mac = ETH.ethernet_mac(); + + mac.macmdioar().modify(|w| { + w.set_pa(self.phy_addr); + w.set_rda(reg); + w.set_goc(0b11); // read + w.set_cr(self.clock_range); + w.set_mb(true); + }); + while mac.macmdioar().read().mb() {} + mac.macmdiodr().read().md() } fn smi_write(&mut self, reg: u8, val: u16) { - // NOTE(unsafe) These registers aren't used in the interrupt and we have `&mut self` - unsafe { - let mac = ETH.ethernet_mac(); + let mac = ETH.ethernet_mac(); - mac.macmdiodr().write(|w| w.set_md(val)); - mac.macmdioar().modify(|w| { - w.set_pa(self.phy_addr); - w.set_rda(reg); - w.set_goc(0b01); // write - w.set_cr(self.clock_range); - w.set_mb(true); - }); - while mac.macmdioar().read().mb() {} - } + mac.macmdiodr().write(|w| w.set_md(val)); + mac.macmdioar().modify(|w| { + w.set_pa(self.phy_addr); + w.set_rda(reg); + w.set_goc(0b01); // write + w.set_cr(self.clock_range); + w.set_mb(true); + }); + while mac.macmdioar().read().mb() {} } } -impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Device for Ethernet<'d, T, P, TX, RX> { - fn is_transmit_ready(&mut self) -> bool { - self.state.with(|s| s.desc_ring.tx.available()) - } - - fn transmit(&mut self, pkt: PacketBuf) { - self.state.with(|s| unwrap!(s.desc_ring.tx.transmit(pkt))); - } - - fn receive(&mut self) -> Option { - self.state.with(|s| s.desc_ring.rx.pop_packet()) - } - - fn register_waker(&mut self, waker: &Waker) { - WAKER.register(waker); - } - - fn capabilities(&self) -> DeviceCapabilities { - let mut caps = DeviceCapabilities::default(); - caps.max_transmission_unit = MTU; - caps.max_burst_size = Some(TX.min(RX)); - caps - } - - fn link_state(&mut self) -> LinkState { - if P::poll_link(self) { - LinkState::Up - } else { - LinkState::Down - } - } - - fn ethernet_address(&self) -> [u8; 6] { - self.mac_addr - } -} - -impl<'d, T: Instance, P: PHY, const TX: usize, const RX: usize> Drop for Ethernet<'d, T, P, TX, RX> { +impl<'d, T: Instance, P: PHY> Drop for Ethernet<'d, T, P> { fn drop(&mut self) { - // NOTE(unsafe) We have `&mut self` and the interrupt doesn't use this registers - unsafe { - let dma = ETH.ethernet_dma(); - let mac = ETH.ethernet_mac(); - let mtl = ETH.ethernet_mtl(); + let dma = ETH.ethernet_dma(); + let mac = ETH.ethernet_mac(); + let mtl = ETH.ethernet_mtl(); - // Disable the TX DMA and wait for any previous transmissions to be completed - dma.dmactx_cr().modify(|w| w.set_st(false)); - while { - let txqueue = mtl.mtltx_qdr().read(); - txqueue.trcsts() == 0b01 || txqueue.txqsts() - } {} + // Disable the TX DMA and wait for any previous transmissions to be completed + dma.dmactx_cr().modify(|w| w.set_st(false)); + while { + let txqueue = mtl.mtltx_qdr().read(); + txqueue.trcsts() == 0b01 || txqueue.txqsts() + } {} - // Disable MAC transmitter and receiver - mac.maccr().modify(|w| { - w.set_re(false); - w.set_te(false); - }); + // Disable MAC transmitter and receiver + mac.maccr().modify(|w| { + w.set_re(false); + w.set_te(false); + }); - // Wait for previous receiver transfers to be completed and then disable the RX DMA - while { - let rxqueue = mtl.mtlrx_qdr().read(); - rxqueue.rxqsts() != 0b00 || rxqueue.prxq() != 0 - } {} - dma.dmacrx_cr().modify(|w| w.set_sr(false)); - } + // Wait for previous receiver transfers to be completed and then disable the RX DMA + while { + let rxqueue = mtl.mtlrx_qdr().read(); + rxqueue.rxqsts() != 0b00 || rxqueue.prxq() != 0 + } {} + dma.dmacrx_cr().modify(|w| w.set_sr(false)); - // NOTE(unsafe) Exclusive access to the regs - critical_section::with(|_| unsafe { + critical_section::with(|_| { for pin in self.pins.iter_mut() { pin.set_as_disconnected(); } }) } } - -//---------------------------------------------------------------------- - -struct Inner<'d, T: Instance, const TX: usize, const RX: usize> { - _peri: PhantomData<&'d mut T>, - desc_ring: DescriptorRing, -} - -impl<'d, T: Instance, const TX: usize, const RX: usize> Inner<'d, T, TX, RX> { - pub fn new(_peri: impl Peripheral

+ 'd) -> Self { - Self { - _peri: PhantomData, - desc_ring: DescriptorRing::new(), - } - } -} - -impl<'d, T: Instance, const TX: usize, const RX: usize> PeripheralState for Inner<'d, T, TX, RX> { - type Interrupt = crate::interrupt::ETH; - - fn on_interrupt(&mut self) { - unwrap!(self.desc_ring.tx.on_interrupt()); - self.desc_ring.rx.on_interrupt(); - - WAKER.wake(); - - // TODO: Check and clear more flags - unsafe { - let dma = ETH.ethernet_dma(); - - dma.dmacsr().modify(|w| { - w.set_ti(true); - w.set_ri(true); - w.set_nis(true); - }); - // Delay two peripheral's clock - dma.dmacsr().read(); - dma.dmacsr().read(); - } - } -} - -static WAKER: AtomicWaker = AtomicWaker::new(); diff --git a/embassy-stm32/src/exti.rs b/embassy-stm32/src/exti.rs index 935149b13..3ff92c9e6 100644 --- a/embassy-stm32/src/exti.rs +++ b/embassy-stm32/src/exti.rs @@ -25,11 +25,11 @@ fn cpu_regs() -> pac::exti::Exti { EXTI } -#[cfg(not(any(exti_g0, exti_l5, gpio_v1, exti_u5)))] +#[cfg(not(any(exti_c0, exti_g0, exti_l5, gpio_v1, exti_u5, exti_h5, exti_h50)))] fn exticr_regs() -> pac::syscfg::Syscfg { pac::SYSCFG } -#[cfg(any(exti_g0, exti_l5, exti_u5))] +#[cfg(any(exti_c0, exti_g0, exti_l5, exti_u5, exti_h5, exti_h50))] fn exticr_regs() -> pac::exti::Exti { EXTI } @@ -39,9 +39,9 @@ fn exticr_regs() -> pac::afio::Afio { } pub unsafe fn on_irq() { - #[cfg(not(any(exti_g0, exti_l5, exti_u5)))] + #[cfg(not(any(exti_c0, exti_g0, exti_l5, exti_u5, exti_h5, exti_h50)))] let bits = EXTI.pr(0).read().0; - #[cfg(any(exti_g0, exti_l5, exti_u5))] + #[cfg(any(exti_c0, exti_g0, exti_l5, exti_u5, exti_h5, exti_h50))] let bits = EXTI.rpr(0).read().0 | EXTI.fpr(0).read().0; // Mask all the channels that fired. @@ -53,9 +53,9 @@ pub unsafe fn on_irq() { } // Clear pending - #[cfg(not(any(exti_g0, exti_l5, exti_u5)))] + #[cfg(not(any(exti_c0, exti_g0, exti_l5, exti_u5, exti_h5, exti_h50)))] EXTI.pr(0).write_value(Lines(bits)); - #[cfg(any(exti_g0, exti_l5, exti_u5))] + #[cfg(any(exti_c0, exti_g0, exti_l5, exti_u5, exti_h5, exti_h50))] { EXTI.rpr(0).write_value(Lines(bits)); EXTI.fpr(0).write_value(Lines(bits)); @@ -155,7 +155,7 @@ mod eh1 { type Error = Infallible; } - impl<'d, T: GpioPin> embedded_hal_1::digital::blocking::InputPin for ExtiInput<'d, T> { + impl<'d, T: GpioPin> embedded_hal_1::digital::InputPin for ExtiInput<'d, T> { fn is_high(&self) -> Result { Ok(self.is_high()) } @@ -165,44 +165,40 @@ mod eh1 { } } } -cfg_if::cfg_if! { - if #[cfg(all(feature = "unstable-traits", feature = "nightly"))] { - use futures::FutureExt; +#[cfg(all(feature = "unstable-traits", feature = "nightly"))] +mod eha { - impl<'d, T: GpioPin> embedded_hal_async::digital::Wait for ExtiInput<'d, T> { - type WaitForHighFuture<'a> = impl Future> + 'a where Self: 'a; + use super::*; - fn wait_for_high<'a>(&'a mut self) -> Self::WaitForHighFuture<'a> { - self.wait_for_high().map(Ok) - } + impl<'d, T: GpioPin> embedded_hal_async::digital::Wait for ExtiInput<'d, T> { + async fn wait_for_high(&mut self) -> Result<(), Self::Error> { + self.wait_for_high().await; + Ok(()) + } - type WaitForLowFuture<'a> = impl Future> + 'a where Self: 'a; + async fn wait_for_low(&mut self) -> Result<(), Self::Error> { + self.wait_for_low().await; + Ok(()) + } - fn wait_for_low<'a>(&'a mut self) -> Self::WaitForLowFuture<'a> { - self.wait_for_low().map(Ok) - } + async fn wait_for_rising_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_rising_edge().await; + Ok(()) + } - type WaitForRisingEdgeFuture<'a> = impl Future> + 'a where Self: 'a; + async fn wait_for_falling_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_falling_edge().await; + Ok(()) + } - fn wait_for_rising_edge<'a>(&'a mut self) -> Self::WaitForRisingEdgeFuture<'a> { - self.wait_for_rising_edge().map(Ok) - } - - type WaitForFallingEdgeFuture<'a> = impl Future> + 'a where Self: 'a; - - fn wait_for_falling_edge<'a>(&'a mut self) -> Self::WaitForFallingEdgeFuture<'a> { - self.wait_for_falling_edge().map(Ok) - } - - type WaitForAnyEdgeFuture<'a> = impl Future> + 'a where Self: 'a; - - fn wait_for_any_edge<'a>(&'a mut self) -> Self::WaitForAnyEdgeFuture<'a> { - self.wait_for_any_edge().map(Ok) - } + async fn wait_for_any_edge(&mut self) -> Result<(), Self::Error> { + self.wait_for_any_edge().await; + Ok(()) } } } +#[must_use = "futures do nothing unless you `.await` or poll them"] struct ExtiInputFuture<'a> { pin: u8, phantom: PhantomData<&'a mut AnyPin>, @@ -210,16 +206,16 @@ struct ExtiInputFuture<'a> { impl<'a> ExtiInputFuture<'a> { fn new(pin: u8, port: u8, rising: bool, falling: bool) -> Self { - critical_section::with(|_| unsafe { + critical_section::with(|_| { let pin = pin as usize; exticr_regs().exticr(pin / 4).modify(|w| w.set_exti(pin % 4, port)); EXTI.rtsr(0).modify(|w| w.set_line(pin, rising)); EXTI.ftsr(0).modify(|w| w.set_line(pin, falling)); // clear pending bit - #[cfg(not(any(exti_g0, exti_l5, exti_u5)))] + #[cfg(not(any(exti_c0, exti_g0, exti_l5, exti_u5, exti_h5, exti_h50)))] EXTI.pr(0).write(|w| w.set_line(pin, true)); - #[cfg(any(exti_g0, exti_l5, exti_u5))] + #[cfg(any(exti_c0, exti_g0, exti_l5, exti_u5, exti_h5, exti_h50))] { EXTI.rpr(0).write(|w| w.set_line(pin, true)); EXTI.fpr(0).write(|w| w.set_line(pin, true)); @@ -237,7 +233,7 @@ impl<'a> ExtiInputFuture<'a> { impl<'a> Drop for ExtiInputFuture<'a> { fn drop(&mut self) { - critical_section::with(|_| unsafe { + critical_section::with(|_| { let pin = self.pin as _; cpu_regs().imr(0).modify(|w| w.set_line(pin, false)); }); @@ -250,7 +246,7 @@ impl<'a> Future for ExtiInputFuture<'a> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { EXTI_WAKERS[self.pin as usize].register(cx.waker()); - let imr = unsafe { cpu_regs().imr(0).read() }; + let imr = cpu_regs().imr(0).read(); if !imr.line(self.pin as _) { Poll::Ready(()) } else { @@ -295,6 +291,7 @@ macro_rules! foreach_exti_irq { macro_rules! impl_irq { ($e:ident) => { + #[cfg(feature = "rt")] #[interrupt] unsafe fn $e() { on_irq() @@ -358,17 +355,17 @@ impl_exti!(EXTI15, 15); macro_rules! enable_irq { ($e:ident) => { - crate::interrupt::$e::steal().enable(); + crate::interrupt::typelevel::$e::enable(); }; } /// safety: must be called only once pub(crate) unsafe fn init() { - use crate::interrupt::{Interrupt, InterruptExt}; + use crate::interrupt::typelevel::Interrupt; foreach_exti_irq!(enable_irq); - #[cfg(not(any(rcc_wb, rcc_wl5, rcc_wle, stm32f1)))] + #[cfg(not(any(rcc_wb, rcc_wl5, rcc_wle, stm32f1, exti_h5, exti_h50)))] ::enable(); #[cfg(stm32f1)] ::enable(); diff --git a/embassy-stm32/src/flash/asynch.rs b/embassy-stm32/src/flash/asynch.rs new file mode 100644 index 000000000..f175349cd --- /dev/null +++ b/embassy-stm32/src/flash/asynch.rs @@ -0,0 +1,188 @@ +use core::marker::PhantomData; +use core::sync::atomic::{fence, Ordering}; + +use embassy_hal_common::drop::OnDrop; +use embassy_hal_common::into_ref; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::mutex::Mutex; + +use super::{ + blocking_read, ensure_sector_aligned, family, get_sector, Async, Error, Flash, FlashLayout, FLASH_BASE, FLASH_SIZE, + WRITE_SIZE, +}; +use crate::interrupt::InterruptExt; +use crate::peripherals::FLASH; +use crate::{interrupt, Peripheral}; + +pub(super) static REGION_ACCESS: Mutex = Mutex::new(()); + +impl<'d> Flash<'d, Async> { + pub fn new( + p: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding + 'd, + ) -> Self { + into_ref!(p); + + crate::interrupt::FLASH.unpend(); + unsafe { crate::interrupt::FLASH.enable() }; + + Self { + inner: p, + _mode: PhantomData, + } + } + + pub fn into_regions(self) -> FlashLayout<'d, Async> { + assert!(family::is_default_layout()); + FlashLayout::new(self.inner) + } + + pub async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Error> { + unsafe { write_chunked(FLASH_BASE as u32, FLASH_SIZE as u32, offset, bytes).await } + } + + pub async fn erase(&mut self, from: u32, to: u32) -> Result<(), Error> { + unsafe { erase_sectored(FLASH_BASE as u32, from, to).await } + } +} + +/// Interrupt handler +pub struct InterruptHandler; + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + family::on_interrupt(); + } +} + +#[cfg(feature = "nightly")] +impl embedded_storage_async::nor_flash::ReadNorFlash for Flash<'_, Async> { + const READ_SIZE: usize = super::READ_SIZE; + + async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.read(offset, bytes) + } + + fn capacity(&self) -> usize { + FLASH_SIZE + } +} + +#[cfg(feature = "nightly")] +impl embedded_storage_async::nor_flash::NorFlash for Flash<'_, Async> { + const WRITE_SIZE: usize = WRITE_SIZE; + const ERASE_SIZE: usize = super::MAX_ERASE_SIZE; + + async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.write(offset, bytes).await + } + + async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.erase(from, to).await + } +} + +pub(super) async unsafe fn write_chunked(base: u32, size: u32, offset: u32, bytes: &[u8]) -> Result<(), Error> { + if offset + bytes.len() as u32 > size { + return Err(Error::Size); + } + if offset % WRITE_SIZE as u32 != 0 || bytes.len() % WRITE_SIZE != 0 { + return Err(Error::Unaligned); + } + + let mut address = base + offset; + trace!("Writing {} bytes at 0x{:x}", bytes.len(), address); + + for chunk in bytes.chunks(WRITE_SIZE) { + family::clear_all_err(); + fence(Ordering::SeqCst); + family::unlock(); + fence(Ordering::SeqCst); + family::enable_write(); + fence(Ordering::SeqCst); + + let _on_drop = OnDrop::new(|| { + family::disable_write(); + fence(Ordering::SeqCst); + family::lock(); + }); + + family::write(address, chunk.try_into().unwrap()).await?; + address += WRITE_SIZE as u32; + } + Ok(()) +} + +pub(super) async unsafe fn erase_sectored(base: u32, from: u32, to: u32) -> Result<(), Error> { + let start_address = base + from; + let end_address = base + to; + let regions = family::get_flash_regions(); + + ensure_sector_aligned(start_address, end_address, regions)?; + + trace!("Erasing from 0x{:x} to 0x{:x}", start_address, end_address); + + let mut address = start_address; + while address < end_address { + let sector = get_sector(address, regions); + trace!("Erasing sector: {:?}", sector); + + family::clear_all_err(); + fence(Ordering::SeqCst); + family::unlock(); + fence(Ordering::SeqCst); + + let _on_drop = OnDrop::new(|| family::lock()); + + family::erase_sector(§or).await?; + address += sector.size; + } + Ok(()) +} + +foreach_flash_region! { + ($type_name:ident, $write_size:literal, $erase_size:literal) => { + impl crate::_generated::flash_regions::$type_name<'_, Async> { + pub async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Error> { + blocking_read(self.0.base, self.0.size, offset, bytes) + } + + pub async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Error> { + let _guard = REGION_ACCESS.lock().await; + unsafe { write_chunked(self.0.base, self.0.size, offset, bytes).await } + } + + pub async fn erase(&mut self, from: u32, to: u32) -> Result<(), Error> { + let _guard = REGION_ACCESS.lock().await; + unsafe { erase_sectored(self.0.base, from, to).await } + } + } + + #[cfg(feature = "nightly")] + impl embedded_storage_async::nor_flash::ReadNorFlash for crate::_generated::flash_regions::$type_name<'_, Async> { + const READ_SIZE: usize = super::READ_SIZE; + + async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.read(offset, bytes).await + } + + fn capacity(&self) -> usize { + self.0.size as usize + } + } + + #[cfg(feature = "nightly")] + impl embedded_storage_async::nor_flash::NorFlash for crate::_generated::flash_regions::$type_name<'_, Async> { + const WRITE_SIZE: usize = $write_size; + const ERASE_SIZE: usize = $erase_size; + + async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.write(offset, bytes).await + } + + async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.erase(from, to).await + } + } + }; +} diff --git a/embassy-stm32/src/flash/common.rs b/embassy-stm32/src/flash/common.rs new file mode 100644 index 000000000..2a374733d --- /dev/null +++ b/embassy-stm32/src/flash/common.rs @@ -0,0 +1,277 @@ +use core::marker::PhantomData; +use core::sync::atomic::{fence, Ordering}; + +use embassy_hal_common::drop::OnDrop; +use embassy_hal_common::{into_ref, PeripheralRef}; +use stm32_metapac::FLASH_BASE; + +use super::{ + family, Async, Blocking, Error, FlashBank, FlashLayout, FlashRegion, FlashSector, FLASH_SIZE, MAX_ERASE_SIZE, + READ_SIZE, WRITE_SIZE, +}; +use crate::peripherals::FLASH; +use crate::Peripheral; + +pub struct Flash<'d, MODE = Async> { + pub(crate) inner: PeripheralRef<'d, FLASH>, + pub(crate) _mode: PhantomData, +} + +impl<'d> Flash<'d, Blocking> { + pub fn new_blocking(p: impl Peripheral

+ 'd) -> Self { + into_ref!(p); + + Self { + inner: p, + _mode: PhantomData, + } + } +} + +impl<'d, MODE> Flash<'d, MODE> { + pub fn into_blocking_regions(self) -> FlashLayout<'d, Blocking> { + assert!(family::is_default_layout()); + FlashLayout::new(self.inner) + } + + pub fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Error> { + blocking_read(FLASH_BASE as u32, FLASH_SIZE as u32, offset, bytes) + } + + pub fn blocking_write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Error> { + unsafe { + blocking_write( + FLASH_BASE as u32, + FLASH_SIZE as u32, + offset, + bytes, + write_chunk_unlocked, + ) + } + } + + pub fn blocking_erase(&mut self, from: u32, to: u32) -> Result<(), Error> { + unsafe { blocking_erase(FLASH_BASE as u32, from, to, erase_sector_unlocked) } + } +} + +pub(super) fn blocking_read(base: u32, size: u32, offset: u32, bytes: &mut [u8]) -> Result<(), Error> { + if offset + bytes.len() as u32 > size { + return Err(Error::Size); + } + + let start_address = base + offset; + + #[cfg(flash_f4)] + family::assert_not_corrupted_read(start_address + bytes.len() as u32); + + let flash_data = unsafe { core::slice::from_raw_parts(start_address as *const u8, bytes.len()) }; + bytes.copy_from_slice(flash_data); + Ok(()) +} + +pub(super) unsafe fn blocking_write( + base: u32, + size: u32, + offset: u32, + bytes: &[u8], + write_chunk: unsafe fn(u32, &[u8]) -> Result<(), Error>, +) -> Result<(), Error> { + if offset + bytes.len() as u32 > size { + return Err(Error::Size); + } + if offset % WRITE_SIZE as u32 != 0 || bytes.len() % WRITE_SIZE != 0 { + return Err(Error::Unaligned); + } + + let mut address = base + offset; + trace!("Writing {} bytes at 0x{:x}", bytes.len(), address); + + for chunk in bytes.chunks(WRITE_SIZE) { + write_chunk(address, chunk)?; + address += WRITE_SIZE as u32; + } + Ok(()) +} + +pub(super) unsafe fn write_chunk_unlocked(address: u32, chunk: &[u8]) -> Result<(), Error> { + family::clear_all_err(); + fence(Ordering::SeqCst); + family::unlock(); + fence(Ordering::SeqCst); + family::enable_blocking_write(); + fence(Ordering::SeqCst); + + let _on_drop = OnDrop::new(|| { + family::disable_blocking_write(); + fence(Ordering::SeqCst); + family::lock(); + }); + + family::blocking_write(address, chunk.try_into().unwrap()) +} + +pub(super) unsafe fn write_chunk_with_critical_section(address: u32, chunk: &[u8]) -> Result<(), Error> { + critical_section::with(|_| write_chunk_unlocked(address, chunk)) +} + +pub(super) unsafe fn blocking_erase( + base: u32, + from: u32, + to: u32, + erase_sector: unsafe fn(&FlashSector) -> Result<(), Error>, +) -> Result<(), Error> { + let start_address = base + from; + let end_address = base + to; + let regions = family::get_flash_regions(); + + ensure_sector_aligned(start_address, end_address, regions)?; + + trace!("Erasing from 0x{:x} to 0x{:x}", start_address, end_address); + + let mut address = start_address; + while address < end_address { + let sector = get_sector(address, regions); + trace!("Erasing sector: {:?}", sector); + erase_sector(§or)?; + address += sector.size; + } + Ok(()) +} + +pub(super) unsafe fn erase_sector_unlocked(sector: &FlashSector) -> Result<(), Error> { + family::clear_all_err(); + fence(Ordering::SeqCst); + family::unlock(); + fence(Ordering::SeqCst); + + let _on_drop = OnDrop::new(|| family::lock()); + + family::blocking_erase_sector(§or) +} + +pub(super) unsafe fn erase_sector_with_critical_section(sector: &FlashSector) -> Result<(), Error> { + critical_section::with(|_| erase_sector_unlocked(sector)) +} + +pub(super) fn get_sector(address: u32, regions: &[&FlashRegion]) -> FlashSector { + let mut current_bank = FlashBank::Bank1; + let mut bank_offset = 0; + for region in regions { + if region.bank != current_bank { + current_bank = region.bank; + bank_offset = 0; + } + + if address >= region.base && address < region.end() { + let index_in_region = (address - region.base) / region.erase_size; + return FlashSector { + bank: region.bank, + index_in_bank: bank_offset + index_in_region as u8, + start: region.base + index_in_region * region.erase_size, + size: region.erase_size, + }; + } + + bank_offset += region.sectors(); + } + + panic!("Flash sector not found"); +} + +pub(super) fn ensure_sector_aligned( + start_address: u32, + end_address: u32, + regions: &[&FlashRegion], +) -> Result<(), Error> { + let mut address = start_address; + while address < end_address { + let sector = get_sector(address, regions); + if sector.start != address { + return Err(Error::Unaligned); + } + address += sector.size; + } + if address != end_address { + return Err(Error::Unaligned); + } + Ok(()) +} + +impl embedded_storage::nor_flash::ErrorType for Flash<'_, MODE> { + type Error = Error; +} + +impl embedded_storage::nor_flash::ReadNorFlash for Flash<'_, MODE> { + const READ_SIZE: usize = READ_SIZE; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.read(offset, bytes) + } + + fn capacity(&self) -> usize { + FLASH_SIZE + } +} + +impl embedded_storage::nor_flash::NorFlash for Flash<'_, MODE> { + const WRITE_SIZE: usize = WRITE_SIZE; + const ERASE_SIZE: usize = MAX_ERASE_SIZE; + + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(offset, bytes) + } + + fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.blocking_erase(from, to) + } +} + +foreach_flash_region! { + ($type_name:ident, $write_size:literal, $erase_size:literal) => { + impl crate::_generated::flash_regions::$type_name<'_, MODE> { + pub fn blocking_read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Error> { + blocking_read(self.0.base, self.0.size, offset, bytes) + } + } + + impl crate::_generated::flash_regions::$type_name<'_, Blocking> { + pub fn blocking_write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Error> { + unsafe { blocking_write(self.0.base, self.0.size, offset, bytes, write_chunk_with_critical_section) } + } + + pub fn blocking_erase(&mut self, from: u32, to: u32) -> Result<(), Error> { + unsafe { blocking_erase(self.0.base, from, to, erase_sector_with_critical_section) } + } + } + + impl embedded_storage::nor_flash::ErrorType for crate::_generated::flash_regions::$type_name<'_, MODE> { + type Error = Error; + } + + impl embedded_storage::nor_flash::ReadNorFlash for crate::_generated::flash_regions::$type_name<'_, MODE> { + const READ_SIZE: usize = READ_SIZE; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_read(offset, bytes) + } + + fn capacity(&self) -> usize { + self.0.size as usize + } + } + + impl embedded_storage::nor_flash::NorFlash for crate::_generated::flash_regions::$type_name<'_, Blocking> { + const WRITE_SIZE: usize = $write_size; + const ERASE_SIZE: usize = $erase_size; + + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(offset, bytes) + } + + fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.blocking_erase(from, to) + } + } + }; +} diff --git a/embassy-stm32/src/flash/f0.rs b/embassy-stm32/src/flash/f0.rs new file mode 100644 index 000000000..ec8343e7c --- /dev/null +++ b/embassy-stm32/src/flash/f0.rs @@ -0,0 +1,108 @@ +use core::convert::TryInto; +use core::ptr::write_volatile; +use core::sync::atomic::{fence, Ordering}; + +use super::{FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; +use crate::flash::Error; +use crate::pac; + +pub const fn is_default_layout() -> bool { + true +} + +pub const fn get_flash_regions() -> &'static [&'static FlashRegion] { + &FLASH_REGIONS +} + +pub(crate) unsafe fn lock() { + pac::FLASH.cr().modify(|w| w.set_lock(true)); +} + +pub(crate) unsafe fn unlock() { + pac::FLASH.keyr().write(|w| w.set_fkeyr(0x4567_0123)); + pac::FLASH.keyr().write(|w| w.set_fkeyr(0xCDEF_89AB)); +} + +pub(crate) unsafe fn enable_blocking_write() { + assert_eq!(0, WRITE_SIZE % 2); + + pac::FLASH.cr().write(|w| w.set_pg(true)); +} + +pub(crate) unsafe fn disable_blocking_write() { + pac::FLASH.cr().write(|w| w.set_pg(false)); +} + +pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) -> Result<(), Error> { + let mut address = start_address; + for chunk in buf.chunks(2) { + write_volatile(address as *mut u16, u16::from_le_bytes(chunk.try_into().unwrap())); + address += chunk.len() as u32; + + // prevents parallelism errors + fence(Ordering::SeqCst); + } + + wait_ready_blocking() +} + +pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), Error> { + pac::FLASH.cr().modify(|w| { + w.set_per(true); + }); + + pac::FLASH.ar().write(|w| w.set_far(sector.start)); + + pac::FLASH.cr().modify(|w| { + w.set_strt(true); + }); + + let mut ret: Result<(), Error> = wait_ready_blocking(); + + if !pac::FLASH.sr().read().eop() { + trace!("FLASH: EOP not set"); + ret = Err(Error::Prog); + } else { + pac::FLASH.sr().write(|w| w.set_eop(true)); + } + + pac::FLASH.cr().modify(|w| w.set_per(false)); + + clear_all_err(); + if ret.is_err() { + return ret; + } + Ok(()) +} + +pub(crate) unsafe fn clear_all_err() { + pac::FLASH.sr().modify(|w| { + if w.pgerr() { + w.set_pgerr(true); + } + if w.wrprt() { + w.set_wrprt(true) + }; + if w.eop() { + w.set_eop(true); + } + }); +} + +unsafe fn wait_ready_blocking() -> Result<(), Error> { + loop { + let sr = pac::FLASH.sr().read(); + + if !sr.bsy() { + if sr.wrprt() { + return Err(Error::Protected); + } + + if sr.pgerr() { + return Err(Error::Seq); + } + + return Ok(()); + } + } +} diff --git a/embassy-stm32/src/flash/f3.rs b/embassy-stm32/src/flash/f3.rs index 1cb08ee1a..40335d643 100644 --- a/embassy-stm32/src/flash/f3.rs +++ b/embassy-stm32/src/flash/f3.rs @@ -1,9 +1,19 @@ use core::convert::TryInto; use core::ptr::write_volatile; +use core::sync::atomic::{fence, Ordering}; +use super::{FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; use crate::flash::Error; use crate::pac; +pub const fn is_default_layout() -> bool { + true +} + +pub const fn get_flash_regions() -> &'static [&'static FlashRegion] { + &FLASH_REGIONS +} + pub(crate) unsafe fn lock() { pac::FLASH.cr().modify(|w| w.set_lock(true)); } @@ -13,58 +23,55 @@ pub(crate) unsafe fn unlock() { pac::FLASH.keyr().write(|w| w.set_fkeyr(0xCDEF_89AB)); } -pub(crate) unsafe fn blocking_write(offset: u32, buf: &[u8]) -> Result<(), Error> { +pub(crate) unsafe fn enable_blocking_write() { + assert_eq!(0, WRITE_SIZE % 2); + pac::FLASH.cr().write(|w| w.set_pg(true)); - - let ret = { - let mut ret: Result<(), Error> = Ok(()); - let mut offset = offset; - for chunk in buf.chunks(2) { - write_volatile(offset as *mut u16, u16::from_le_bytes(chunk[0..2].try_into().unwrap())); - offset += chunk.len() as u32; - - ret = blocking_wait_ready(); - if ret.is_err() { - break; - } - } - ret - }; - - pac::FLASH.cr().write(|w| w.set_pg(false)); - - ret } -pub(crate) unsafe fn blocking_erase(from: u32, to: u32) -> Result<(), Error> { - for page in (from..to).step_by(super::ERASE_SIZE) { - pac::FLASH.cr().modify(|w| { - w.set_per(true); - }); +pub(crate) unsafe fn disable_blocking_write() { + pac::FLASH.cr().write(|w| w.set_pg(false)); +} - pac::FLASH.ar().write(|w| w.set_far(page)); +pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) -> Result<(), Error> { + let mut address = start_address; + for chunk in buf.chunks(2) { + write_volatile(address as *mut u16, u16::from_le_bytes(chunk.try_into().unwrap())); + address += chunk.len() as u32; - pac::FLASH.cr().modify(|w| { - w.set_strt(true); - }); - - let mut ret: Result<(), Error> = blocking_wait_ready(); - - if !pac::FLASH.sr().read().eop() { - trace!("FLASH: EOP not set"); - ret = Err(Error::Prog); - } else { - pac::FLASH.sr().write(|w| w.set_eop(true)); - } - - pac::FLASH.cr().modify(|w| w.set_per(false)); - - clear_all_err(); - if ret.is_err() { - return ret; - } + // prevents parallelism errors + fence(Ordering::SeqCst); } + wait_ready_blocking() +} + +pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), Error> { + pac::FLASH.cr().modify(|w| { + w.set_per(true); + }); + + pac::FLASH.ar().write(|w| w.set_far(sector.start)); + + pac::FLASH.cr().modify(|w| { + w.set_strt(true); + }); + + let mut ret: Result<(), Error> = wait_ready_blocking(); + + if !pac::FLASH.sr().read().eop() { + trace!("FLASH: EOP not set"); + ret = Err(Error::Prog); + } else { + pac::FLASH.sr().write(|w| w.set_eop(true)); + } + + pac::FLASH.cr().modify(|w| w.set_per(false)); + + clear_all_err(); + if ret.is_err() { + return ret; + } Ok(()) } @@ -82,7 +89,7 @@ pub(crate) unsafe fn clear_all_err() { }); } -pub(crate) unsafe fn blocking_wait_ready() -> Result<(), Error> { +unsafe fn wait_ready_blocking() -> Result<(), Error> { loop { let sr = pac::FLASH.sr().read(); diff --git a/embassy-stm32/src/flash/f4.rs b/embassy-stm32/src/flash/f4.rs index b8327ce4e..4cb39e033 100644 --- a/embassy-stm32/src/flash/f4.rs +++ b/embassy-stm32/src/flash/f4.rs @@ -1,29 +1,226 @@ use core::convert::TryInto; use core::ptr::write_volatile; +use core::sync::atomic::{fence, AtomicBool, Ordering}; -use atomic_polyfill::{fence, Ordering}; +use embassy_sync::waitqueue::AtomicWaker; +use pac::flash::regs::Sr; +use pac::FLASH_SIZE; -use super::{ERASE_SIZE, FLASH_BASE, FLASH_SIZE}; +use super::{FlashBank, FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; use crate::flash::Error; use crate::pac; -const SECOND_BANK_SECTOR_START: u32 = 12; +#[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f469, stm32f479))] +mod alt_regions { + use core::marker::PhantomData; -unsafe fn is_dual_bank() -> bool { - match FLASH_SIZE / 1024 { - // 1 MB devices depend on configuration - 1024 => { - if cfg!(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f469, stm32f479)) { - pac::FLASH.optcr().read().db1m() - } else { - false + use embassy_hal_common::PeripheralRef; + use stm32_metapac::FLASH_SIZE; + + use crate::_generated::flash_regions::{OTPRegion, BANK1_REGION1, BANK1_REGION2, BANK1_REGION3, OTP_REGION}; + use crate::flash::{asynch, Async, Bank1Region1, Bank1Region2, Blocking, Error, Flash, FlashBank, FlashRegion}; + use crate::peripherals::FLASH; + + pub const ALT_BANK1_REGION3: FlashRegion = FlashRegion { + size: 3 * BANK1_REGION3.erase_size, + ..BANK1_REGION3 + }; + pub const ALT_BANK2_REGION1: FlashRegion = FlashRegion { + bank: FlashBank::Bank2, + base: BANK1_REGION1.base + FLASH_SIZE as u32 / 2, + ..BANK1_REGION1 + }; + pub const ALT_BANK2_REGION2: FlashRegion = FlashRegion { + bank: FlashBank::Bank2, + base: BANK1_REGION2.base + FLASH_SIZE as u32 / 2, + ..BANK1_REGION2 + }; + pub const ALT_BANK2_REGION3: FlashRegion = FlashRegion { + bank: FlashBank::Bank2, + base: BANK1_REGION3.base + FLASH_SIZE as u32 / 2, + size: 3 * BANK1_REGION3.erase_size, + ..BANK1_REGION3 + }; + + pub const ALT_FLASH_REGIONS: [&FlashRegion; 6] = [ + &BANK1_REGION1, + &BANK1_REGION2, + &ALT_BANK1_REGION3, + &ALT_BANK2_REGION1, + &ALT_BANK2_REGION2, + &ALT_BANK2_REGION3, + ]; + + pub struct AltBank1Region3<'d, MODE = Async>(pub &'static FlashRegion, PeripheralRef<'d, FLASH>, PhantomData); + pub struct AltBank2Region1<'d, MODE = Async>(pub &'static FlashRegion, PeripheralRef<'d, FLASH>, PhantomData); + pub struct AltBank2Region2<'d, MODE = Async>(pub &'static FlashRegion, PeripheralRef<'d, FLASH>, PhantomData); + pub struct AltBank2Region3<'d, MODE = Async>(pub &'static FlashRegion, PeripheralRef<'d, FLASH>, PhantomData); + + pub struct AltFlashLayout<'d, MODE = Async> { + pub bank1_region1: Bank1Region1<'d, MODE>, + pub bank1_region2: Bank1Region2<'d, MODE>, + pub bank1_region3: AltBank1Region3<'d, MODE>, + pub bank2_region1: AltBank2Region1<'d, MODE>, + pub bank2_region2: AltBank2Region2<'d, MODE>, + pub bank2_region3: AltBank2Region3<'d, MODE>, + pub otp_region: OTPRegion<'d, MODE>, + } + + impl<'d> Flash<'d> { + pub fn into_alt_regions(self) -> AltFlashLayout<'d, Async> { + assert!(!super::is_default_layout()); + + // SAFETY: We never expose the cloned peripheral references, and their instance is not public. + // Also, all async flash region operations are protected with a mutex. + let p = self.inner; + AltFlashLayout { + bank1_region1: Bank1Region1(&BANK1_REGION1, unsafe { p.clone_unchecked() }, PhantomData), + bank1_region2: Bank1Region2(&BANK1_REGION2, unsafe { p.clone_unchecked() }, PhantomData), + bank1_region3: AltBank1Region3(&ALT_BANK1_REGION3, unsafe { p.clone_unchecked() }, PhantomData), + bank2_region1: AltBank2Region1(&ALT_BANK2_REGION1, unsafe { p.clone_unchecked() }, PhantomData), + bank2_region2: AltBank2Region2(&ALT_BANK2_REGION2, unsafe { p.clone_unchecked() }, PhantomData), + bank2_region3: AltBank2Region3(&ALT_BANK2_REGION3, unsafe { p.clone_unchecked() }, PhantomData), + otp_region: OTPRegion(&OTP_REGION, unsafe { p.clone_unchecked() }, PhantomData), + } + } + + pub fn into_alt_blocking_regions(self) -> AltFlashLayout<'d, Blocking> { + assert!(!super::is_default_layout()); + + // SAFETY: We never expose the cloned peripheral references, and their instance is not public. + // Also, all blocking flash region operations are protected with a cs. + let p = self.inner; + AltFlashLayout { + bank1_region1: Bank1Region1(&BANK1_REGION1, unsafe { p.clone_unchecked() }, PhantomData), + bank1_region2: Bank1Region2(&BANK1_REGION2, unsafe { p.clone_unchecked() }, PhantomData), + bank1_region3: AltBank1Region3(&ALT_BANK1_REGION3, unsafe { p.clone_unchecked() }, PhantomData), + bank2_region1: AltBank2Region1(&ALT_BANK2_REGION1, unsafe { p.clone_unchecked() }, PhantomData), + bank2_region2: AltBank2Region2(&ALT_BANK2_REGION2, unsafe { p.clone_unchecked() }, PhantomData), + bank2_region3: AltBank2Region3(&ALT_BANK2_REGION3, unsafe { p.clone_unchecked() }, PhantomData), + otp_region: OTPRegion(&OTP_REGION, unsafe { p.clone_unchecked() }, PhantomData), } } - // 2 MB devices are always dual bank - 2048 => true, - // All other devices are single bank - _ => false, } + + macro_rules! foreach_altflash_region { + ($type_name:ident, $region:ident) => { + impl $type_name<'_, MODE> { + pub fn blocking_read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Error> { + crate::flash::common::blocking_read(self.0.base, self.0.size, offset, bytes) + } + } + + impl $type_name<'_, Async> { + pub async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Error> { + self.blocking_read(offset, bytes) + } + + pub async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Error> { + let _guard = asynch::REGION_ACCESS.lock().await; + unsafe { asynch::write_chunked(self.0.base, self.0.size, offset, bytes).await } + } + + pub async fn erase(&mut self, from: u32, to: u32) -> Result<(), Error> { + let _guard = asynch::REGION_ACCESS.lock().await; + unsafe { asynch::erase_sectored(self.0.base, from, to).await } + } + } + + impl embedded_storage::nor_flash::ErrorType for $type_name<'_, MODE> { + type Error = Error; + } + + impl embedded_storage::nor_flash::ReadNorFlash for $type_name<'_, MODE> { + const READ_SIZE: usize = crate::flash::READ_SIZE; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_read(offset, bytes) + } + + fn capacity(&self) -> usize { + self.0.size as usize + } + } + + #[cfg(feature = "nightly")] + impl embedded_storage_async::nor_flash::ReadNorFlash for $type_name<'_, Async> { + const READ_SIZE: usize = crate::flash::READ_SIZE; + + async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + self.read(offset, bytes).await + } + + fn capacity(&self) -> usize { + self.0.size as usize + } + } + + #[cfg(feature = "nightly")] + impl embedded_storage_async::nor_flash::NorFlash for $type_name<'_, Async> { + const WRITE_SIZE: usize = $region.write_size as usize; + const ERASE_SIZE: usize = $region.erase_size as usize; + + async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.write(offset, bytes).await + } + + async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + self.erase(from, to).await + } + } + }; + } + + foreach_altflash_region!(AltBank1Region3, ALT_BANK1_REGION3); + foreach_altflash_region!(AltBank2Region1, ALT_BANK2_REGION1); + foreach_altflash_region!(AltBank2Region2, ALT_BANK2_REGION2); + foreach_altflash_region!(AltBank2Region3, ALT_BANK2_REGION3); +} + +#[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f469, stm32f479))] +pub use alt_regions::*; + +static WAKER: AtomicWaker = AtomicWaker::new(); +static DATA_CACHE_WAS_ENABLED: AtomicBool = AtomicBool::new(false); + +impl FlashSector { + const fn snb(&self) -> u8 { + ((self.bank as u8) << 4) + self.index_in_bank + } +} + +#[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f469, stm32f479))] +pub(crate) fn is_default_layout() -> bool { + !pac::FLASH.optcr().read().db1m() +} + +#[cfg(not(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f469, stm32f479)))] +pub(crate) const fn is_default_layout() -> bool { + true +} + +#[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f469, stm32f479))] +pub fn get_flash_regions() -> &'static [&'static FlashRegion] { + if is_default_layout() { + &FLASH_REGIONS + } else { + &ALT_FLASH_REGIONS + } +} + +#[cfg(not(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f469, stm32f479)))] +pub const fn get_flash_regions() -> &'static [&'static FlashRegion] { + &FLASH_REGIONS +} + +pub(crate) unsafe fn on_interrupt() { + // Clear IRQ flags + pac::FLASH.sr().write(|w| { + w.set_operr(true); + w.set_eop(true); + }); + + WAKER.wake(); } pub(crate) unsafe fn lock() { @@ -31,101 +228,101 @@ pub(crate) unsafe fn lock() { } pub(crate) unsafe fn unlock() { - pac::FLASH.keyr().write(|w| w.set_key(0x4567_0123)); - pac::FLASH.keyr().write(|w| w.set_key(0xCDEF_89AB)); + pac::FLASH.keyr().write(|w| w.set_key(0x45670123)); + pac::FLASH.keyr().write(|w| w.set_key(0xCDEF89AB)); } -pub(crate) unsafe fn blocking_write(offset: u32, buf: &[u8]) -> Result<(), Error> { +pub(crate) unsafe fn enable_write() { + assert_eq!(0, WRITE_SIZE % 4); + save_data_cache_state(); + + pac::FLASH.cr().write(|w| { + w.set_pg(true); + w.set_psize(pac::flash::vals::Psize::PSIZE32); + w.set_eopie(true); + w.set_errie(true); + }); +} + +pub(crate) unsafe fn disable_write() { + pac::FLASH.cr().write(|w| { + w.set_pg(false); + w.set_eopie(false); + w.set_errie(false); + }); + restore_data_cache_state(); +} + +pub(crate) unsafe fn enable_blocking_write() { + assert_eq!(0, WRITE_SIZE % 4); + save_data_cache_state(); + pac::FLASH.cr().write(|w| { w.set_pg(true); w.set_psize(pac::flash::vals::Psize::PSIZE32); }); +} - let ret = { - let mut ret: Result<(), Error> = Ok(()); - let mut offset = offset; - for chunk in buf.chunks(super::WRITE_SIZE) { - for val in chunk.chunks(4) { - write_volatile(offset as *mut u32, u32::from_le_bytes(val[0..4].try_into().unwrap())); - offset += val.len() as u32; - - // prevents parallelism errors - fence(Ordering::SeqCst); - } - - ret = blocking_wait_ready(); - if ret.is_err() { - break; - } - } - ret - }; - +pub(crate) unsafe fn disable_blocking_write() { pac::FLASH.cr().write(|w| w.set_pg(false)); - - ret + restore_data_cache_state(); } -struct FlashSector { - index: u8, - size: u32, +pub(crate) async unsafe fn write(start_address: u32, buf: &[u8; WRITE_SIZE]) -> Result<(), Error> { + write_start(start_address, buf); + wait_ready().await } -fn get_sector(addr: u32, dual_bank: bool) -> FlashSector { - let offset = addr - FLASH_BASE as u32; - - let bank_size = match dual_bank { - true => FLASH_SIZE / 2, - false => FLASH_SIZE, - } as u32; - - let bank = offset / bank_size; - let offset_in_bank = offset % bank_size; - - let index_in_bank = if offset_in_bank >= ERASE_SIZE as u32 / 2 { - 4 + offset_in_bank / ERASE_SIZE as u32 - } else { - offset_in_bank / (ERASE_SIZE as u32 / 8) - }; - - // First 4 sectors are 16KB, then one 64KB, and rest are 128KB - let size = match index_in_bank { - 0..=3 => 16 * 1024, - 4 => 64 * 1024, - _ => 128 * 1024, - }; - - let index = if bank == 1 { - SECOND_BANK_SECTOR_START + index_in_bank - } else { - index_in_bank - } as u8; - - FlashSector { index, size } +pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) -> Result<(), Error> { + write_start(start_address, buf); + blocking_wait_ready() } -pub(crate) unsafe fn blocking_erase(from: u32, to: u32) -> Result<(), Error> { - let mut addr = from; - let dual_bank = is_dual_bank(); +unsafe fn write_start(start_address: u32, buf: &[u8; WRITE_SIZE]) { + let mut address = start_address; + for val in buf.chunks(4) { + write_volatile(address as *mut u32, u32::from_le_bytes(val.try_into().unwrap())); + address += val.len() as u32; - while addr < to { - let sector = get_sector(addr, dual_bank); - erase_sector(sector.index)?; - addr += sector.size; + // prevents parallelism errors + fence(Ordering::SeqCst); } - - Ok(()) } -unsafe fn erase_sector(sector: u8) -> Result<(), Error> { - let bank = sector / SECOND_BANK_SECTOR_START as u8; - let snb = (bank << 4) + (sector % SECOND_BANK_SECTOR_START as u8); +pub(crate) async unsafe fn erase_sector(sector: &FlashSector) -> Result<(), Error> { + save_data_cache_state(); - trace!("Erasing sector: {}", sector); + trace!("Erasing sector number {}", sector.snb()); pac::FLASH.cr().modify(|w| { w.set_ser(true); - w.set_snb(snb) + w.set_snb(sector.snb()); + w.set_eopie(true); + w.set_errie(true); + }); + + pac::FLASH.cr().modify(|w| { + w.set_strt(true); + }); + + let ret: Result<(), Error> = wait_ready().await; + pac::FLASH.cr().modify(|w| { + w.set_eopie(false); + w.set_errie(false); + }); + clear_all_err(); + restore_data_cache_state(); + ret +} + +pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), Error> { + save_data_cache_state(); + + trace!("Blocking erasing sector number {}", sector.snb()); + + pac::FLASH.cr().modify(|w| { + w.set_ser(true); + w.set_snb(sector.snb()) }); pac::FLASH.cr().modify(|w| { @@ -133,44 +330,234 @@ unsafe fn erase_sector(sector: u8) -> Result<(), Error> { }); let ret: Result<(), Error> = blocking_wait_ready(); - clear_all_err(); - + restore_data_cache_state(); ret } -pub(crate) unsafe fn clear_all_err() { +pub(crate) fn clear_all_err() { pac::FLASH.sr().write(|w| { w.set_pgserr(true); w.set_pgperr(true); w.set_pgaerr(true); w.set_wrperr(true); - w.set_eop(true); }); } -pub(crate) unsafe fn blocking_wait_ready() -> Result<(), Error> { +pub(crate) async fn wait_ready() -> Result<(), Error> { + use core::task::Poll; + + use futures::future::poll_fn; + + poll_fn(|cx| { + WAKER.register(cx.waker()); + + let sr = pac::FLASH.sr().read(); + if !sr.bsy() { + Poll::Ready(get_result(sr)) + } else { + return Poll::Pending; + } + }) + .await +} + +unsafe fn blocking_wait_ready() -> Result<(), Error> { loop { let sr = pac::FLASH.sr().read(); if !sr.bsy() { - if sr.pgserr() { - return Err(Error::Seq); - } - - if sr.pgperr() { - return Err(Error::Parallelism); - } - - if sr.pgaerr() { - return Err(Error::Unaligned); - } - - if sr.wrperr() { - return Err(Error::Protected); - } - - return Ok(()); + return get_result(sr); } } } + +fn get_result(sr: Sr) -> Result<(), Error> { + if sr.pgserr() { + Err(Error::Seq) + } else if sr.pgperr() { + Err(Error::Parallelism) + } else if sr.pgaerr() { + Err(Error::Unaligned) + } else if sr.wrperr() { + Err(Error::Protected) + } else { + Ok(()) + } +} + +fn save_data_cache_state() { + let dual_bank = get_flash_regions().last().unwrap().bank == FlashBank::Bank2; + if dual_bank { + // Disable data cache during write/erase if there are two banks, see errata 2.2.12 + let dcen = pac::FLASH.acr().read().dcen(); + DATA_CACHE_WAS_ENABLED.store(dcen, Ordering::Relaxed); + if dcen { + pac::FLASH.acr().modify(|w| w.set_dcen(false)); + } + } +} + +fn restore_data_cache_state() { + let dual_bank = get_flash_regions().last().unwrap().bank == FlashBank::Bank2; + if dual_bank { + // Restore data cache if it was enabled + let dcen = DATA_CACHE_WAS_ENABLED.load(Ordering::Relaxed); + if dcen { + // Reset data cache before we enable it again + pac::FLASH.acr().modify(|w| w.set_dcrst(true)); + pac::FLASH.acr().modify(|w| w.set_dcrst(false)); + pac::FLASH.acr().modify(|w| w.set_dcen(true)) + } + } +} + +pub(crate) fn assert_not_corrupted_read(end_address: u32) { + #[allow(unused)] + const REVISION_3: u16 = 0x2001; + + #[allow(unused)] + let second_bank_read = + get_flash_regions().last().unwrap().bank == FlashBank::Bank2 && end_address > (FLASH_SIZE / 2) as u32; + + #[cfg(any( + feature = "stm32f427ai", + feature = "stm32f427ii", + feature = "stm32f427vi", + feature = "stm32f427zi", + feature = "stm32f429ai", + feature = "stm32f429bi", + feature = "stm32f429ii", + feature = "stm32f429ni", + feature = "stm32f429vi", + feature = "stm32f429zi", + feature = "stm32f437ai", + feature = "stm32f437ii", + feature = "stm32f437vi", + feature = "stm32f437zi", + feature = "stm32f439ai", + feature = "stm32f439bi", + feature = "stm32f439ii", + feature = "stm32f439ni", + feature = "stm32f439vi", + feature = "stm32f439zi", + ))] + if second_bank_read && pac::DBGMCU.idcode().read().rev_id() < REVISION_3 && !pa12_is_output_pull_low() { + panic!("Read corruption for stm32f42xxI and stm32f43xxI when PA12 is in use for chips below revision 3, see errata 2.2.11"); + } + + #[cfg(any( + feature = "stm32f427ag", + feature = "stm32f427ig", + feature = "stm32f427vg", + feature = "stm32f427zg", + feature = "stm32f429ag", + feature = "stm32f429bg", + feature = "stm32f429ig", + feature = "stm32f429ng", + feature = "stm32f429vg", + feature = "stm32f429zg", + feature = "stm32f437ig", + feature = "stm32f437vg", + feature = "stm32f437zg", + feature = "stm32f439bg", + feature = "stm32f439ig", + feature = "stm32f439ng", + feature = "stm32f439vg", + feature = "stm32f439zg", + ))] + if second_bank_read && unsafe { pac::DBGMCU.idcode().read().rev_id() < REVISION_3 && !pa12_is_output_pull_low() } { + panic!("Read corruption for stm32f42xxG and stm32f43xxG in dual bank mode when PA12 is in use for chips below revision 3, see errata 2.2.11"); + } +} + +#[allow(unused)] +fn pa12_is_output_pull_low() -> bool { + use pac::gpio::vals; + use pac::GPIOA; + const PIN: usize = 12; + GPIOA.moder().read().moder(PIN) == vals::Moder::OUTPUT + && GPIOA.pupdr().read().pupdr(PIN) == vals::Pupdr::PULLDOWN + && GPIOA.odr().read().odr(PIN) == vals::Odr::LOW +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::flash::{get_sector, FlashBank}; + + #[test] + #[cfg(stm32f429)] + fn can_get_sector() { + const SMALL_SECTOR_SIZE: u32 = 16 * 1024; + const MEDIUM_SECTOR_SIZE: u32 = 64 * 1024; + const LARGE_SECTOR_SIZE: u32 = 128 * 1024; + + let assert_sector = |snb: u8, index_in_bank: u8, start: u32, size: u32, address: u32| { + let sector = get_sector(address, &FLASH_REGIONS); + assert_eq!(snb, sector.snb()); + assert_eq!( + FlashSector { + bank: FlashBank::Bank1, + index_in_bank, + start, + size + }, + sector + ); + }; + + assert_sector(0x00, 0, 0x0800_0000, SMALL_SECTOR_SIZE, 0x0800_0000); + assert_sector(0x00, 0, 0x0800_0000, SMALL_SECTOR_SIZE, 0x0800_3FFF); + assert_sector(0x03, 3, 0x0800_C000, SMALL_SECTOR_SIZE, 0x0800_C000); + assert_sector(0x03, 3, 0x0800_C000, SMALL_SECTOR_SIZE, 0x0800_FFFF); + + assert_sector(0x04, 4, 0x0801_0000, MEDIUM_SECTOR_SIZE, 0x0801_0000); + assert_sector(0x04, 4, 0x0801_0000, MEDIUM_SECTOR_SIZE, 0x0801_FFFF); + + assert_sector(0x05, 5, 0x0802_0000, LARGE_SECTOR_SIZE, 0x0802_0000); + assert_sector(0x05, 5, 0x0802_0000, LARGE_SECTOR_SIZE, 0x0803_FFFF); + assert_sector(0x0B, 11, 0x080E_0000, LARGE_SECTOR_SIZE, 0x080E_0000); + assert_sector(0x0B, 11, 0x080E_0000, LARGE_SECTOR_SIZE, 0x080F_FFFF); + + let assert_sector = |snb: u8, bank: FlashBank, index_in_bank: u8, start: u32, size: u32, address: u32| { + let sector = get_sector(address, &ALT_FLASH_REGIONS); + assert_eq!(snb, sector.snb()); + assert_eq!( + FlashSector { + bank, + index_in_bank, + start, + size + }, + sector + ) + }; + + assert_sector(0x00, FlashBank::Bank1, 0, 0x0800_0000, SMALL_SECTOR_SIZE, 0x0800_0000); + assert_sector(0x00, FlashBank::Bank1, 0, 0x0800_0000, SMALL_SECTOR_SIZE, 0x0800_3FFF); + assert_sector(0x03, FlashBank::Bank1, 3, 0x0800_C000, SMALL_SECTOR_SIZE, 0x0800_C000); + assert_sector(0x03, FlashBank::Bank1, 3, 0x0800_C000, SMALL_SECTOR_SIZE, 0x0800_FFFF); + + assert_sector(0x04, FlashBank::Bank1, 4, 0x0801_0000, MEDIUM_SECTOR_SIZE, 0x0801_0000); + assert_sector(0x04, FlashBank::Bank1, 4, 0x0801_0000, MEDIUM_SECTOR_SIZE, 0x0801_FFFF); + + assert_sector(0x05, FlashBank::Bank1, 5, 0x0802_0000, LARGE_SECTOR_SIZE, 0x0802_0000); + assert_sector(0x05, FlashBank::Bank1, 5, 0x0802_0000, LARGE_SECTOR_SIZE, 0x0803_FFFF); + assert_sector(0x07, FlashBank::Bank1, 7, 0x0806_0000, LARGE_SECTOR_SIZE, 0x0806_0000); + assert_sector(0x07, FlashBank::Bank1, 7, 0x0806_0000, LARGE_SECTOR_SIZE, 0x0807_FFFF); + + assert_sector(0x10, FlashBank::Bank2, 0, 0x0808_0000, SMALL_SECTOR_SIZE, 0x0808_0000); + assert_sector(0x10, FlashBank::Bank2, 0, 0x0808_0000, SMALL_SECTOR_SIZE, 0x0808_3FFF); + assert_sector(0x13, FlashBank::Bank2, 3, 0x0808_C000, SMALL_SECTOR_SIZE, 0x0808_C000); + assert_sector(0x13, FlashBank::Bank2, 3, 0x0808_C000, SMALL_SECTOR_SIZE, 0x0808_FFFF); + + assert_sector(0x14, FlashBank::Bank2, 4, 0x0809_0000, MEDIUM_SECTOR_SIZE, 0x0809_0000); + assert_sector(0x14, FlashBank::Bank2, 4, 0x0809_0000, MEDIUM_SECTOR_SIZE, 0x0809_FFFF); + + assert_sector(0x15, FlashBank::Bank2, 5, 0x080A_0000, LARGE_SECTOR_SIZE, 0x080A_0000); + assert_sector(0x15, FlashBank::Bank2, 5, 0x080A_0000, LARGE_SECTOR_SIZE, 0x080B_FFFF); + assert_sector(0x17, FlashBank::Bank2, 7, 0x080E_0000, LARGE_SECTOR_SIZE, 0x080E_0000); + assert_sector(0x17, FlashBank::Bank2, 7, 0x080E_0000, LARGE_SECTOR_SIZE, 0x080F_FFFF); + } +} diff --git a/embassy-stm32/src/flash/f7.rs b/embassy-stm32/src/flash/f7.rs index 6d47b78a0..1a6d6bb03 100644 --- a/embassy-stm32/src/flash/f7.rs +++ b/embassy-stm32/src/flash/f7.rs @@ -1,11 +1,19 @@ use core::convert::TryInto; use core::ptr::write_volatile; +use core::sync::atomic::{fence, Ordering}; -use atomic_polyfill::{fence, Ordering}; - +use super::{FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; use crate::flash::Error; use crate::pac; +pub const fn is_default_layout() -> bool { + true +} + +pub const fn get_flash_regions() -> &'static [&'static FlashRegion] { + &FLASH_REGIONS +} + pub(crate) unsafe fn lock() { pac::FLASH.cr().modify(|w| w.set_lock(true)); } @@ -15,64 +23,36 @@ pub(crate) unsafe fn unlock() { pac::FLASH.keyr().write(|w| w.set_key(0xCDEF_89AB)); } -pub(crate) unsafe fn blocking_write(offset: u32, buf: &[u8]) -> Result<(), Error> { +pub(crate) unsafe fn enable_blocking_write() { + assert_eq!(0, WRITE_SIZE % 4); + pac::FLASH.cr().write(|w| { w.set_pg(true); w.set_psize(pac::flash::vals::Psize::PSIZE32); }); - - let ret = { - let mut ret: Result<(), Error> = Ok(()); - let mut offset = offset; - for chunk in buf.chunks(super::WRITE_SIZE) { - for val in chunk.chunks(4) { - write_volatile(offset as *mut u32, u32::from_le_bytes(val[0..4].try_into().unwrap())); - offset += val.len() as u32; - - // prevents parallelism errors - fence(Ordering::SeqCst); - } - - ret = blocking_wait_ready(); - if ret.is_err() { - break; - } - } - ret - }; - - pac::FLASH.cr().write(|w| w.set_pg(false)); - - ret } -pub(crate) unsafe fn blocking_erase(from: u32, to: u32) -> Result<(), Error> { - let start_sector = if from >= (super::FLASH_BASE + super::ERASE_SIZE / 2) as u32 { - 4 + (from - super::FLASH_BASE as u32) / super::ERASE_SIZE as u32 - } else { - (from - super::FLASH_BASE as u32) / (super::ERASE_SIZE as u32 / 8) - }; +pub(crate) unsafe fn disable_blocking_write() { + pac::FLASH.cr().write(|w| w.set_pg(false)); +} - let end_sector = if to >= (super::FLASH_BASE + super::ERASE_SIZE / 2) as u32 { - 4 + (to - super::FLASH_BASE as u32) / super::ERASE_SIZE as u32 - } else { - (to - super::FLASH_BASE as u32) / (super::ERASE_SIZE as u32 / 8) - }; +pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) -> Result<(), Error> { + let mut address = start_address; + for val in buf.chunks(4) { + write_volatile(address as *mut u32, u32::from_le_bytes(val.try_into().unwrap())); + address += val.len() as u32; - for sector in start_sector..end_sector { - let ret = erase_sector(sector as u8); - if ret.is_err() { - return ret; - } + // prevents parallelism errors + fence(Ordering::SeqCst); } - Ok(()) + blocking_wait_ready() } -unsafe fn erase_sector(sector: u8) -> Result<(), Error> { +pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), Error> { pac::FLASH.cr().modify(|w| { w.set_ser(true); - w.set_snb(sector) + w.set_snb(sector.index_in_bank) }); pac::FLASH.cr().modify(|w| { @@ -80,11 +60,8 @@ unsafe fn erase_sector(sector: u8) -> Result<(), Error> { }); let ret: Result<(), Error> = blocking_wait_ready(); - pac::FLASH.cr().modify(|w| w.set_ser(false)); - clear_all_err(); - ret } @@ -108,7 +85,7 @@ pub(crate) unsafe fn clear_all_err() { }); } -pub(crate) unsafe fn blocking_wait_ready() -> Result<(), Error> { +unsafe fn blocking_wait_ready() -> Result<(), Error> { loop { let sr = pac::FLASH.sr().read(); @@ -133,3 +110,75 @@ pub(crate) unsafe fn blocking_wait_ready() -> Result<(), Error> { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::flash::{get_sector, FlashBank}; + + #[test] + #[cfg(stm32f732)] + fn can_get_sector() { + const SMALL_SECTOR_SIZE: u32 = 16 * 1024; + const MEDIUM_SECTOR_SIZE: u32 = 64 * 1024; + const LARGE_SECTOR_SIZE: u32 = 128 * 1024; + + let assert_sector = |index_in_bank: u8, start: u32, size: u32, address: u32| { + assert_eq!( + FlashSector { + bank: FlashBank::Bank1, + index_in_bank, + start, + size + }, + get_sector(address, &FLASH_REGIONS) + ) + }; + + assert_sector(0, 0x0800_0000, SMALL_SECTOR_SIZE, 0x0800_0000); + assert_sector(0, 0x0800_0000, SMALL_SECTOR_SIZE, 0x0800_3FFF); + assert_sector(3, 0x0800_C000, SMALL_SECTOR_SIZE, 0x0800_C000); + assert_sector(3, 0x0800_C000, SMALL_SECTOR_SIZE, 0x0800_FFFF); + + assert_sector(4, 0x0801_0000, MEDIUM_SECTOR_SIZE, 0x0801_0000); + assert_sector(4, 0x0801_0000, MEDIUM_SECTOR_SIZE, 0x0801_FFFF); + + assert_sector(5, 0x0802_0000, LARGE_SECTOR_SIZE, 0x0802_0000); + assert_sector(5, 0x0802_0000, LARGE_SECTOR_SIZE, 0x0803_FFFF); + assert_sector(7, 0x0806_0000, LARGE_SECTOR_SIZE, 0x0806_0000); + assert_sector(7, 0x0806_0000, LARGE_SECTOR_SIZE, 0x0807_FFFF); + } + + #[test] + #[cfg(stm32f769)] + fn can_get_sector() { + const SMALL_SECTOR_SIZE: u32 = 32 * 1024; + const MEDIUM_SECTOR_SIZE: u32 = 128 * 1024; + const LARGE_SECTOR_SIZE: u32 = 256 * 1024; + + let assert_sector = |index_in_bank: u8, start: u32, size: u32, address: u32| { + assert_eq!( + FlashSector { + bank: FlashBank::Bank1, + index_in_bank, + start, + size + }, + get_sector(address, &FLASH_REGIONS) + ) + }; + + assert_sector(0, 0x0800_0000, SMALL_SECTOR_SIZE, 0x0800_0000); + assert_sector(0, 0x0800_0000, SMALL_SECTOR_SIZE, 0x0800_7FFF); + assert_sector(3, 0x0801_8000, SMALL_SECTOR_SIZE, 0x0801_8000); + assert_sector(3, 0x0801_8000, SMALL_SECTOR_SIZE, 0x0801_FFFF); + + assert_sector(4, 0x0802_0000, MEDIUM_SECTOR_SIZE, 0x0802_0000); + assert_sector(4, 0x0802_0000, MEDIUM_SECTOR_SIZE, 0x0803_FFFF); + + assert_sector(5, 0x0804_0000, LARGE_SECTOR_SIZE, 0x0804_0000); + assert_sector(5, 0x0804_0000, LARGE_SECTOR_SIZE, 0x0807_FFFF); + assert_sector(7, 0x080C_0000, LARGE_SECTOR_SIZE, 0x080C_0000); + assert_sector(7, 0x080C_0000, LARGE_SECTOR_SIZE, 0x080F_FFFF); + } +} diff --git a/embassy-stm32/src/flash/h7.rs b/embassy-stm32/src/flash/h7.rs index 7ce0ac776..bf17b5b18 100644 --- a/embassy-stm32/src/flash/h7.rs +++ b/embassy-stm32/src/flash/h7.rs @@ -1,13 +1,21 @@ use core::convert::TryInto; use core::ptr::write_volatile; +use core::sync::atomic::{fence, Ordering}; +use super::{FlashRegion, FlashSector, BANK1_REGION, FLASH_REGIONS, WRITE_SIZE}; use crate::flash::Error; use crate::pac; -const SECOND_BANK_OFFSET: usize = 0x0010_0000; +pub const fn is_default_layout() -> bool { + true +} const fn is_dual_bank() -> bool { - super::FLASH_SIZE / 2 > super::ERASE_SIZE + FLASH_REGIONS.len() == 2 +} + +pub fn get_flash_regions() -> &'static [&'static FlashRegion] { + &FLASH_REGIONS } pub(crate) unsafe fn lock() { @@ -20,87 +28,64 @@ pub(crate) unsafe fn lock() { pub(crate) unsafe fn unlock() { pac::FLASH.bank(0).keyr().write(|w| w.set_keyr(0x4567_0123)); pac::FLASH.bank(0).keyr().write(|w| w.set_keyr(0xCDEF_89AB)); - if is_dual_bank() { pac::FLASH.bank(1).keyr().write(|w| w.set_keyr(0x4567_0123)); pac::FLASH.bank(1).keyr().write(|w| w.set_keyr(0xCDEF_89AB)); } } -pub(crate) unsafe fn blocking_write(offset: u32, buf: &[u8]) -> Result<(), Error> { - let bank = if !is_dual_bank() || (offset - super::FLASH_BASE as u32) < SECOND_BANK_OFFSET as u32 { +pub(crate) unsafe fn enable_blocking_write() { + assert_eq!(0, WRITE_SIZE % 4); +} + +pub(crate) unsafe fn disable_blocking_write() {} + +pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) -> Result<(), Error> { + // We cannot have the write setup sequence in begin_write as it depends on the address + let bank = if start_address < BANK1_REGION.end() { pac::FLASH.bank(0) } else { pac::FLASH.bank(1) }; - bank.cr().write(|w| { w.set_pg(true); w.set_psize(2); // 32 bits at once }); + cortex_m::asm::isb(); + cortex_m::asm::dsb(); + fence(Ordering::SeqCst); - let ret = { - let mut ret: Result<(), Error> = Ok(()); - let mut offset = offset; - 'outer: for chunk in buf.chunks(super::WRITE_SIZE) { - for val in chunk.chunks(4) { - trace!("Writing at {:x}", offset); - write_volatile(offset as *mut u32, u32::from_le_bytes(val[0..4].try_into().unwrap())); - offset += val.len() as u32; + let mut res = None; + let mut address = start_address; + for val in buf.chunks(4) { + write_volatile(address as *mut u32, u32::from_le_bytes(val.try_into().unwrap())); + address += val.len() as u32; - ret = blocking_wait_ready(bank); - bank.sr().modify(|w| { - if w.eop() { - w.set_eop(true); - } - }); - if ret.is_err() { - break 'outer; - } + res = Some(blocking_wait_ready(bank)); + bank.sr().modify(|w| { + if w.eop() { + w.set_eop(true); } - } - ret - }; - - bank.cr().write(|w| w.set_pg(false)); - - ret -} - -pub(crate) unsafe fn blocking_erase(from: u32, to: u32) -> Result<(), Error> { - let from = from - super::FLASH_BASE as u32; - let to = to - super::FLASH_BASE as u32; - - let bank_size = (super::FLASH_SIZE / 2) as u32; - - let (bank, start, end) = if to <= bank_size { - let start_sector = from / super::ERASE_SIZE as u32; - let end_sector = to / super::ERASE_SIZE as u32; - (0, start_sector, end_sector) - } else if from >= SECOND_BANK_OFFSET as u32 && to <= (SECOND_BANK_OFFSET as u32 + bank_size) { - let start_sector = (from - SECOND_BANK_OFFSET as u32) / super::ERASE_SIZE as u32; - let end_sector = (to - SECOND_BANK_OFFSET as u32) / super::ERASE_SIZE as u32; - (1, start_sector, end_sector) - } else { - error!("Attempting to write outside of defined sectors"); - return Err(Error::Unaligned); - }; - - trace!("Erasing bank {}, sectors from {} to {}", bank, start, end); - for sector in start..end { - let ret = erase_sector(pac::FLASH.bank(bank), sector as u8); - if ret.is_err() { - return ret; + }); + if res.unwrap().is_err() { + break; } } - Ok(()) + bank.cr().write(|w| w.set_pg(false)); + + cortex_m::asm::isb(); + cortex_m::asm::dsb(); + fence(Ordering::SeqCst); + + res.unwrap() } -unsafe fn erase_sector(bank: pac::flash::Bank, sector: u8) -> Result<(), Error> { +pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), Error> { + let bank = pac::FLASH.bank(sector.bank as usize); bank.cr().modify(|w| { w.set_ser(true); - w.set_snb(sector) + w.set_snb(sector.index_in_bank) }); bank.cr().modify(|w| { @@ -108,11 +93,8 @@ unsafe fn erase_sector(bank: pac::flash::Bank, sector: u8) -> Result<(), Error> }); let ret: Result<(), Error> = blocking_wait_ready(bank); - bank.cr().modify(|w| w.set_ser(false)); - bank_clear_all_err(bank); - ret } @@ -157,7 +139,7 @@ unsafe fn bank_clear_all_err(bank: pac::flash::Bank) { }); } -pub(crate) unsafe fn blocking_wait_ready(bank: pac::flash::Bank) -> Result<(), Error> { +unsafe fn blocking_wait_ready(bank: pac::flash::Bank) -> Result<(), Error> { loop { let sr = bank.sr().read(); diff --git a/embassy-stm32/src/flash/l.rs b/embassy-stm32/src/flash/l.rs index 5048a3314..243c8b51d 100644 --- a/embassy-stm32/src/flash/l.rs +++ b/embassy-stm32/src/flash/l.rs @@ -1,9 +1,18 @@ -use core::convert::TryInto; use core::ptr::write_volatile; +use core::sync::atomic::{fence, Ordering}; +use super::{FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; use crate::flash::Error; use crate::pac; +pub const fn is_default_layout() -> bool { + true +} + +pub const fn get_flash_regions() -> &'static [&'static FlashRegion] { + &FLASH_REGIONS +} + pub(crate) unsafe fn lock() { #[cfg(any(flash_wl, flash_wb, flash_l4))] pac::FLASH.cr().modify(|w| w.set_lock(true)); @@ -33,82 +42,74 @@ pub(crate) unsafe fn unlock() { } } -pub(crate) unsafe fn blocking_write(offset: u32, buf: &[u8]) -> Result<(), Error> { +pub(crate) unsafe fn enable_blocking_write() { + assert_eq!(0, WRITE_SIZE % 4); + #[cfg(any(flash_wl, flash_wb, flash_l4))] pac::FLASH.cr().write(|w| w.set_pg(true)); - - let ret = { - let mut ret: Result<(), Error> = Ok(()); - let mut offset = offset; - for chunk in buf.chunks(super::WRITE_SIZE) { - for val in chunk.chunks(4) { - write_volatile(offset as *mut u32, u32::from_le_bytes(val[0..4].try_into().unwrap())); - offset += val.len() as u32; - } - - ret = blocking_wait_ready(); - if ret.is_err() { - break; - } - } - ret - }; - - #[cfg(any(flash_wl, flash_wb, flash_l4))] - pac::FLASH.cr().write(|w| w.set_pg(false)); - - ret } -pub(crate) unsafe fn blocking_erase(from: u32, to: u32) -> Result<(), Error> { - for page in (from..to).step_by(super::ERASE_SIZE) { - #[cfg(any(flash_l0, flash_l1))] - { - pac::FLASH.pecr().modify(|w| { - w.set_erase(true); - w.set_prog(true); - }); +pub(crate) unsafe fn disable_blocking_write() { + #[cfg(any(flash_wl, flash_wb, flash_l4))] + pac::FLASH.cr().write(|w| w.set_pg(false)); +} - write_volatile(page as *mut u32, 0xFFFFFFFF); - } +pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) -> Result<(), Error> { + let mut address = start_address; + for val in buf.chunks(4) { + write_volatile(address as *mut u32, u32::from_le_bytes(val.try_into().unwrap())); + address += val.len() as u32; - #[cfg(any(flash_wl, flash_wb, flash_l4))] - { - let idx = (page - super::FLASH_BASE as u32) / super::ERASE_SIZE as u32; - - #[cfg(flash_l4)] - let (idx, bank) = if idx > 255 { (idx - 256, true) } else { (idx, false) }; - - pac::FLASH.cr().modify(|w| { - w.set_per(true); - w.set_pnb(idx as u8); - #[cfg(any(flash_wl, flash_wb))] - w.set_strt(true); - #[cfg(any(flash_l4))] - w.set_start(true); - #[cfg(any(flash_l4))] - w.set_bker(bank); - }); - } - - let ret: Result<(), Error> = blocking_wait_ready(); - - #[cfg(any(flash_wl, flash_wb, flash_l4))] - pac::FLASH.cr().modify(|w| w.set_per(false)); - - #[cfg(any(flash_l0, flash_l1))] - pac::FLASH.pecr().modify(|w| { - w.set_erase(false); - w.set_prog(false); - }); - - clear_all_err(); - if ret.is_err() { - return ret; - } + // prevents parallelism errors + fence(Ordering::SeqCst); } - Ok(()) + wait_ready_blocking() +} + +pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), Error> { + #[cfg(any(flash_l0, flash_l1))] + { + pac::FLASH.pecr().modify(|w| { + w.set_erase(true); + w.set_prog(true); + }); + + write_volatile(sector.start as *mut u32, 0xFFFFFFFF); + } + + #[cfg(any(flash_wl, flash_wb, flash_l4))] + { + let idx = (sector.start - super::FLASH_BASE as u32) / super::BANK1_REGION.erase_size as u32; + + #[cfg(flash_l4)] + let (idx, bank) = if idx > 255 { (idx - 256, true) } else { (idx, false) }; + + pac::FLASH.cr().modify(|w| { + w.set_per(true); + w.set_pnb(idx as u8); + #[cfg(any(flash_wl, flash_wb))] + w.set_strt(true); + #[cfg(any(flash_l4))] + w.set_start(true); + #[cfg(any(flash_l4))] + w.set_bker(bank); + }); + } + + let ret: Result<(), Error> = wait_ready_blocking(); + + #[cfg(any(flash_wl, flash_wb, flash_l4))] + pac::FLASH.cr().modify(|w| w.set_per(false)); + + #[cfg(any(flash_l0, flash_l1))] + pac::FLASH.pecr().modify(|w| { + w.set_erase(false); + w.set_prog(false); + }); + + clear_all_err(); + ret } pub(crate) unsafe fn clear_all_err() { @@ -149,7 +150,7 @@ pub(crate) unsafe fn clear_all_err() { }); } -pub(crate) unsafe fn blocking_wait_ready() -> Result<(), Error> { +unsafe fn wait_ready_blocking() -> Result<(), Error> { loop { let sr = pac::FLASH.sr().read(); diff --git a/embassy-stm32/src/flash/mod.rs b/embassy-stm32/src/flash/mod.rs index 5258c9b04..f44ef2c16 100644 --- a/embassy-stm32/src/flash/mod.rs +++ b/embassy-stm32/src/flash/mod.rs @@ -1,90 +1,79 @@ -use embassy_hal_common::{into_ref, PeripheralRef}; -use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; +use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind}; -pub use crate::pac::{ERASE_SIZE, ERASE_VALUE, FLASH_BASE, FLASH_SIZE, WRITE_SIZE}; -use crate::peripherals::FLASH; -use crate::Peripheral; -const FLASH_END: usize = FLASH_BASE + FLASH_SIZE; +#[cfg(flash_f4)] +mod asynch; +#[cfg(flash)] +mod common; -#[cfg_attr(any(flash_wl, flash_wb, flash_l0, flash_l1, flash_l4), path = "l.rs")] +#[cfg(flash_f4)] +pub use asynch::InterruptHandler; +#[cfg(flash)] +pub use common::*; + +pub use crate::_generated::flash_regions::*; +pub use crate::_generated::MAX_ERASE_SIZE; +pub use crate::pac::{FLASH_BASE, FLASH_SIZE, WRITE_SIZE}; + +pub const READ_SIZE: usize = 1; + +pub struct Blocking; +pub struct Async; + +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct FlashRegion { + pub bank: FlashBank, + pub base: u32, + pub size: u32, + pub erase_size: u32, + pub write_size: u32, + pub erase_value: u8, + pub(crate) _ensure_internal: (), +} + +#[derive(Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct FlashSector { + pub bank: FlashBank, + pub index_in_bank: u8, + pub start: u32, + pub size: u32, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum FlashBank { + Bank1 = 0, + Bank2 = 1, + Otp, +} + +impl FlashRegion { + pub const fn end(&self) -> u32 { + self.base + self.size + } + + pub const fn sectors(&self) -> u8 { + (self.size / self.erase_size) as u8 + } +} + +#[cfg_attr(any(flash_l0, flash_l1, flash_l4, flash_wl, flash_wb), path = "l.rs")] +#[cfg_attr(flash_f0, path = "f0.rs")] #[cfg_attr(flash_f3, path = "f3.rs")] #[cfg_attr(flash_f4, path = "f4.rs")] #[cfg_attr(flash_f7, path = "f7.rs")] #[cfg_attr(flash_h7, path = "h7.rs")] +#[cfg_attr( + not(any( + flash_l0, flash_l1, flash_l4, flash_wl, flash_wb, flash_f0, flash_f3, flash_f4, flash_f7, flash_h7 + )), + path = "other.rs" +)] mod family; -pub struct Flash<'d> { - _inner: PeripheralRef<'d, FLASH>, -} - -impl<'d> Flash<'d> { - pub fn new(p: impl Peripheral

+ 'd) -> Self { - into_ref!(p); - Self { _inner: p } - } - - pub fn unlock(p: impl Peripheral

+ 'd) -> Self { - let flash = Self::new(p); - - unsafe { family::unlock() }; - flash - } - - pub fn lock(&mut self) { - unsafe { family::lock() }; - } - - pub fn blocking_read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Error> { - let offset = FLASH_BASE as u32 + offset; - if offset as usize >= FLASH_END || offset as usize + bytes.len() > FLASH_END { - return Err(Error::Size); - } - - let flash_data = unsafe { core::slice::from_raw_parts(offset as *const u8, bytes.len()) }; - bytes.copy_from_slice(flash_data); - Ok(()) - } - - pub fn blocking_write(&mut self, offset: u32, buf: &[u8]) -> Result<(), Error> { - let offset = FLASH_BASE as u32 + offset; - if offset as usize + buf.len() > FLASH_END { - return Err(Error::Size); - } - if offset as usize % WRITE_SIZE != 0 || buf.len() as usize % WRITE_SIZE != 0 { - return Err(Error::Unaligned); - } - trace!("Writing {} bytes at 0x{:x}", buf.len(), offset); - - self.clear_all_err(); - - unsafe { family::blocking_write(offset, buf) } - } - - pub fn blocking_erase(&mut self, from: u32, to: u32) -> Result<(), Error> { - let from = FLASH_BASE as u32 + from; - let to = FLASH_BASE as u32 + to; - if to < from || to as usize > FLASH_END { - return Err(Error::Size); - } - if (from as usize % ERASE_SIZE) != 0 || (to as usize % ERASE_SIZE) != 0 { - return Err(Error::Unaligned); - } - - self.clear_all_err(); - - unsafe { family::blocking_erase(from, to) } - } - - fn clear_all_err(&mut self) { - unsafe { family::clear_all_err() }; - } -} - -impl Drop for Flash<'_> { - fn drop(&mut self) { - self.lock(); - } -} +#[allow(unused_imports)] +pub use family::*; #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -98,10 +87,6 @@ pub enum Error { Parallelism, } -impl<'d> ErrorType for Flash<'d> { - type Error = Error; -} - impl NorFlashError for Error { fn kind(&self) -> NorFlashErrorKind { match self { @@ -111,71 +96,3 @@ impl NorFlashError for Error { } } } - -impl<'d> ReadNorFlash for Flash<'d> { - const READ_SIZE: usize = WRITE_SIZE; - - fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { - self.blocking_read(offset, bytes) - } - - fn capacity(&self) -> usize { - FLASH_SIZE - } -} - -impl<'d> NorFlash for Flash<'d> { - const WRITE_SIZE: usize = WRITE_SIZE; - const ERASE_SIZE: usize = ERASE_SIZE; - - fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { - self.blocking_erase(from, to) - } - - fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { - self.blocking_write(offset, bytes) - } -} - -/* -cfg_if::cfg_if! { - if #[cfg(feature = "nightly")] - { - use embedded_storage_async::nor_flash::{AsyncNorFlash, AsyncReadNorFlash}; - use core::future::Future; - - impl<'d> AsyncNorFlash for Flash<'d> { - const WRITE_SIZE: usize = ::WRITE_SIZE; - const ERASE_SIZE: usize = ::ERASE_SIZE; - - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - fn write<'a>(&'a mut self, offset: u32, data: &'a [u8]) -> Self::WriteFuture<'a> { - async move { - todo!() - } - } - - type EraseFuture<'a> = impl Future> + 'a where Self: 'a; - fn erase<'a>(&'a mut self, from: u32, to: u32) -> Self::EraseFuture<'a> { - async move { - todo!() - } - } - } - - impl<'d> AsyncReadNorFlash for Flash<'d> { - const READ_SIZE: usize = ::READ_SIZE; - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; - fn read<'a>(&'a mut self, address: u32, data: &'a mut [u8]) -> Self::ReadFuture<'a> { - async move { - todo!() - } - } - - fn capacity(&self) -> usize { - FLASH_SIZE - } - } - } -} -*/ diff --git a/embassy-stm32/src/flash/other.rs b/embassy-stm32/src/flash/other.rs new file mode 100644 index 000000000..a7e8d1d57 --- /dev/null +++ b/embassy-stm32/src/flash/other.rs @@ -0,0 +1,33 @@ +#![allow(unused)] + +use super::{Error, FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; + +pub const fn is_default_layout() -> bool { + true +} + +pub const fn get_flash_regions() -> &'static [&'static FlashRegion] { + &FLASH_REGIONS +} + +pub(crate) unsafe fn lock() { + unimplemented!(); +} +pub(crate) unsafe fn unlock() { + unimplemented!(); +} +pub(crate) unsafe fn enable_blocking_write() { + unimplemented!(); +} +pub(crate) unsafe fn disable_blocking_write() { + unimplemented!(); +} +pub(crate) unsafe fn blocking_write(_start_address: u32, _buf: &[u8; WRITE_SIZE]) -> Result<(), Error> { + unimplemented!(); +} +pub(crate) unsafe fn blocking_erase_sector(_sector: &FlashSector) -> Result<(), Error> { + unimplemented!(); +} +pub(crate) unsafe fn clear_all_err() { + unimplemented!(); +} diff --git a/embassy-stm32/src/fmc.rs b/embassy-stm32/src/fmc.rs new file mode 100644 index 000000000..60d7a00ee --- /dev/null +++ b/embassy-stm32/src/fmc.rs @@ -0,0 +1,292 @@ +use core::marker::PhantomData; + +use embassy_hal_common::into_ref; + +use crate::gpio::sealed::AFType; +use crate::gpio::{Pull, Speed}; +use crate::Peripheral; + +pub struct Fmc<'d, T: Instance> { + peri: PhantomData<&'d mut T>, +} + +unsafe impl<'d, T> Send for Fmc<'d, T> where T: Instance {} + +unsafe impl<'d, T> stm32_fmc::FmcPeripheral for Fmc<'d, T> +where + T: Instance, +{ + const REGISTERS: *const () = T::REGS.as_ptr() as *const _; + + fn enable(&mut self) { + ::enable(); + ::reset(); + } + + fn memory_controller_enable(&mut self) { + // fmc v1 and v2 does not have the fmcen bit + // fsmc v1, v2 and v3 does not have the fmcen bit + // This is a "not" because it is expected that all future versions have this bit + #[cfg(not(any(fmc_v1x3, fmc_v2x1, fsmc_v1x0, fsmc_v1x3, fsmc_v2x3, fsmc_v3x1)))] + T::REGS.bcr1().modify(|r| r.set_fmcen(true)); + } + + fn source_clock_hz(&self) -> u32 { + ::frequency().0 + } +} + +macro_rules! config_pins { + ($($pin:ident),*) => { + into_ref!($($pin),*); + $( + $pin.set_as_af_pull($pin.af_num(), AFType::OutputPushPull, Pull::Up); + $pin.set_speed(Speed::VeryHigh); + )* + }; +} + +macro_rules! fmc_sdram_constructor { + ($name:ident: ( + bank: $bank:expr, + addr: [$(($addr_pin_name:ident: $addr_signal:ident)),*], + ba: [$(($ba_pin_name:ident: $ba_signal:ident)),*], + d: [$(($d_pin_name:ident: $d_signal:ident)),*], + nbl: [$(($nbl_pin_name:ident: $nbl_signal:ident)),*], + ctrl: [$(($ctrl_pin_name:ident: $ctrl_signal:ident)),*] + )) => { + pub fn $name( + _instance: impl Peripheral

+ 'd, + $($addr_pin_name: impl Peripheral

> + 'd),*, + $($ba_pin_name: impl Peripheral

> + 'd),*, + $($d_pin_name: impl Peripheral

> + 'd),*, + $($nbl_pin_name: impl Peripheral

> + 'd),*, + $($ctrl_pin_name: impl Peripheral

> + 'd),*, + chip: CHIP + ) -> stm32_fmc::Sdram, CHIP> { + + critical_section::with(|_| { + config_pins!( + $($addr_pin_name),*, + $($ba_pin_name),*, + $($d_pin_name),*, + $($nbl_pin_name),*, + $($ctrl_pin_name),* + ); + }); + + let fmc = Self { peri: PhantomData }; + stm32_fmc::Sdram::new_unchecked( + fmc, + $bank, + chip, + ) + } + }; +} + +impl<'d, T: Instance> Fmc<'d, T> { + fmc_sdram_constructor!(sdram_a12bits_d16bits_4banks_bank1: ( + bank: stm32_fmc::SdramTargetBank::Bank1, + addr: [ + (a0: A0Pin), (a1: A1Pin), (a2: A2Pin), (a3: A3Pin), (a4: A4Pin), (a5: A5Pin), (a6: A6Pin), (a7: A7Pin), (a8: A8Pin), (a9: A9Pin), (a10: A10Pin), (a11: A11Pin) + ], + ba: [(ba0: BA0Pin), (ba1: BA1Pin)], + d: [ + (d0: D0Pin), (d1: D1Pin), (d2: D2Pin), (d3: D3Pin), (d4: D4Pin), (d5: D5Pin), (d6: D6Pin), (d7: D7Pin), + (d8: D8Pin), (d9: D9Pin), (d10: D10Pin), (d11: D11Pin), (d12: D12Pin), (d13: D13Pin), (d14: D14Pin), (d15: D15Pin) + ], + nbl: [ + (nbl0: NBL0Pin), (nbl1: NBL1Pin) + ], + ctrl: [ + (sdcke: SDCKE0Pin), (sdclk: SDCLKPin), (sdncas: SDNCASPin), (sdne: SDNE0Pin), (sdnras: SDNRASPin), (sdnwe: SDNWEPin) + ] + )); + + fmc_sdram_constructor!(sdram_a12bits_d32bits_4banks_bank1: ( + bank: stm32_fmc::SdramTargetBank::Bank1, + addr: [ + (a0: A0Pin), (a1: A1Pin), (a2: A2Pin), (a3: A3Pin), (a4: A4Pin), (a5: A5Pin), (a6: A6Pin), (a7: A7Pin), (a8: A8Pin), (a9: A9Pin), (a10: A10Pin), (a11: A11Pin) + ], + ba: [(ba0: BA0Pin), (ba1: BA1Pin)], + d: [ + (d0: D0Pin), (d1: D1Pin), (d2: D2Pin), (d3: D3Pin), (d4: D4Pin), (d5: D5Pin), (d6: D6Pin), (d7: D7Pin), + (d8: D8Pin), (d9: D9Pin), (d10: D10Pin), (d11: D11Pin), (d12: D12Pin), (d13: D13Pin), (d14: D14Pin), (d15: D15Pin), + (d16: D16Pin), (d17: D17Pin), (d18: D18Pin), (d19: D19Pin), (d20: D20Pin), (d21: D21Pin), (d22: D22Pin), (d23: D23Pin), + (d24: D24Pin), (d25: D25Pin), (d26: D26Pin), (d27: D27Pin), (d28: D28Pin), (d29: D29Pin), (d30: D30Pin), (d31: D31Pin) + ], + nbl: [ + (nbl0: NBL0Pin), (nbl1: NBL1Pin), (nbl2: NBL2Pin), (nbl3: NBL3Pin) + ], + ctrl: [ + (sdcke: SDCKE0Pin), (sdclk: SDCLKPin), (sdncas: SDNCASPin), (sdne: SDNE0Pin), (sdnras: SDNRASPin), (sdnwe: SDNWEPin) + ] + )); + + fmc_sdram_constructor!(sdram_a12bits_d16bits_4banks_bank2: ( + bank: stm32_fmc::SdramTargetBank::Bank2, + addr: [ + (a0: A0Pin), (a1: A1Pin), (a2: A2Pin), (a3: A3Pin), (a4: A4Pin), (a5: A5Pin), (a6: A6Pin), (a7: A7Pin), (a8: A8Pin), (a9: A9Pin), (a10: A10Pin), (a11: A11Pin) + ], + ba: [(ba0: BA0Pin), (ba1: BA1Pin)], + d: [ + (d0: D0Pin), (d1: D1Pin), (d2: D2Pin), (d3: D3Pin), (d4: D4Pin), (d5: D5Pin), (d6: D6Pin), (d7: D7Pin), + (d8: D8Pin), (d9: D9Pin), (d10: D10Pin), (d11: D11Pin), (d12: D12Pin), (d13: D13Pin), (d14: D14Pin), (d15: D15Pin) + ], + nbl: [ + (nbl0: NBL0Pin), (nbl1: NBL1Pin) + ], + ctrl: [ + (sdcke: SDCKE1Pin), (sdclk: SDCLKPin), (sdncas: SDNCASPin), (sdne: SDNE1Pin), (sdnras: SDNRASPin), (sdnwe: SDNWEPin) + ] + )); + + fmc_sdram_constructor!(sdram_a12bits_d32bits_4banks_bank2: ( + bank: stm32_fmc::SdramTargetBank::Bank2, + addr: [ + (a0: A0Pin), (a1: A1Pin), (a2: A2Pin), (a3: A3Pin), (a4: A4Pin), (a5: A5Pin), (a6: A6Pin), (a7: A7Pin), (a8: A8Pin), (a9: A9Pin), (a10: A10Pin), (a11: A11Pin) + ], + ba: [(ba0: BA0Pin), (ba1: BA1Pin)], + d: [ + (d0: D0Pin), (d1: D1Pin), (d2: D2Pin), (d3: D3Pin), (d4: D4Pin), (d5: D5Pin), (d6: D6Pin), (d7: D7Pin), + (d8: D8Pin), (d9: D9Pin), (d10: D10Pin), (d11: D11Pin), (d12: D12Pin), (d13: D13Pin), (d14: D14Pin), (d15: D15Pin), + (d16: D16Pin), (d17: D17Pin), (d18: D18Pin), (d19: D19Pin), (d20: D20Pin), (d21: D21Pin), (d22: D22Pin), (d23: D23Pin), + (d24: D24Pin), (d25: D25Pin), (d26: D26Pin), (d27: D27Pin), (d28: D28Pin), (d29: D29Pin), (d30: D30Pin), (d31: D31Pin) + ], + nbl: [ + (nbl0: NBL0Pin), (nbl1: NBL1Pin), (nbl2: NBL2Pin), (nbl3: NBL3Pin) + ], + ctrl: [ + (sdcke: SDCKE1Pin), (sdclk: SDCLKPin), (sdncas: SDNCASPin), (sdne: SDNE1Pin), (sdnras: SDNRASPin), (sdnwe: SDNWEPin) + ] + )); +} + +pub(crate) mod sealed { + pub trait Instance: crate::rcc::sealed::RccPeripheral { + const REGS: crate::pac::fmc::Fmc; + } +} + +pub trait Instance: sealed::Instance + 'static {} + +foreach_peripheral!( + (fmc, $inst:ident) => { + impl crate::fmc::sealed::Instance for crate::peripherals::$inst { + const REGS: crate::pac::fmc::Fmc = crate::pac::$inst; + } + impl crate::fmc::Instance for crate::peripherals::$inst {} + }; +); + +pin_trait!(SDNWEPin, Instance); +pin_trait!(SDNCASPin, Instance); +pin_trait!(SDNRASPin, Instance); + +pin_trait!(SDNE0Pin, Instance); +pin_trait!(SDNE1Pin, Instance); + +pin_trait!(SDCKE0Pin, Instance); +pin_trait!(SDCKE1Pin, Instance); + +pin_trait!(SDCLKPin, Instance); + +pin_trait!(NBL0Pin, Instance); +pin_trait!(NBL1Pin, Instance); +pin_trait!(NBL2Pin, Instance); +pin_trait!(NBL3Pin, Instance); + +pin_trait!(INTPin, Instance); +pin_trait!(NLPin, Instance); +pin_trait!(NWaitPin, Instance); + +pin_trait!(NE1Pin, Instance); +pin_trait!(NE2Pin, Instance); +pin_trait!(NE3Pin, Instance); +pin_trait!(NE4Pin, Instance); + +pin_trait!(NCEPin, Instance); +pin_trait!(NOEPin, Instance); +pin_trait!(NWEPin, Instance); +pin_trait!(ClkPin, Instance); + +pin_trait!(BA0Pin, Instance); +pin_trait!(BA1Pin, Instance); + +pin_trait!(D0Pin, Instance); +pin_trait!(D1Pin, Instance); +pin_trait!(D2Pin, Instance); +pin_trait!(D3Pin, Instance); +pin_trait!(D4Pin, Instance); +pin_trait!(D5Pin, Instance); +pin_trait!(D6Pin, Instance); +pin_trait!(D7Pin, Instance); +pin_trait!(D8Pin, Instance); +pin_trait!(D9Pin, Instance); +pin_trait!(D10Pin, Instance); +pin_trait!(D11Pin, Instance); +pin_trait!(D12Pin, Instance); +pin_trait!(D13Pin, Instance); +pin_trait!(D14Pin, Instance); +pin_trait!(D15Pin, Instance); +pin_trait!(D16Pin, Instance); +pin_trait!(D17Pin, Instance); +pin_trait!(D18Pin, Instance); +pin_trait!(D19Pin, Instance); +pin_trait!(D20Pin, Instance); +pin_trait!(D21Pin, Instance); +pin_trait!(D22Pin, Instance); +pin_trait!(D23Pin, Instance); +pin_trait!(D24Pin, Instance); +pin_trait!(D25Pin, Instance); +pin_trait!(D26Pin, Instance); +pin_trait!(D27Pin, Instance); +pin_trait!(D28Pin, Instance); +pin_trait!(D29Pin, Instance); +pin_trait!(D30Pin, Instance); +pin_trait!(D31Pin, Instance); + +pin_trait!(DA0Pin, Instance); +pin_trait!(DA1Pin, Instance); +pin_trait!(DA2Pin, Instance); +pin_trait!(DA3Pin, Instance); +pin_trait!(DA4Pin, Instance); +pin_trait!(DA5Pin, Instance); +pin_trait!(DA6Pin, Instance); +pin_trait!(DA7Pin, Instance); +pin_trait!(DA8Pin, Instance); +pin_trait!(DA9Pin, Instance); +pin_trait!(DA10Pin, Instance); +pin_trait!(DA11Pin, Instance); +pin_trait!(DA12Pin, Instance); +pin_trait!(DA13Pin, Instance); +pin_trait!(DA14Pin, Instance); +pin_trait!(DA15Pin, Instance); + +pin_trait!(A0Pin, Instance); +pin_trait!(A1Pin, Instance); +pin_trait!(A2Pin, Instance); +pin_trait!(A3Pin, Instance); +pin_trait!(A4Pin, Instance); +pin_trait!(A5Pin, Instance); +pin_trait!(A6Pin, Instance); +pin_trait!(A7Pin, Instance); +pin_trait!(A8Pin, Instance); +pin_trait!(A9Pin, Instance); +pin_trait!(A10Pin, Instance); +pin_trait!(A11Pin, Instance); +pin_trait!(A12Pin, Instance); +pin_trait!(A13Pin, Instance); +pin_trait!(A14Pin, Instance); +pin_trait!(A15Pin, Instance); +pin_trait!(A16Pin, Instance); +pin_trait!(A17Pin, Instance); +pin_trait!(A18Pin, Instance); +pin_trait!(A19Pin, Instance); +pin_trait!(A20Pin, Instance); +pin_trait!(A21Pin, Instance); +pin_trait!(A22Pin, Instance); +pin_trait!(A23Pin, Instance); +pin_trait!(A24Pin, Instance); +pin_trait!(A25Pin, Instance); diff --git a/embassy-stm32/src/fmc/mod.rs b/embassy-stm32/src/fmc/mod.rs deleted file mode 100644 index 856a4adca..000000000 --- a/embassy-stm32/src/fmc/mod.rs +++ /dev/null @@ -1,140 +0,0 @@ -use core::marker::PhantomData; - -use embassy_hal_common::into_ref; - -use crate::gpio::sealed::AFType; -use crate::gpio::{Pull, Speed}; -use crate::Peripheral; - -mod pins; -pub use pins::*; - -pub struct Fmc<'d, T: Instance> { - peri: PhantomData<&'d mut T>, -} - -unsafe impl<'d, T> Send for Fmc<'d, T> where T: Instance {} - -unsafe impl<'d, T> stm32_fmc::FmcPeripheral for Fmc<'d, T> -where - T: Instance, -{ - const REGISTERS: *const () = crate::pac::FMC.0 as *const _; - - fn enable(&mut self) { - ::enable(); - ::reset(); - } - - fn memory_controller_enable(&mut self) { - // The FMCEN bit of the FMC_BCR2..4 registers is don’t - // care. It is only enabled through the FMC_BCR1 register. - unsafe { T::regs().bcr1().modify(|r| r.set_fmcen(true)) }; - } - - fn source_clock_hz(&self) -> u32 { - ::frequency().0 - } -} - -macro_rules! config_pins { - ($($pin:ident),*) => { - into_ref!($($pin),*); - $( - $pin.set_as_af_pull($pin.af_num(), AFType::OutputPushPull, Pull::Up); - $pin.set_speed(Speed::VeryHigh); - )* - }; -} - -macro_rules! fmc_sdram_constructor { - ($name:ident: ( - bank: $bank:expr, - addr: [$(($addr_pin_name:ident: $addr_signal:ident)),*], - ba: [$(($ba_pin_name:ident: $ba_signal:ident)),*], - d: [$(($d_pin_name:ident: $d_signal:ident)),*], - nbl: [$(($nbl_pin_name:ident: $nbl_signal:ident)),*], - ctrl: [$(($ctrl_pin_name:ident: $ctrl_signal:ident)),*] - )) => { - pub fn $name( - _instance: impl Peripheral

+ 'd, - $($addr_pin_name: impl Peripheral

> + 'd),*, - $($ba_pin_name: impl Peripheral

> + 'd),*, - $($d_pin_name: impl Peripheral

> + 'd),*, - $($nbl_pin_name: impl Peripheral

> + 'd),*, - $($ctrl_pin_name: impl Peripheral

> + 'd),*, - chip: CHIP - ) -> stm32_fmc::Sdram, CHIP> { - - critical_section::with(|_| unsafe { - config_pins!( - $($addr_pin_name),*, - $($ba_pin_name),*, - $($d_pin_name),*, - $($nbl_pin_name),*, - $($ctrl_pin_name),* - ); - }); - - let fmc = Self { peri: PhantomData }; - stm32_fmc::Sdram::new_unchecked( - fmc, - $bank, - chip, - ) - } - }; -} - -impl<'d, T: Instance> Fmc<'d, T> { - fmc_sdram_constructor!(sdram_a12bits_d32bits_4banks_bank1: ( - bank: stm32_fmc::SdramTargetBank::Bank1, - addr: [ - (a0: A0Pin), (a1: A1Pin), (a2: A2Pin), (a3: A3Pin), (a4: A4Pin), (a5: A5Pin), (a6: A6Pin), (a7: A7Pin), (a8: A8Pin), (a9: A9Pin), (a10: A10Pin), (a11: A11Pin) - ], - ba: [(ba0: BA0Pin), (ba1: BA1Pin)], - d: [ - (d0: D0Pin), (d1: D1Pin), (d2: D2Pin), (d3: D3Pin), (d4: D4Pin), (d5: D5Pin), (d6: D6Pin), (d7: D7Pin), - (d8: D8Pin), (d9: D9Pin), (d10: D10Pin), (d11: D11Pin), (d12: D12Pin), (d13: D13Pin), (d14: D14Pin), (d15: D15Pin), - (d16: D16Pin), (d17: D17Pin), (d18: D18Pin), (d19: D19Pin), (d20: D20Pin), (d21: D21Pin), (d22: D22Pin), (d23: D23Pin), - (d24: D24Pin), (d25: D25Pin), (d26: D26Pin), (d27: D27Pin), (d28: D28Pin), (d29: D29Pin), (d30: D30Pin), (d31: D31Pin) - ], - nbl: [ - (nbl0: NBL0Pin), (nbl1: NBL1Pin), (nbl2: NBL2Pin), (nbl3: NBL3Pin) - ], - ctrl: [ - (sdcke: SDCKE0Pin), (sdclk: SDCLKPin), (sdncas: SDNCASPin), (sdne: SDNE0Pin), (sdnras: SDNRASPin), (sdnwe: SDNWEPin) - ] - )); - - fmc_sdram_constructor!(sdram_a12bits_d32bits_4banks_bank2: ( - bank: stm32_fmc::SdramTargetBank::Bank2, - addr: [ - (a0: A0Pin), (a1: A1Pin), (a2: A2Pin), (a3: A3Pin), (a4: A4Pin), (a5: A5Pin), (a6: A6Pin), (a7: A7Pin), (a8: A8Pin), (a9: A9Pin), (a10: A10Pin), (a11: A11Pin) - ], - ba: [(ba0: BA0Pin), (ba1: BA1Pin)], - d: [ - (d0: D0Pin), (d1: D1Pin), (d2: D2Pin), (d3: D3Pin), (d4: D4Pin), (d5: D5Pin), (d6: D6Pin), (d7: D7Pin), - (d8: D8Pin), (d9: D9Pin), (d10: D10Pin), (d11: D11Pin), (d12: D12Pin), (d13: D13Pin), (d14: D14Pin), (d15: D15Pin), - (d16: D16Pin), (d17: D17Pin), (d18: D18Pin), (d19: D19Pin), (d20: D20Pin), (d21: D21Pin), (d22: D22Pin), (d23: D23Pin), - (d24: D24Pin), (d25: D25Pin), (d26: D26Pin), (d27: D27Pin), (d28: D28Pin), (d29: D29Pin), (d30: D30Pin), (d31: D31Pin) - ], - nbl: [ - (nbl0: NBL0Pin), (nbl1: NBL1Pin), (nbl2: NBL2Pin), (nbl3: NBL3Pin) - ], - ctrl: [ - (sdcke: SDCKE1Pin), (sdclk: SDCLKPin), (sdncas: SDNCASPin), (sdne: SDNE1Pin), (sdnras: SDNRASPin), (sdnwe: SDNWEPin) - ] - )); -} - -foreach_peripheral!( - (fmc, $inst:ident) => { - impl crate::fmc::sealed::Instance for crate::peripherals::$inst { - fn regs() -> stm32_metapac::fmc::Fmc { - crate::pac::$inst - } - } - impl crate::fmc::Instance for crate::peripherals::$inst {} - }; -); diff --git a/embassy-stm32/src/fmc/pins.rs b/embassy-stm32/src/fmc/pins.rs deleted file mode 100644 index 5062e52ae..000000000 --- a/embassy-stm32/src/fmc/pins.rs +++ /dev/null @@ -1,118 +0,0 @@ -pub(crate) mod sealed { - pub trait Instance: crate::rcc::sealed::RccPeripheral { - fn regs() -> crate::pac::fmc::Fmc; - } -} - -pub trait Instance: sealed::Instance + 'static {} - -pin_trait!(SDNWEPin, Instance); -pin_trait!(SDNCASPin, Instance); -pin_trait!(SDNRASPin, Instance); - -pin_trait!(SDNE0Pin, Instance); -pin_trait!(SDNE1Pin, Instance); - -pin_trait!(SDCKE0Pin, Instance); -pin_trait!(SDCKE1Pin, Instance); - -pin_trait!(SDCLKPin, Instance); - -pin_trait!(NBL0Pin, Instance); -pin_trait!(NBL1Pin, Instance); -pin_trait!(NBL2Pin, Instance); -pin_trait!(NBL3Pin, Instance); - -pin_trait!(INTPin, Instance); -pin_trait!(NLPin, Instance); -pin_trait!(NWaitPin, Instance); - -pin_trait!(NE1Pin, Instance); -pin_trait!(NE2Pin, Instance); -pin_trait!(NE3Pin, Instance); -pin_trait!(NE4Pin, Instance); - -pin_trait!(NCEPin, Instance); -pin_trait!(NOEPin, Instance); -pin_trait!(NWEPin, Instance); -pin_trait!(ClkPin, Instance); - -pin_trait!(BA0Pin, Instance); -pin_trait!(BA1Pin, Instance); - -pin_trait!(D0Pin, Instance); -pin_trait!(D1Pin, Instance); -pin_trait!(D2Pin, Instance); -pin_trait!(D3Pin, Instance); -pin_trait!(D4Pin, Instance); -pin_trait!(D5Pin, Instance); -pin_trait!(D6Pin, Instance); -pin_trait!(D7Pin, Instance); -pin_trait!(D8Pin, Instance); -pin_trait!(D9Pin, Instance); -pin_trait!(D10Pin, Instance); -pin_trait!(D11Pin, Instance); -pin_trait!(D12Pin, Instance); -pin_trait!(D13Pin, Instance); -pin_trait!(D14Pin, Instance); -pin_trait!(D15Pin, Instance); -pin_trait!(D16Pin, Instance); -pin_trait!(D17Pin, Instance); -pin_trait!(D18Pin, Instance); -pin_trait!(D19Pin, Instance); -pin_trait!(D20Pin, Instance); -pin_trait!(D21Pin, Instance); -pin_trait!(D22Pin, Instance); -pin_trait!(D23Pin, Instance); -pin_trait!(D24Pin, Instance); -pin_trait!(D25Pin, Instance); -pin_trait!(D26Pin, Instance); -pin_trait!(D27Pin, Instance); -pin_trait!(D28Pin, Instance); -pin_trait!(D29Pin, Instance); -pin_trait!(D30Pin, Instance); -pin_trait!(D31Pin, Instance); - -pin_trait!(DA0Pin, Instance); -pin_trait!(DA1Pin, Instance); -pin_trait!(DA2Pin, Instance); -pin_trait!(DA3Pin, Instance); -pin_trait!(DA4Pin, Instance); -pin_trait!(DA5Pin, Instance); -pin_trait!(DA6Pin, Instance); -pin_trait!(DA7Pin, Instance); -pin_trait!(DA8Pin, Instance); -pin_trait!(DA9Pin, Instance); -pin_trait!(DA10Pin, Instance); -pin_trait!(DA11Pin, Instance); -pin_trait!(DA12Pin, Instance); -pin_trait!(DA13Pin, Instance); -pin_trait!(DA14Pin, Instance); -pin_trait!(DA15Pin, Instance); - -pin_trait!(A0Pin, Instance); -pin_trait!(A1Pin, Instance); -pin_trait!(A2Pin, Instance); -pin_trait!(A3Pin, Instance); -pin_trait!(A4Pin, Instance); -pin_trait!(A5Pin, Instance); -pin_trait!(A6Pin, Instance); -pin_trait!(A7Pin, Instance); -pin_trait!(A8Pin, Instance); -pin_trait!(A9Pin, Instance); -pin_trait!(A10Pin, Instance); -pin_trait!(A11Pin, Instance); -pin_trait!(A12Pin, Instance); -pin_trait!(A13Pin, Instance); -pin_trait!(A14Pin, Instance); -pin_trait!(A15Pin, Instance); -pin_trait!(A16Pin, Instance); -pin_trait!(A17Pin, Instance); -pin_trait!(A18Pin, Instance); -pin_trait!(A19Pin, Instance); -pin_trait!(A20Pin, Instance); -pin_trait!(A21Pin, Instance); -pin_trait!(A22Pin, Instance); -pin_trait!(A23Pin, Instance); -pin_trait!(A24Pin, Instance); -pin_trait!(A25Pin, Instance); diff --git a/embassy-stm32/src/gpio.rs b/embassy-stm32/src/gpio.rs index d794e3989..af3a8eaca 100644 --- a/embassy-stm32/src/gpio.rs +++ b/embassy-stm32/src/gpio.rs @@ -28,10 +28,25 @@ impl<'d, T: Pin> Flex<'d, T> { Self { pin } } + #[inline] + pub fn degrade(self) -> Flex<'d, AnyPin> { + // Safety: We are about to drop the other copy of this pin, so + // this clone is safe. + let pin = unsafe { self.pin.clone_unchecked() }; + + // We don't want to run the destructor here, because that would + // deconfigure the pin. + core::mem::forget(self); + + Flex { + pin: pin.map_into::(), + } + } + /// Put the pin into input mode. #[inline] pub fn set_as_input(&mut self, pull: Pull) { - critical_section::with(|_| unsafe { + critical_section::with(|_| { let r = self.pin.block(); let n = self.pin.pin() as usize; #[cfg(gpio_v1)] @@ -69,7 +84,7 @@ impl<'d, T: Pin> Flex<'d, T> { /// at a specific level, call `set_high`/`set_low` on the pin first. #[inline] pub fn set_as_output(&mut self, speed: Speed) { - critical_section::with(|_| unsafe { + critical_section::with(|_| { let r = self.pin.block(); let n = self.pin.pin() as usize; #[cfg(gpio_v1)] @@ -101,7 +116,7 @@ impl<'d, T: Pin> Flex<'d, T> { /// at a specific level, call `set_high`/`set_low` on the pin first. #[inline] pub fn set_as_input_output(&mut self, speed: Speed, pull: Pull) { - critical_section::with(|_| unsafe { + critical_section::with(|_| { let r = self.pin.block(); let n = self.pin.pin() as usize; #[cfg(gpio_v1)] @@ -132,7 +147,7 @@ impl<'d, T: Pin> Flex<'d, T> { #[inline] pub fn is_low(&self) -> bool { - let state = unsafe { self.pin.block().idr().read().idr(self.pin.pin() as _) }; + let state = self.pin.block().idr().read().idr(self.pin.pin() as _); state == vals::Idr::LOW } @@ -149,7 +164,7 @@ impl<'d, T: Pin> Flex<'d, T> { /// Is the output pin set as low? #[inline] pub fn is_set_low(&self) -> bool { - let state = unsafe { self.pin.block().odr().read().odr(self.pin.pin() as _) }; + let state = self.pin.block().odr().read().odr(self.pin.pin() as _); state == vals::Odr::LOW } @@ -192,7 +207,7 @@ impl<'d, T: Pin> Flex<'d, T> { impl<'d, T: Pin> Drop for Flex<'d, T> { #[inline] fn drop(&mut self) { - critical_section::with(|_| unsafe { + critical_section::with(|_| { let r = self.pin.block(); let n = self.pin.pin() as usize; #[cfg(gpio_v1)] @@ -286,6 +301,13 @@ impl<'d, T: Pin> Input<'d, T> { Self { pin } } + #[inline] + pub fn degrade(self) -> Input<'d, AnyPin> { + Input { + pin: self.pin.degrade(), + } + } + #[inline] pub fn is_high(&self) -> bool { self.pin.is_high() @@ -319,9 +341,9 @@ impl From for Level { } } -impl Into for Level { - fn into(self) -> bool { - match self { +impl From for bool { + fn from(level: Level) -> bool { + match level { Level::Low => false, Level::High => true, } @@ -345,6 +367,13 @@ impl<'d, T: Pin> Output<'d, T> { Self { pin } } + #[inline] + pub fn degrade(self) -> Output<'d, AnyPin> { + Output { + pin: self.pin.degrade(), + } + } + /// Set the output as high. #[inline] pub fn set_high(&mut self) { @@ -407,6 +436,13 @@ impl<'d, T: Pin> OutputOpenDrain<'d, T> { Self { pin } } + #[inline] + pub fn degrade(self) -> Output<'d, AnyPin> { + Output { + pin: self.pin.degrade(), + } + } + #[inline] pub fn is_high(&self) -> bool { !self.pin.is_low() @@ -498,29 +534,25 @@ pub(crate) mod sealed { /// Set the output as high. #[inline] fn set_high(&self) { - unsafe { - let n = self._pin() as _; - self.block().bsrr().write(|w| w.set_bs(n, true)); - } + let n = self._pin() as _; + self.block().bsrr().write(|w| w.set_bs(n, true)); } /// Set the output as low. #[inline] fn set_low(&self) { - unsafe { - let n = self._pin() as _; - self.block().bsrr().write(|w| w.set_br(n, true)); - } + let n = self._pin() as _; + self.block().bsrr().write(|w| w.set_br(n, true)); } #[inline] - unsafe fn set_as_af(&self, af_num: u8, af_type: AFType) { + fn set_as_af(&self, af_num: u8, af_type: AFType) { self.set_as_af_pull(af_num, af_type, Pull::None); } #[cfg(gpio_v1)] #[inline] - unsafe fn set_as_af_pull(&self, _af_num: u8, af_type: AFType, pull: Pull) { + fn set_as_af_pull(&self, _af_num: u8, af_type: AFType, pull: Pull) { // F1 uses the AFIO register for remapping. // For now, this is not implemented, so af_num is ignored // _af_num should be zero here, since it is not set by stm32-data @@ -563,7 +595,7 @@ pub(crate) mod sealed { #[cfg(gpio_v2)] #[inline] - unsafe fn set_as_af_pull(&self, af_num: u8, af_type: AFType, pull: Pull) { + fn set_as_af_pull(&self, af_num: u8, af_type: AFType, pull: Pull) { let pin = self._pin() as usize; let block = self.block(); block.afr(pin / 8).modify(|w| w.set_afr(pin % 8, af_num)); @@ -578,7 +610,7 @@ pub(crate) mod sealed { } #[inline] - unsafe fn set_as_analog(&self) { + fn set_as_analog(&self) { let pin = self._pin() as usize; let block = self.block(); #[cfg(gpio_v1)] @@ -599,12 +631,12 @@ pub(crate) mod sealed { /// This is currently the same as set_as_analog but is semantically different really. /// Drivers should set_as_disconnected pins when dropped. #[inline] - unsafe fn set_as_disconnected(&self) { + fn set_as_disconnected(&self) { self.set_as_analog(); } #[inline] - unsafe fn set_speed(&self, speed: Speed) { + fn set_speed(&self, speed: Speed) { let pin = self._pin() as usize; #[cfg(gpio_v1)] @@ -848,8 +880,7 @@ mod eh02 { #[cfg(feature = "unstable-traits")] mod eh1 { - use embedded_hal_1::digital::blocking::{InputPin, OutputPin, StatefulOutputPin, ToggleableOutputPin}; - use embedded_hal_1::digital::ErrorType; + use embedded_hal_1::digital::{ErrorType, InputPin, OutputPin, StatefulOutputPin, ToggleableOutputPin}; use super::*; diff --git a/embassy-stm32/src/i2c/mod.rs b/embassy-stm32/src/i2c/mod.rs index 9d314f411..b35678ed9 100644 --- a/embassy-stm32/src/i2c/mod.rs +++ b/embassy-stm32/src/i2c/mod.rs @@ -1,12 +1,17 @@ #![macro_use] -use crate::interrupt::Interrupt; +use crate::interrupt; #[cfg_attr(i2c_v1, path = "v1.rs")] #[cfg_attr(i2c_v2, path = "v2.rs")] mod _version; pub use _version::*; +#[cfg(feature = "time")] +mod timeout; +#[cfg(feature = "time")] +pub use timeout::*; + use crate::peripherals; #[derive(Debug)] @@ -30,7 +35,7 @@ pub(crate) mod sealed { } pub trait Instance: sealed::Instance + 'static { - type Interrupt: Interrupt; + type Interrupt: interrupt::typelevel::Interrupt; } pin_trait!(SclPin, Instance); @@ -52,7 +57,7 @@ foreach_interrupt!( } impl Instance for peripherals::$inst { - type Interrupt = crate::interrupt::$irq; + type Interrupt = crate::interrupt::typelevel::$irq; } }; ); diff --git a/embassy-stm32/src/i2c/timeout.rs b/embassy-stm32/src/i2c/timeout.rs new file mode 100644 index 000000000..939e2750e --- /dev/null +++ b/embassy-stm32/src/i2c/timeout.rs @@ -0,0 +1,121 @@ +use embassy_time::{Duration, Instant}; + +use super::{Error, I2c, Instance}; + +/// An I2C wrapper, which provides `embassy-time` based timeouts for all `embedded-hal` trait methods. +/// +/// This is useful for recovering from a shorted bus or a device stuck in a clock stretching state. +/// A regular [I2c] would freeze until condition is removed. +pub struct TimeoutI2c<'d, T: Instance, TXDMA, RXDMA> { + i2c: &'d mut I2c<'d, T, TXDMA, RXDMA>, + timeout: Duration, +} + +fn timeout_fn(timeout: Duration) -> impl Fn() -> Result<(), Error> { + let deadline = Instant::now() + timeout; + move || { + if Instant::now() > deadline { + Err(Error::Timeout) + } else { + Ok(()) + } + } +} + +impl<'d, T: Instance, TXDMA, RXDMA> TimeoutI2c<'d, T, TXDMA, RXDMA> { + pub fn new(i2c: &'d mut I2c<'d, T, TXDMA, RXDMA>, timeout: Duration) -> Self { + Self { i2c, timeout } + } + + /// Blocking read with a custom timeout + pub fn blocking_read_timeout(&mut self, addr: u8, read: &mut [u8], timeout: Duration) -> Result<(), Error> { + self.i2c.blocking_read_timeout(addr, read, timeout_fn(timeout)) + } + + /// Blocking read with default timeout, provided in [`TimeoutI2c::new()`] + pub fn blocking_read(&mut self, addr: u8, read: &mut [u8]) -> Result<(), Error> { + self.blocking_read_timeout(addr, read, self.timeout) + } + + /// Blocking write with a custom timeout + pub fn blocking_write_timeout(&mut self, addr: u8, write: &[u8], timeout: Duration) -> Result<(), Error> { + self.i2c.blocking_write_timeout(addr, write, timeout_fn(timeout)) + } + + /// Blocking write with default timeout, provided in [`TimeoutI2c::new()`] + pub fn blocking_write(&mut self, addr: u8, write: &[u8]) -> Result<(), Error> { + self.blocking_write_timeout(addr, write, self.timeout) + } + + /// Blocking write-read with a custom timeout + pub fn blocking_write_read_timeout( + &mut self, + addr: u8, + write: &[u8], + read: &mut [u8], + timeout: Duration, + ) -> Result<(), Error> { + self.i2c + .blocking_write_read_timeout(addr, write, read, timeout_fn(timeout)) + } + + /// Blocking write-read with default timeout, provided in [`TimeoutI2c::new()`] + pub fn blocking_write_read(&mut self, addr: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error> { + self.blocking_write_read_timeout(addr, write, read, self.timeout) + } +} + +impl<'d, T: Instance, TXDMA, RXDMA> embedded_hal_02::blocking::i2c::Read for TimeoutI2c<'d, T, TXDMA, RXDMA> { + type Error = Error; + + fn read(&mut self, addr: u8, read: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_read(addr, read) + } +} + +impl<'d, T: Instance, TXDMA, RXDMA> embedded_hal_02::blocking::i2c::Write for TimeoutI2c<'d, T, TXDMA, RXDMA> { + type Error = Error; + + fn write(&mut self, addr: u8, write: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(addr, write) + } +} + +impl<'d, T: Instance, TXDMA, RXDMA> embedded_hal_02::blocking::i2c::WriteRead for TimeoutI2c<'d, T, TXDMA, RXDMA> { + type Error = Error; + + fn write_read(&mut self, addr: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_write_read(addr, write, read) + } +} + +#[cfg(feature = "unstable-traits")] +mod eh1 { + use super::*; + + impl<'d, T: Instance, TXDMA, RXDMA> embedded_hal_1::i2c::ErrorType for TimeoutI2c<'d, T, TXDMA, RXDMA> { + type Error = Error; + } + + impl<'d, T: Instance, TXDMA, RXDMA> embedded_hal_1::i2c::I2c for TimeoutI2c<'d, T, TXDMA, RXDMA> { + fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_read(address, read) + } + + fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(address, write) + } + + fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_write_read(address, write, read) + } + + fn transaction( + &mut self, + _address: u8, + _operations: &mut [embedded_hal_1::i2c::Operation<'_>], + ) -> Result<(), Self::Error> { + todo!(); + } + } +} diff --git a/embassy-stm32/src/i2c/v1.rs b/embassy-stm32/src/i2c/v1.rs index 9dc75789a..aa485cd86 100644 --- a/embassy-stm32/src/i2c/v1.rs +++ b/embassy-stm32/src/i2c/v1.rs @@ -1,14 +1,24 @@ use core::marker::PhantomData; use embassy_embedded_hal::SetConfig; -use embassy_hal_common::into_ref; +use embassy_hal_common::{into_ref, PeripheralRef}; +use crate::dma::NoDma; use crate::gpio::sealed::AFType; use crate::gpio::Pull; use crate::i2c::{Error, Instance, SclPin, SdaPin}; use crate::pac::i2c; use crate::time::Hertz; -use crate::Peripheral; +use crate::{interrupt, Peripheral}; + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() {} +} #[non_exhaustive] #[derive(Copy, Clone)] @@ -34,75 +44,78 @@ impl State { } } -pub struct I2c<'d, T: Instance> { +pub struct I2c<'d, T: Instance, TXDMA = NoDma, RXDMA = NoDma> { phantom: PhantomData<&'d mut T>, + #[allow(dead_code)] + tx_dma: PeripheralRef<'d, TXDMA>, + #[allow(dead_code)] + rx_dma: PeripheralRef<'d, RXDMA>, } -impl<'d, T: Instance> I2c<'d, T> { +impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { pub fn new( _peri: impl Peripheral

+ 'd, scl: impl Peripheral

> + 'd, sda: impl Peripheral

> + 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + tx_dma: impl Peripheral

+ 'd, + rx_dma: impl Peripheral

+ 'd, freq: Hertz, config: Config, ) -> Self { - into_ref!(scl, sda); + into_ref!(scl, sda, tx_dma, rx_dma); T::enable(); T::reset(); - unsafe { - scl.set_as_af_pull( - scl.af_num(), - AFType::OutputOpenDrain, - match config.scl_pullup { - true => Pull::Up, - false => Pull::None, - }, - ); - sda.set_as_af_pull( - sda.af_num(), - AFType::OutputOpenDrain, - match config.sda_pullup { - true => Pull::Up, - false => Pull::None, - }, - ); - } + scl.set_as_af_pull( + scl.af_num(), + AFType::OutputOpenDrain, + match config.scl_pullup { + true => Pull::Up, + false => Pull::None, + }, + ); + sda.set_as_af_pull( + sda.af_num(), + AFType::OutputOpenDrain, + match config.sda_pullup { + true => Pull::Up, + false => Pull::None, + }, + ); - unsafe { - T::regs().cr1().modify(|reg| { - reg.set_pe(false); - //reg.set_anfoff(false); - }); - } + T::regs().cr1().modify(|reg| { + reg.set_pe(false); + //reg.set_anfoff(false); + }); let timings = Timings::new(T::frequency(), freq.into()); - unsafe { - T::regs().cr2().modify(|reg| { - reg.set_freq(timings.freq); - }); - T::regs().ccr().modify(|reg| { - reg.set_f_s(timings.mode.f_s()); - reg.set_duty(timings.duty.duty()); - reg.set_ccr(timings.ccr); - }); - T::regs().trise().modify(|reg| { - reg.set_trise(timings.trise); - }); - } + T::regs().cr2().modify(|reg| { + reg.set_freq(timings.freq); + }); + T::regs().ccr().modify(|reg| { + reg.set_f_s(timings.mode.f_s()); + reg.set_duty(timings.duty.duty()); + reg.set_ccr(timings.ccr); + }); + T::regs().trise().modify(|reg| { + reg.set_trise(timings.trise); + }); - unsafe { - T::regs().cr1().modify(|reg| { - reg.set_pe(true); - }); - } + T::regs().cr1().modify(|reg| { + reg.set_pe(true); + }); - Self { phantom: PhantomData } + Self { + phantom: PhantomData, + tx_dma, + rx_dma, + } } - unsafe fn check_and_clear_error_flags(&self) -> Result { + fn check_and_clear_error_flags(&self) -> Result { // Note that flags should only be cleared once they have been registered. If flags are // cleared otherwise, there may be an inherent race condition and flags may be missed. let sr1 = T::regs().sr1().read(); @@ -141,7 +154,12 @@ impl<'d, T: Instance> I2c<'d, T> { Ok(sr1) } - unsafe fn write_bytes(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Error> { + fn write_bytes( + &mut self, + addr: u8, + bytes: &[u8], + check_timeout: impl Fn() -> Result<(), Error>, + ) -> Result<(), Error> { // Send a START condition T::regs().cr1().modify(|reg| { @@ -149,7 +167,9 @@ impl<'d, T: Instance> I2c<'d, T> { }); // Wait until START condition was generated - while !self.check_and_clear_error_flags()?.start() {} + while !self.check_and_clear_error_flags()?.start() { + check_timeout()?; + } // Also wait until signalled we're master and everything is waiting for us while { @@ -157,7 +177,9 @@ impl<'d, T: Instance> I2c<'d, T> { let sr2 = T::regs().sr2().read(); !sr2.msl() && !sr2.busy() - } {} + } { + check_timeout()?; + } // Set up current address, we're trying to talk to T::regs().dr().write(|reg| reg.set_dr(addr << 1)); @@ -165,26 +187,30 @@ impl<'d, T: Instance> I2c<'d, T> { // Wait until address was sent // Wait for the address to be acknowledged // Check for any I2C errors. If a NACK occurs, the ADDR bit will never be set. - while !self.check_and_clear_error_flags()?.addr() {} + while !self.check_and_clear_error_flags()?.addr() { + check_timeout()?; + } // Clear condition by reading SR2 let _ = T::regs().sr2().read(); // Send bytes for c in bytes { - self.send_byte(*c)?; + self.send_byte(*c, &check_timeout)?; } // Fallthrough is success Ok(()) } - unsafe fn send_byte(&self, byte: u8) -> Result<(), Error> { + fn send_byte(&self, byte: u8, check_timeout: impl Fn() -> Result<(), Error>) -> Result<(), Error> { // Wait until we're ready for sending while { // Check for any I2C errors. If a NACK occurs, the ADDR bit will never be set. !self.check_and_clear_error_flags()?.txe() - } {} + } { + check_timeout()?; + } // Push out a byte of data T::regs().dr().write(|reg| reg.set_dr(byte)); @@ -193,70 +219,83 @@ impl<'d, T: Instance> I2c<'d, T> { while { // Check for any potential error conditions. !self.check_and_clear_error_flags()?.btf() - } {} + } { + check_timeout()?; + } Ok(()) } - unsafe fn recv_byte(&self) -> Result { + fn recv_byte(&self, check_timeout: impl Fn() -> Result<(), Error>) -> Result { while { // Check for any potential error conditions. self.check_and_clear_error_flags()?; !T::regs().sr1().read().rxne() - } {} + } { + check_timeout()?; + } let value = T::regs().dr().read().dr(); Ok(value) } - pub fn blocking_read(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), Error> { + pub fn blocking_read_timeout( + &mut self, + addr: u8, + buffer: &mut [u8], + check_timeout: impl Fn() -> Result<(), Error>, + ) -> Result<(), Error> { if let Some((last, buffer)) = buffer.split_last_mut() { // Send a START condition and set ACK bit - unsafe { - T::regs().cr1().modify(|reg| { - reg.set_start(true); - reg.set_ack(true); - }); - } + T::regs().cr1().modify(|reg| { + reg.set_start(true); + reg.set_ack(true); + }); // Wait until START condition was generated - while unsafe { !T::regs().sr1().read().start() } {} + while !self.check_and_clear_error_flags()?.start() { + check_timeout()?; + } // Also wait until signalled we're master and everything is waiting for us while { - let sr2 = unsafe { T::regs().sr2().read() }; + let sr2 = T::regs().sr2().read(); !sr2.msl() && !sr2.busy() - } {} + } { + check_timeout()?; + } // Set up current address, we're trying to talk to - unsafe { T::regs().dr().write(|reg| reg.set_dr((addr << 1) + 1)) } + T::regs().dr().write(|reg| reg.set_dr((addr << 1) + 1)); // Wait until address was sent // Wait for the address to be acknowledged - while unsafe { !self.check_and_clear_error_flags()?.addr() } {} + while !self.check_and_clear_error_flags()?.addr() { + check_timeout()?; + } // Clear condition by reading SR2 - let _ = unsafe { T::regs().sr2().read() }; + let _ = T::regs().sr2().read(); // Receive bytes into buffer for c in buffer { - *c = unsafe { self.recv_byte()? }; + *c = self.recv_byte(&check_timeout)?; } // Prepare to send NACK then STOP after next byte - unsafe { - T::regs().cr1().modify(|reg| { - reg.set_ack(false); - reg.set_stop(true); - }) - } + T::regs().cr1().modify(|reg| { + reg.set_ack(false); + reg.set_stop(true); + }); // Receive last byte - *last = unsafe { self.recv_byte()? }; + *last = self.recv_byte(&check_timeout)?; // Wait for the STOP to be sent. - while unsafe { T::regs().cr1().read().stop() } {} + while T::regs().cr1().read().stop() { + check_timeout()?; + } // Fallthrough is success Ok(()) @@ -265,48 +304,71 @@ impl<'d, T: Instance> I2c<'d, T> { } } - pub fn blocking_write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Error> { - unsafe { - self.write_bytes(addr, bytes)?; - // Send a STOP condition - T::regs().cr1().modify(|reg| reg.set_stop(true)); - // Wait for STOP condition to transmit. - while T::regs().cr1().read().stop() {} - }; + pub fn blocking_read(&mut self, addr: u8, read: &mut [u8]) -> Result<(), Error> { + self.blocking_read_timeout(addr, read, || Ok(())) + } + + pub fn blocking_write_timeout( + &mut self, + addr: u8, + write: &[u8], + check_timeout: impl Fn() -> Result<(), Error>, + ) -> Result<(), Error> { + self.write_bytes(addr, write, &check_timeout)?; + // Send a STOP condition + T::regs().cr1().modify(|reg| reg.set_stop(true)); + // Wait for STOP condition to transmit. + while T::regs().cr1().read().stop() { + check_timeout()?; + } // Fallthrough is success Ok(()) } - pub fn blocking_write_read(&mut self, addr: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Error> { - unsafe { self.write_bytes(addr, bytes)? }; - self.blocking_read(addr, buffer)?; + pub fn blocking_write(&mut self, addr: u8, write: &[u8]) -> Result<(), Error> { + self.blocking_write_timeout(addr, write, || Ok(())) + } + + pub fn blocking_write_read_timeout( + &mut self, + addr: u8, + write: &[u8], + read: &mut [u8], + check_timeout: impl Fn() -> Result<(), Error>, + ) -> Result<(), Error> { + self.write_bytes(addr, write, &check_timeout)?; + self.blocking_read_timeout(addr, read, &check_timeout)?; Ok(()) } + + pub fn blocking_write_read(&mut self, addr: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error> { + self.blocking_write_read_timeout(addr, write, read, || Ok(())) + } } impl<'d, T: Instance> embedded_hal_02::blocking::i2c::Read for I2c<'d, T> { type Error = Error; - fn read(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { - self.blocking_read(addr, buffer) + fn read(&mut self, addr: u8, read: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_read(addr, read) } } impl<'d, T: Instance> embedded_hal_02::blocking::i2c::Write for I2c<'d, T> { type Error = Error; - fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Self::Error> { - self.blocking_write(addr, bytes) + fn write(&mut self, addr: u8, write: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(addr, write) } } impl<'d, T: Instance> embedded_hal_02::blocking::i2c::WriteRead for I2c<'d, T> { type Error = Error; - fn write_read(&mut self, addr: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Self::Error> { - self.blocking_write_read(addr, bytes, buffer) + fn write_read(&mut self, addr: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_write_read(addr, write, read) } } @@ -334,47 +396,26 @@ mod eh1 { type Error = Error; } - impl<'d, T: Instance> embedded_hal_1::i2c::blocking::I2c for I2c<'d, T> { - fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { - self.blocking_read(address, buffer) + impl<'d, T: Instance> embedded_hal_1::i2c::I2c for I2c<'d, T> { + fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_read(address, read) } - fn write(&mut self, address: u8, buffer: &[u8]) -> Result<(), Self::Error> { - self.blocking_write(address, buffer) + fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(address, write) } - fn write_iter(&mut self, _address: u8, _bytes: B) -> Result<(), Self::Error> - where - B: IntoIterator, - { - todo!(); + fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_write_read(address, write, read) } - fn write_iter_read(&mut self, _address: u8, _bytes: B, _buffer: &mut [u8]) -> Result<(), Self::Error> - where - B: IntoIterator, - { - todo!(); - } - - fn write_read(&mut self, address: u8, wr_buffer: &[u8], rd_buffer: &mut [u8]) -> Result<(), Self::Error> { - self.blocking_write_read(address, wr_buffer, rd_buffer) - } - - fn transaction<'a>( + fn transaction( &mut self, _address: u8, - _operations: &mut [embedded_hal_1::i2c::blocking::Operation<'a>], + _operations: &mut [embedded_hal_1::i2c::Operation<'_>], ) -> Result<(), Self::Error> { todo!(); } - - fn transaction_iter<'a, O>(&mut self, _address: u8, _operations: O) -> Result<(), Self::Error> - where - O: IntoIterator>, - { - todo!(); - } } } @@ -423,8 +464,6 @@ impl Timings { assert!(freq >= 2 && freq <= 50); // Configure bus frequency into I2C peripheral - //self.i2c.cr2.write(|w| unsafe { w.freq().bits(freq as u8) }); - let trise = if speed <= 100_000 { freq + 1 } else { @@ -484,18 +523,16 @@ impl<'d, T: Instance> SetConfig for I2c<'d, T> { type Config = Hertz; fn set_config(&mut self, config: &Self::Config) { let timings = Timings::new(T::frequency(), *config); - unsafe { - T::regs().cr2().modify(|reg| { - reg.set_freq(timings.freq); - }); - T::regs().ccr().modify(|reg| { - reg.set_f_s(timings.mode.f_s()); - reg.set_duty(timings.duty.duty()); - reg.set_ccr(timings.ccr); - }); - T::regs().trise().modify(|reg| { - reg.set_trise(timings.trise); - }); - } + T::regs().cr2().modify(|reg| { + reg.set_freq(timings.freq); + }); + T::regs().ccr().modify(|reg| { + reg.set_f_s(timings.mode.f_s()); + reg.set_duty(timings.duty.duty()); + reg.set_ccr(timings.ccr); + }); + T::regs().trise().modify(|reg| { + reg.set_trise(timings.trise); + }); } } diff --git a/embassy-stm32/src/i2c/v2.rs b/embassy-stm32/src/i2c/v2.rs index 07a3105da..208d1527d 100644 --- a/embassy-stm32/src/i2c/v2.rs +++ b/embassy-stm32/src/i2c/v2.rs @@ -1,21 +1,42 @@ use core::cmp; +use core::future::poll_fn; +use core::marker::PhantomData; use core::task::Poll; -use atomic_polyfill::{AtomicUsize, Ordering}; use embassy_embedded_hal::SetConfig; use embassy_hal_common::drop::OnDrop; use embassy_hal_common::{into_ref, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; -use futures::future::poll_fn; -use crate::dma::NoDma; +use crate::dma::{NoDma, Transfer}; use crate::gpio::sealed::AFType; use crate::gpio::Pull; use crate::i2c::{Error, Instance, SclPin, SdaPin}; -use crate::interrupt::InterruptExt; +use crate::interrupt::typelevel::Interrupt; use crate::pac::i2c; use crate::time::Hertz; -use crate::Peripheral; +use crate::{interrupt, Peripheral}; + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let regs = T::regs(); + let isr = regs.isr().read(); + + if isr.tcr() || isr.tc() { + T::state().waker.wake(); + } + // The flag can only be cleared by writting to nbytes, we won't do that here, so disable + // the interrupt + critical_section::with(|_| { + regs.cr1().modify(|w| w.set_tcie(false)); + }); + } +} #[non_exhaustive] #[derive(Copy, Clone)] @@ -35,14 +56,12 @@ impl Default for Config { pub struct State { waker: AtomicWaker, - chunks_transferred: AtomicUsize, } impl State { pub(crate) const fn new() -> Self { Self { waker: AtomicWaker::new(), - chunks_transferred: AtomicUsize::new(0), } } } @@ -59,64 +78,55 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { peri: impl Peripheral

+ 'd, scl: impl Peripheral

> + 'd, sda: impl Peripheral

> + 'd, - irq: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, tx_dma: impl Peripheral

+ 'd, rx_dma: impl Peripheral

+ 'd, freq: Hertz, config: Config, ) -> Self { - into_ref!(peri, irq, scl, sda, tx_dma, rx_dma); + into_ref!(peri, scl, sda, tx_dma, rx_dma); T::enable(); T::reset(); - unsafe { - scl.set_as_af_pull( - scl.af_num(), - AFType::OutputOpenDrain, - match config.scl_pullup { - true => Pull::Up, - false => Pull::None, - }, - ); - sda.set_as_af_pull( - sda.af_num(), - AFType::OutputOpenDrain, - match config.sda_pullup { - true => Pull::Up, - false => Pull::None, - }, - ); - } + scl.set_as_af_pull( + scl.af_num(), + AFType::OutputOpenDrain, + match config.scl_pullup { + true => Pull::Up, + false => Pull::None, + }, + ); + sda.set_as_af_pull( + sda.af_num(), + AFType::OutputOpenDrain, + match config.sda_pullup { + true => Pull::Up, + false => Pull::None, + }, + ); - unsafe { - T::regs().cr1().modify(|reg| { - reg.set_pe(false); - reg.set_anfoff(false); - }); - } + T::regs().cr1().modify(|reg| { + reg.set_pe(false); + reg.set_anfoff(false); + }); let timings = Timings::new(T::frequency(), freq.into()); - unsafe { - T::regs().timingr().write(|reg| { - reg.set_presc(timings.prescale); - reg.set_scll(timings.scll); - reg.set_sclh(timings.sclh); - reg.set_sdadel(timings.sdadel); - reg.set_scldel(timings.scldel); - }); - } + T::regs().timingr().write(|reg| { + reg.set_presc(timings.prescale); + reg.set_scll(timings.scll); + reg.set_sclh(timings.sclh); + reg.set_sdadel(timings.sdadel); + reg.set_scldel(timings.scldel); + }); - unsafe { - T::regs().cr1().modify(|reg| { - reg.set_pe(true); - }); - } + T::regs().cr1().modify(|reg| { + reg.set_pe(true); + }); - irq.set_handler(Self::on_interrupt); - irq.unpend(); - irq.enable(); + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; Self { _peri: peri, @@ -125,36 +135,27 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { } } - unsafe fn on_interrupt(_: *mut ()) { - let regs = T::regs(); - let isr = regs.isr().read(); - - if isr.tcr() || isr.tc() { - let state = T::state(); - state.chunks_transferred.fetch_add(1, Ordering::Relaxed); - state.waker.wake(); - } - // The flag can only be cleared by writting to nbytes, we won't do that here, so disable - // the interrupt - critical_section::with(|_| { - regs.cr1().modify(|w| w.set_tcie(false)); - }); - } - fn master_stop(&mut self) { - unsafe { - T::regs().cr2().write(|w| w.set_stop(true)); - } + T::regs().cr2().write(|w| w.set_stop(true)); } - unsafe fn master_read(address: u8, length: usize, stop: Stop, reload: bool, restart: bool) { + fn master_read( + address: u8, + length: usize, + stop: Stop, + reload: bool, + restart: bool, + check_timeout: impl Fn() -> Result<(), Error>, + ) -> Result<(), Error> { assert!(length < 256); if !restart { // Wait for any previous address sequence to end // automatically. This could be up to 50% of a bus // cycle (ie. up to 0.5/freq) - while T::regs().cr2().read().start() {} + while T::regs().cr2().read().start() { + check_timeout()?; + } } // Set START and prepare to receive bytes into @@ -176,15 +177,25 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { w.set_autoend(stop.autoend()); w.set_reload(reload); }); + + Ok(()) } - unsafe fn master_write(address: u8, length: usize, stop: Stop, reload: bool) { + fn master_write( + address: u8, + length: usize, + stop: Stop, + reload: bool, + check_timeout: impl Fn() -> Result<(), Error>, + ) -> Result<(), Error> { assert!(length < 256); // Wait for any previous address sequence to end // automatically. This could be up to 50% of a bus // cycle (ie. up to 0.5/freq) - while T::regs().cr2().read().start() {} + while T::regs().cr2().read().start() { + check_timeout()?; + } let reload = if reload { i2c::vals::Reload::NOTCOMPLETED @@ -204,12 +215,20 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { w.set_autoend(stop.autoend()); w.set_reload(reload); }); + + Ok(()) } - unsafe fn master_continue(length: usize, reload: bool) { + fn master_continue( + length: usize, + reload: bool, + check_timeout: impl Fn() -> Result<(), Error>, + ) -> Result<(), Error> { assert!(length < 256 && length > 0); - while !T::regs().isr().read().tcr() {} + while !T::regs().isr().read().tcr() { + check_timeout()?; + } let reload = if reload { i2c::vals::Reload::NOTCOMPLETED @@ -221,6 +240,8 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { w.set_nbytes(length as u8); w.set_reload(reload); }); + + Ok(()) } fn flush_txdr(&self) { @@ -228,13 +249,11 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { //$i2c.txdr.write(|w| w.txdata().bits(0)); //} - unsafe { - if T::regs().isr().read().txis() { - T::regs().txdr().write(|w| w.set_txdata(0)); - } - if T::regs().isr().read().txe() { - T::regs().isr().modify(|w| w.set_txe(true)) - } + if T::regs().isr().read().txis() { + T::regs().txdr().write(|w| w.set_txdata(0)); + } + if !T::regs().isr().read().txe() { + T::regs().isr().modify(|w| w.set_txe(true)) } // If TXDR is not flagged as empty, write 1 to flush it @@ -243,111 +262,117 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { //} } - fn wait_txe(&self) -> Result<(), Error> { + fn wait_txe(&self, check_timeout: impl Fn() -> Result<(), Error>) -> Result<(), Error> { loop { - unsafe { - let isr = T::regs().isr().read(); - if isr.txe() { - return Ok(()); - } else if isr.berr() { - T::regs().icr().write(|reg| reg.set_berrcf(true)); - return Err(Error::Bus); - } else if isr.arlo() { - T::regs().icr().write(|reg| reg.set_arlocf(true)); - return Err(Error::Arbitration); - } else if isr.nackf() { - T::regs().icr().write(|reg| reg.set_nackcf(true)); - self.flush_txdr(); - return Err(Error::Nack); - } + let isr = T::regs().isr().read(); + if isr.txe() { + return Ok(()); + } else if isr.berr() { + T::regs().icr().write(|reg| reg.set_berrcf(true)); + return Err(Error::Bus); + } else if isr.arlo() { + T::regs().icr().write(|reg| reg.set_arlocf(true)); + return Err(Error::Arbitration); + } else if isr.nackf() { + T::regs().icr().write(|reg| reg.set_nackcf(true)); + self.flush_txdr(); + return Err(Error::Nack); } + + check_timeout()?; } } - fn wait_rxne(&self) -> Result<(), Error> { + fn wait_rxne(&self, check_timeout: impl Fn() -> Result<(), Error>) -> Result<(), Error> { loop { - unsafe { - let isr = T::regs().isr().read(); - if isr.rxne() { - return Ok(()); - } else if isr.berr() { - T::regs().icr().write(|reg| reg.set_berrcf(true)); - return Err(Error::Bus); - } else if isr.arlo() { - T::regs().icr().write(|reg| reg.set_arlocf(true)); - return Err(Error::Arbitration); - } else if isr.nackf() { - T::regs().icr().write(|reg| reg.set_nackcf(true)); - self.flush_txdr(); - return Err(Error::Nack); - } + let isr = T::regs().isr().read(); + if isr.rxne() { + return Ok(()); + } else if isr.berr() { + T::regs().icr().write(|reg| reg.set_berrcf(true)); + return Err(Error::Bus); + } else if isr.arlo() { + T::regs().icr().write(|reg| reg.set_arlocf(true)); + return Err(Error::Arbitration); + } else if isr.nackf() { + T::regs().icr().write(|reg| reg.set_nackcf(true)); + self.flush_txdr(); + return Err(Error::Nack); } + + check_timeout()?; } } - fn wait_tc(&self) -> Result<(), Error> { + fn wait_tc(&self, check_timeout: impl Fn() -> Result<(), Error>) -> Result<(), Error> { loop { - unsafe { - let isr = T::regs().isr().read(); - if isr.tc() { - return Ok(()); - } else if isr.berr() { - T::regs().icr().write(|reg| reg.set_berrcf(true)); - return Err(Error::Bus); - } else if isr.arlo() { - T::regs().icr().write(|reg| reg.set_arlocf(true)); - return Err(Error::Arbitration); - } else if isr.nackf() { - T::regs().icr().write(|reg| reg.set_nackcf(true)); - self.flush_txdr(); - return Err(Error::Nack); - } + let isr = T::regs().isr().read(); + if isr.tc() { + return Ok(()); + } else if isr.berr() { + T::regs().icr().write(|reg| reg.set_berrcf(true)); + return Err(Error::Bus); + } else if isr.arlo() { + T::regs().icr().write(|reg| reg.set_arlocf(true)); + return Err(Error::Arbitration); + } else if isr.nackf() { + T::regs().icr().write(|reg| reg.set_nackcf(true)); + self.flush_txdr(); + return Err(Error::Nack); } + + check_timeout()?; } } - fn read_internal(&mut self, address: u8, buffer: &mut [u8], restart: bool) -> Result<(), Error> { - let completed_chunks = buffer.len() / 255; - let total_chunks = if completed_chunks * 255 == buffer.len() { + fn read_internal( + &mut self, + address: u8, + read: &mut [u8], + restart: bool, + check_timeout: impl Fn() -> Result<(), Error>, + ) -> Result<(), Error> { + let completed_chunks = read.len() / 255; + let total_chunks = if completed_chunks * 255 == read.len() { completed_chunks } else { completed_chunks + 1 }; let last_chunk_idx = total_chunks.saturating_sub(1); - unsafe { - Self::master_read( - address, - buffer.len().min(255), - Stop::Automatic, - last_chunk_idx != 0, - restart, - ); - } + Self::master_read( + address, + read.len().min(255), + Stop::Automatic, + last_chunk_idx != 0, + restart, + &check_timeout, + )?; - for (number, chunk) in buffer.chunks_mut(255).enumerate() { + for (number, chunk) in read.chunks_mut(255).enumerate() { if number != 0 { - // NOTE(unsafe) We have &mut self - unsafe { - Self::master_continue(chunk.len(), number != last_chunk_idx); - } + Self::master_continue(chunk.len(), number != last_chunk_idx, &check_timeout)?; } for byte in chunk { // Wait until we have received something - self.wait_rxne()?; + self.wait_rxne(&check_timeout)?; - unsafe { - *byte = T::regs().rxdr().read().rxdata(); - } + *byte = T::regs().rxdr().read().rxdata(); } } Ok(()) } - fn write_internal(&mut self, address: u8, bytes: &[u8], send_stop: bool) -> Result<(), Error> { - let completed_chunks = bytes.len() / 255; - let total_chunks = if completed_chunks * 255 == bytes.len() { + fn write_internal( + &mut self, + address: u8, + write: &[u8], + send_stop: bool, + check_timeout: impl Fn() -> Result<(), Error>, + ) -> Result<(), Error> { + let completed_chunks = write.len() / 255; + let total_chunks = if completed_chunks * 255 == write.len() { completed_chunks } else { completed_chunks + 1 @@ -357,56 +382,58 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { // I2C start // // ST SAD+W - // NOTE(unsafe) We have &mut self - unsafe { - Self::master_write(address, bytes.len().min(255), Stop::Software, last_chunk_idx != 0); + if let Err(err) = Self::master_write( + address, + write.len().min(255), + Stop::Software, + last_chunk_idx != 0, + &check_timeout, + ) { + if send_stop { + self.master_stop(); + } + return Err(err); } - for (number, chunk) in bytes.chunks(255).enumerate() { + for (number, chunk) in write.chunks(255).enumerate() { if number != 0 { - // NOTE(unsafe) We have &mut self - unsafe { - Self::master_continue(chunk.len(), number != last_chunk_idx); - } + Self::master_continue(chunk.len(), number != last_chunk_idx, &check_timeout)?; } for byte in chunk { // Wait until we are allowed to send data // (START has been ACKed or last byte when // through) - self.wait_txe()?; - - unsafe { - T::regs().txdr().write(|w| w.set_txdata(*byte)); + if let Err(err) = self.wait_txe(&check_timeout) { + if send_stop { + self.master_stop(); + } + return Err(err); } + + T::regs().txdr().write(|w| w.set_txdata(*byte)); } } // Wait until the write finishes - self.wait_tc()?; - + let result = self.wait_tc(&check_timeout); if send_stop { self.master_stop(); } - Ok(()) + result } async fn write_dma_internal( &mut self, address: u8, - bytes: &[u8], + write: &[u8], first_slice: bool, last_slice: bool, + check_timeout: impl Fn() -> Result<(), Error>, ) -> Result<(), Error> where TXDMA: crate::i2c::TxDma, { - let total_len = bytes.len(); - let completed_chunks = total_len / 255; - let total_chunks = if completed_chunks * 255 == total_len { - completed_chunks - } else { - completed_chunks + 1 - }; + let total_len = write.len(); let dma_transfer = unsafe { let regs = T::regs(); @@ -416,87 +443,86 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { w.set_tcie(true); } }); - let dst = regs.txdr().ptr() as *mut u8; + let dst = regs.txdr().as_ptr() as *mut u8; let ch = &mut self.tx_dma; let request = ch.request(); - crate::dma::write(ch, request, bytes, dst) + Transfer::new_write(ch, request, write, dst, Default::default()) }; let state = T::state(); - state.chunks_transferred.store(0, Ordering::Relaxed); let mut remaining_len = total_len; - let _on_drop = OnDrop::new(|| { + let on_drop = OnDrop::new(|| { let regs = T::regs(); - unsafe { - regs.cr1().modify(|w| { - if last_slice { - w.set_txdmaen(false); - } - w.set_tcie(false); - }) - } + regs.cr1().modify(|w| { + if last_slice { + w.set_txdmaen(false); + } + w.set_tcie(false); + }) }); - // NOTE(unsafe) self.tx_dma does not fiddle with the i2c registers - if first_slice { - unsafe { - Self::master_write( - address, - total_len.min(255), - Stop::Software, - (total_chunks != 1) || !last_slice, - ); - } - } else { - unsafe { - Self::master_continue(total_len.min(255), (total_chunks != 1) || !last_slice); - T::regs().cr1().modify(|w| w.set_tcie(true)); - } - } - poll_fn(|cx| { state.waker.register(cx.waker()); - let chunks_transferred = state.chunks_transferred.load(Ordering::Relaxed); - if chunks_transferred == total_chunks { - return Poll::Ready(()); - } else if chunks_transferred != 0 { - remaining_len = remaining_len.saturating_sub(255); - let last_piece = (chunks_transferred + 1 == total_chunks) && last_slice; - - // NOTE(unsafe) self.tx_dma does not fiddle with the i2c registers - unsafe { - Self::master_continue(remaining_len.min(255), !last_piece); + let isr = T::regs().isr().read(); + if remaining_len == total_len { + if first_slice { + Self::master_write( + address, + total_len.min(255), + Stop::Software, + (total_len > 255) || !last_slice, + &check_timeout, + )?; + } else { + Self::master_continue(total_len.min(255), (total_len > 255) || !last_slice, &check_timeout)?; T::regs().cr1().modify(|w| w.set_tcie(true)); } + } else if !(isr.tcr() || isr.tc()) { + // poll_fn was woken without an interrupt present + return Poll::Pending; + } else if remaining_len == 0 { + return Poll::Ready(Ok(())); + } else { + let last_piece = (remaining_len <= 255) && last_slice; + + if let Err(e) = Self::master_continue(remaining_len.min(255), !last_piece, &check_timeout) { + return Poll::Ready(Err(e)); + } + T::regs().cr1().modify(|w| w.set_tcie(true)); } + + remaining_len = remaining_len.saturating_sub(255); Poll::Pending }) - .await; + .await?; dma_transfer.await; if last_slice { // This should be done already - self.wait_tc()?; + self.wait_tc(&check_timeout)?; self.master_stop(); } + + drop(on_drop); + Ok(()) } - async fn read_dma_internal(&mut self, address: u8, buffer: &mut [u8], restart: bool) -> Result<(), Error> + async fn read_dma_internal( + &mut self, + address: u8, + buffer: &mut [u8], + restart: bool, + check_timeout: impl Fn() -> Result<(), Error>, + ) -> Result<(), Error> where RXDMA: crate::i2c::RxDma, { let total_len = buffer.len(); - let completed_chunks = total_len / 255; - let total_chunks = if completed_chunks * 255 == total_len { - completed_chunks - } else { - completed_chunks + 1 - }; let dma_transfer = unsafe { let regs = T::regs(); @@ -504,82 +530,89 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { w.set_rxdmaen(true); w.set_tcie(true); }); - let src = regs.rxdr().ptr() as *mut u8; + let src = regs.rxdr().as_ptr() as *mut u8; let ch = &mut self.rx_dma; let request = ch.request(); - crate::dma::read(ch, request, src, buffer) + Transfer::new_read(ch, request, src, buffer, Default::default()) }; let state = T::state(); - state.chunks_transferred.store(0, Ordering::Relaxed); let mut remaining_len = total_len; - let _on_drop = OnDrop::new(|| { + let on_drop = OnDrop::new(|| { let regs = T::regs(); - unsafe { - regs.cr1().modify(|w| { - w.set_rxdmaen(false); - w.set_tcie(false); - }) - } + regs.cr1().modify(|w| { + w.set_rxdmaen(false); + w.set_tcie(false); + }) }); - // NOTE(unsafe) self.rx_dma does not fiddle with the i2c registers - unsafe { - Self::master_read(address, total_len.min(255), Stop::Software, total_chunks != 1, restart); - } - poll_fn(|cx| { state.waker.register(cx.waker()); - let chunks_transferred = state.chunks_transferred.load(Ordering::Relaxed); - if chunks_transferred == total_chunks { - return Poll::Ready(()); - } else if chunks_transferred != 0 { - remaining_len = remaining_len.saturating_sub(255); - let last_piece = chunks_transferred + 1 == total_chunks; + let isr = T::regs().isr().read(); + if remaining_len == total_len { + Self::master_read( + address, + total_len.min(255), + Stop::Software, + total_len > 255, + restart, + &check_timeout, + )?; + } else if !(isr.tcr() || isr.tc()) { + // poll_fn was woken without an interrupt present + return Poll::Pending; + } else if remaining_len == 0 { + return Poll::Ready(Ok(())); + } else { + let last_piece = remaining_len <= 255; - // NOTE(unsafe) self.rx_dma does not fiddle with the i2c registers - unsafe { - Self::master_continue(remaining_len.min(255), !last_piece); - T::regs().cr1().modify(|w| w.set_tcie(true)); + if let Err(e) = Self::master_continue(remaining_len.min(255), !last_piece, &check_timeout) { + return Poll::Ready(Err(e)); } + T::regs().cr1().modify(|w| w.set_tcie(true)); } + + remaining_len = remaining_len.saturating_sub(255); Poll::Pending }) - .await; + .await?; dma_transfer.await; // This should be done already - self.wait_tc()?; + self.wait_tc(&check_timeout)?; self.master_stop(); + + drop(on_drop); + Ok(()) } // ========================= // Async public API - pub async fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Error> + pub async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Error> where TXDMA: crate::i2c::TxDma, { - if bytes.is_empty() { - self.write_internal(address, bytes, true) + if write.is_empty() { + self.write_internal(address, write, true, || Ok(())) } else { - self.write_dma_internal(address, bytes, true, true).await + self.write_dma_internal(address, write, true, true, || Ok(())).await } } - pub async fn write_vectored(&mut self, address: u8, bytes: &[&[u8]]) -> Result<(), Error> + pub async fn write_vectored(&mut self, address: u8, write: &[&[u8]]) -> Result<(), Error> where TXDMA: crate::i2c::TxDma, { - if bytes.is_empty() { + if write.is_empty() { return Err(Error::ZeroLengthTransfer); } - let mut iter = bytes.iter(); + let mut iter = write.iter(); let mut first = true; let mut current = iter.next(); @@ -587,7 +620,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { let next = iter.next(); let is_last = next.is_none(); - self.write_dma_internal(address, c, first, is_last).await?; + self.write_dma_internal(address, c, first, is_last, || Ok(())).await?; first = false; current = next; } @@ -599,27 +632,27 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { RXDMA: crate::i2c::RxDma, { if buffer.is_empty() { - self.read_internal(address, buffer, false) + self.read_internal(address, buffer, false, || Ok(())) } else { - self.read_dma_internal(address, buffer, false).await + self.read_dma_internal(address, buffer, false, || Ok(())).await } } - pub async fn write_read(&mut self, address: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Error> + pub async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error> where TXDMA: super::TxDma, RXDMA: super::RxDma, { - if bytes.is_empty() { - self.write_internal(address, bytes, false)?; + if write.is_empty() { + self.write_internal(address, write, false, || Ok(()))?; } else { - self.write_dma_internal(address, bytes, true, true).await?; + self.write_dma_internal(address, write, true, true, || Ok(())).await?; } - if buffer.is_empty() { - self.read_internal(address, buffer, true)?; + if read.is_empty() { + self.read_internal(address, read, true, || Ok(()))?; } else { - self.read_dma_internal(address, buffer, true).await?; + self.read_dma_internal(address, read, true, || Ok(())).await?; } Ok(()) @@ -628,39 +661,73 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { // ========================= // Blocking public API - pub fn blocking_read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> { - self.read_internal(address, buffer, false) + pub fn blocking_read_timeout( + &mut self, + address: u8, + read: &mut [u8], + check_timeout: impl Fn() -> Result<(), Error>, + ) -> Result<(), Error> { + self.read_internal(address, read, false, &check_timeout) // Automatic Stop } - pub fn blocking_write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Error> { - self.write_internal(address, bytes, true) + pub fn blocking_read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Error> { + self.blocking_read_timeout(address, read, || Ok(())) } - pub fn blocking_write_read(&mut self, address: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Error> { - self.write_internal(address, bytes, false)?; - self.read_internal(address, buffer, true) + pub fn blocking_write_timeout( + &mut self, + address: u8, + write: &[u8], + check_timeout: impl Fn() -> Result<(), Error>, + ) -> Result<(), Error> { + self.write_internal(address, write, true, &check_timeout) + } + + pub fn blocking_write(&mut self, address: u8, write: &[u8]) -> Result<(), Error> { + self.blocking_write_timeout(address, write, || Ok(())) + } + + pub fn blocking_write_read_timeout( + &mut self, + address: u8, + write: &[u8], + read: &mut [u8], + check_timeout: impl Fn() -> Result<(), Error>, + ) -> Result<(), Error> { + self.write_internal(address, write, false, &check_timeout)?; + self.read_internal(address, read, true, &check_timeout) // Automatic Stop } - pub fn blocking_write_vectored(&mut self, address: u8, bytes: &[&[u8]]) -> Result<(), Error> { - if bytes.is_empty() { + pub fn blocking_write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error> { + self.blocking_write_read_timeout(address, write, read, || Ok(())) + } + + pub fn blocking_write_vectored_timeout( + &mut self, + address: u8, + write: &[&[u8]], + check_timeout: impl Fn() -> Result<(), Error>, + ) -> Result<(), Error> { + if write.is_empty() { return Err(Error::ZeroLengthTransfer); } - let first_length = bytes[0].len(); - let last_slice_index = bytes.len() - 1; + let first_length = write[0].len(); + let last_slice_index = write.len() - 1; - // NOTE(unsafe) We have &mut self - unsafe { - Self::master_write( - address, - first_length.min(255), - Stop::Software, - (first_length > 255) || (last_slice_index != 0), - ); + if let Err(err) = Self::master_write( + address, + first_length.min(255), + Stop::Software, + (first_length > 255) || (last_slice_index != 0), + &check_timeout, + ) { + self.master_stop(); + return Err(err); } - for (idx, slice) in bytes.iter().enumerate() { + for (idx, slice) in write.iter().enumerate() { let slice_len = slice.len(); let completed_chunks = slice_len / 255; let total_chunks = if completed_chunks * 255 == slice_len { @@ -671,17 +738,25 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { let last_chunk_idx = total_chunks.saturating_sub(1); if idx != 0 { - // NOTE(unsafe) We have &mut self - unsafe { - Self::master_continue(slice_len.min(255), (idx != last_slice_index) || (slice_len > 255)); + if let Err(err) = Self::master_continue( + slice_len.min(255), + (idx != last_slice_index) || (slice_len > 255), + &check_timeout, + ) { + self.master_stop(); + return Err(err); } } for (number, chunk) in slice.chunks(255).enumerate() { if number != 0 { - // NOTE(unsafe) We have &mut self - unsafe { - Self::master_continue(chunk.len(), (number != last_chunk_idx) || (idx != last_slice_index)); + if let Err(err) = Self::master_continue( + chunk.len(), + (number != last_chunk_idx) || (idx != last_slice_index), + &check_timeout, + ) { + self.master_stop(); + return Err(err); } } @@ -689,21 +764,25 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> { // Wait until we are allowed to send data // (START has been ACKed or last byte when // through) - self.wait_txe()?; + if let Err(err) = self.wait_txe(&check_timeout) { + self.master_stop(); + return Err(err); + } // Put byte on the wire //self.i2c.txdr.write(|w| w.txdata().bits(*byte)); - unsafe { - T::regs().txdr().write(|w| w.set_txdata(*byte)); - } + T::regs().txdr().write(|w| w.set_txdata(*byte)); } } } // Wait until the write finishes - self.wait_tc()?; + let result = self.wait_tc(&check_timeout); self.master_stop(); + result + } - Ok(()) + pub fn blocking_write_vectored(&mut self, address: u8, write: &[&[u8]]) -> Result<(), Error> { + self.blocking_write_vectored_timeout(address, write, || Ok(())) } } @@ -721,16 +800,16 @@ mod eh02 { impl<'d, T: Instance> embedded_hal_02::blocking::i2c::Write for I2c<'d, T> { type Error = Error; - fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Self::Error> { - self.blocking_write(address, bytes) + fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(address, write) } } impl<'d, T: Instance> embedded_hal_02::blocking::i2c::WriteRead for I2c<'d, T> { type Error = Error; - fn write_read(&mut self, address: u8, bytes: &[u8], buffer: &mut [u8]) -> Result<(), Self::Error> { - self.blocking_write_read(address, bytes, buffer) + fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_write_read(address, write, read) } } } @@ -883,90 +962,55 @@ mod eh1 { type Error = Error; } - impl<'d, T: Instance> embedded_hal_1::i2c::blocking::I2c for I2c<'d, T, NoDma, NoDma> { - fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { - self.blocking_read(address, buffer) + impl<'d, T: Instance> embedded_hal_1::i2c::I2c for I2c<'d, T, NoDma, NoDma> { + fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_read(address, read) } - fn write(&mut self, address: u8, buffer: &[u8]) -> Result<(), Self::Error> { - self.blocking_write(address, buffer) + fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(address, write) } - fn write_iter(&mut self, _address: u8, _bytes: B) -> Result<(), Self::Error> - where - B: IntoIterator, - { - todo!(); + fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> { + self.blocking_write_read(address, write, read) } - fn write_iter_read(&mut self, _address: u8, _bytes: B, _buffer: &mut [u8]) -> Result<(), Self::Error> - where - B: IntoIterator, - { - todo!(); - } - - fn write_read(&mut self, address: u8, wr_buffer: &[u8], rd_buffer: &mut [u8]) -> Result<(), Self::Error> { - self.blocking_write_read(address, wr_buffer, rd_buffer) - } - - fn transaction<'a>( + fn transaction( &mut self, _address: u8, - _operations: &mut [embedded_hal_1::i2c::blocking::Operation<'a>], + _operations: &mut [embedded_hal_1::i2c::Operation<'_>], ) -> Result<(), Self::Error> { todo!(); } - - fn transaction_iter<'a, O>(&mut self, _address: u8, _operations: O) -> Result<(), Self::Error> - where - O: IntoIterator>, - { - todo!(); - } } } -cfg_if::cfg_if! { - if #[cfg(all(feature = "unstable-traits", feature = "nightly"))] { - use super::{RxDma, TxDma}; - use core::future::Future; +#[cfg(all(feature = "unstable-traits", feature = "nightly"))] +mod eha { + use super::super::{RxDma, TxDma}; + use super::*; - impl<'d, T: Instance, TXDMA: TxDma, RXDMA: RxDma> embedded_hal_async::i2c::I2c - for I2c<'d, T, TXDMA, RXDMA> - { - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; + impl<'d, T: Instance, TXDMA: TxDma, RXDMA: RxDma> embedded_hal_async::i2c::I2c for I2c<'d, T, TXDMA, RXDMA> { + async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> { + self.read(address, read).await + } - fn read<'a>(&'a mut self, address: u8, buffer: &'a mut [u8]) -> Self::ReadFuture<'a> { - self.read(address, buffer) - } + async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> { + self.write(address, write).await + } - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - fn write<'a>(&'a mut self, address: u8, bytes: &'a [u8]) -> Self::WriteFuture<'a> { - self.write(address, bytes) - } + async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> { + self.write_read(address, write, read).await + } - type WriteReadFuture<'a> = impl Future> + 'a where Self: 'a; - fn write_read<'a>( - &'a mut self, - address: u8, - bytes: &'a [u8], - buffer: &'a mut [u8], - ) -> Self::WriteReadFuture<'a> { - self.write_read(address, bytes, buffer) - } - - type TransactionFuture<'a, 'b> = impl Future> + 'a where Self: 'a, 'b: 'a; - - fn transaction<'a, 'b>( - &'a mut self, - address: u8, - operations: &'a mut [embedded_hal_async::i2c::Operation<'b>], - ) -> Self::TransactionFuture<'a, 'b> { - let _ = address; - let _ = operations; - async move { todo!() } - } + async fn transaction( + &mut self, + address: u8, + operations: &mut [embedded_hal_1::i2c::Operation<'_>], + ) -> Result<(), Self::Error> { + let _ = address; + let _ = operations; + todo!() } } } @@ -975,14 +1019,12 @@ impl<'d, T: Instance> SetConfig for I2c<'d, T> { type Config = Hertz; fn set_config(&mut self, config: &Self::Config) { let timings = Timings::new(T::frequency(), *config); - unsafe { - T::regs().timingr().write(|reg| { - reg.set_presc(timings.prescale); - reg.set_scll(timings.scll); - reg.set_sclh(timings.sclh); - reg.set_sdadel(timings.sdadel); - reg.set_scldel(timings.scldel); - }); - } + T::regs().timingr().write(|reg| { + reg.set_presc(timings.prescale); + reg.set_scll(timings.scll); + reg.set_sclh(timings.sclh); + reg.set_sdadel(timings.sdadel); + reg.set_scldel(timings.scldel); + }); } } diff --git a/embassy-stm32/src/i2s.rs b/embassy-stm32/src/i2s.rs new file mode 100644 index 000000000..62dda69b4 --- /dev/null +++ b/embassy-stm32/src/i2s.rs @@ -0,0 +1,302 @@ +use embassy_hal_common::into_ref; + +use crate::gpio::sealed::{AFType, Pin as _}; +use crate::gpio::AnyPin; +use crate::pac::spi::vals; +use crate::rcc::get_freqs; +use crate::spi::{Config as SpiConfig, *}; +use crate::time::Hertz; +use crate::{Peripheral, PeripheralRef}; + +#[derive(Copy, Clone)] +pub enum Mode { + Master, + Slave, +} + +#[derive(Copy, Clone)] +pub enum Function { + Transmit, + Receive, +} + +#[derive(Copy, Clone)] +pub enum Standard { + Philips, + MsbFirst, + LsbFirst, + PcmLongSync, + PcmShortSync, +} + +impl Standard { + #[cfg(any(spi_v1, spi_f1))] + pub const fn i2sstd(&self) -> vals::I2sstd { + match self { + Standard::Philips => vals::I2sstd::PHILIPS, + Standard::MsbFirst => vals::I2sstd::MSB, + Standard::LsbFirst => vals::I2sstd::LSB, + Standard::PcmLongSync => vals::I2sstd::PCM, + Standard::PcmShortSync => vals::I2sstd::PCM, + } + } + + #[cfg(any(spi_v1, spi_f1))] + pub const fn pcmsync(&self) -> vals::Pcmsync { + match self { + Standard::PcmLongSync => vals::Pcmsync::LONG, + _ => vals::Pcmsync::SHORT, + } + } +} + +#[derive(Copy, Clone)] +pub enum Format { + /// 16 bit data length on 16 bit wide channel + Data16Channel16, + /// 16 bit data length on 32 bit wide channel + Data16Channel32, + /// 24 bit data length on 32 bit wide channel + Data24Channel32, + /// 32 bit data length on 32 bit wide channel + Data32Channel32, +} + +impl Format { + #[cfg(any(spi_v1, spi_f1))] + pub const fn datlen(&self) -> vals::Datlen { + match self { + Format::Data16Channel16 => vals::Datlen::SIXTEENBIT, + Format::Data16Channel32 => vals::Datlen::SIXTEENBIT, + Format::Data24Channel32 => vals::Datlen::TWENTYFOURBIT, + Format::Data32Channel32 => vals::Datlen::THIRTYTWOBIT, + } + } + + #[cfg(any(spi_v1, spi_f1))] + pub const fn chlen(&self) -> vals::Chlen { + match self { + Format::Data16Channel16 => vals::Chlen::SIXTEENBIT, + Format::Data16Channel32 => vals::Chlen::THIRTYTWOBIT, + Format::Data24Channel32 => vals::Chlen::THIRTYTWOBIT, + Format::Data32Channel32 => vals::Chlen::THIRTYTWOBIT, + } + } +} + +#[derive(Copy, Clone)] +pub enum ClockPolarity { + IdleLow, + IdleHigh, +} + +impl ClockPolarity { + #[cfg(any(spi_v1, spi_f1))] + pub const fn ckpol(&self) -> vals::Ckpol { + match self { + ClockPolarity::IdleHigh => vals::Ckpol::IDLEHIGH, + ClockPolarity::IdleLow => vals::Ckpol::IDLELOW, + } + } +} + +/// [`I2S`] configuration. +/// +/// - `MS`: `Master` or `Slave` +/// - `TR`: `Transmit` or `Receive` +/// - `STD`: I2S standard, eg `Philips` +/// - `FMT`: Frame Format marker, eg `Data16Channel16` +#[non_exhaustive] +#[derive(Copy, Clone)] +pub struct Config { + pub mode: Mode, + pub function: Function, + pub standard: Standard, + pub format: Format, + pub clock_polarity: ClockPolarity, + pub master_clock: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + mode: Mode::Master, + function: Function::Transmit, + standard: Standard::Philips, + format: Format::Data16Channel16, + clock_polarity: ClockPolarity::IdleLow, + master_clock: true, + } + } +} + +pub struct I2S<'d, T: Instance, Tx, Rx> { + _peri: Spi<'d, T, Tx, Rx>, + sd: Option>, + ws: Option>, + ck: Option>, + mck: Option>, +} + +impl<'d, T: Instance, Tx, Rx> I2S<'d, T, Tx, Rx> { + /// Note: Full-Duplex modes are not supported at this time + pub fn new( + peri: impl Peripheral

+ 'd, + sd: impl Peripheral

> + 'd, + ws: impl Peripheral

> + 'd, + ck: impl Peripheral

> + 'd, + mck: impl Peripheral

> + 'd, + txdma: impl Peripheral

+ 'd, + rxdma: impl Peripheral

+ 'd, + freq: Hertz, + config: Config, + ) -> Self { + into_ref!(sd, ws, ck, mck); + + sd.set_as_af(sd.af_num(), AFType::OutputPushPull); + sd.set_speed(crate::gpio::Speed::VeryHigh); + + ws.set_as_af(ws.af_num(), AFType::OutputPushPull); + ws.set_speed(crate::gpio::Speed::VeryHigh); + + ck.set_as_af(ck.af_num(), AFType::OutputPushPull); + ck.set_speed(crate::gpio::Speed::VeryHigh); + + mck.set_as_af(mck.af_num(), AFType::OutputPushPull); + mck.set_speed(crate::gpio::Speed::VeryHigh); + + let spi = Spi::new_internal(peri, txdma, rxdma, freq, SpiConfig::default()); + + #[cfg(all(rcc_f4, not(stm32f410)))] + let pclk = unsafe { get_freqs() }.plli2s.unwrap(); + + #[cfg(stm32f410)] + let pclk = T::frequency(); + + let (odd, div) = compute_baud_rate(pclk, freq, config.master_clock, config.format); + + #[cfg(any(spi_v1, spi_f1))] + { + use stm32_metapac::spi::vals::{I2scfg, Odd}; + + // 1. Select the I2SDIV[7:0] bits in the SPI_I2SPR register to define the serial clock baud + // rate to reach the proper audio sample frequency. The ODD bit in the SPI_I2SPR + // register also has to be defined. + + T::REGS.i2spr().modify(|w| { + w.set_i2sdiv(div); + w.set_odd(match odd { + true => Odd::ODD, + false => Odd::EVEN, + }); + + w.set_mckoe(config.master_clock); + }); + + // 2. Select the CKPOL bit to define the steady level for the communication clock. Set the + // MCKOE bit in the SPI_I2SPR register if the master clock MCK needs to be provided to + // the external DAC/ADC audio component (the I2SDIV and ODD values should be + // computed depending on the state of the MCK output, for more details refer to + // Section 28.4.4: Clock generator). + + // 3. Set the I2SMOD bit in SPI_I2SCFGR to activate the I2S functionalities and choose the + // I2S standard through the I2SSTD[1:0] and PCMSYNC bits, the data length through the + // DATLEN[1:0] bits and the number of bits per channel by configuring the CHLEN bit. + // Select also the I2S master mode and direction (Transmitter or Receiver) through the + // I2SCFG[1:0] bits in the SPI_I2SCFGR register. + + // 4. If needed, select all the potential interruption sources and the DMA capabilities by + // writing the SPI_CR2 register. + + // 5. The I2SE bit in SPI_I2SCFGR register must be set. + + T::REGS.i2scfgr().modify(|w| { + w.set_ckpol(config.clock_polarity.ckpol()); + + w.set_i2smod(true); + w.set_i2sstd(config.standard.i2sstd()); + w.set_pcmsync(config.standard.pcmsync()); + + w.set_datlen(config.format.datlen()); + w.set_chlen(config.format.chlen()); + + w.set_i2scfg(match (config.mode, config.function) { + (Mode::Master, Function::Transmit) => I2scfg::MASTERTX, + (Mode::Master, Function::Receive) => I2scfg::MASTERRX, + (Mode::Slave, Function::Transmit) => I2scfg::SLAVETX, + (Mode::Slave, Function::Receive) => I2scfg::SLAVERX, + }); + + w.set_i2se(true) + }); + } + + Self { + _peri: spi, + sd: Some(sd.map_into()), + ws: Some(ws.map_into()), + ck: Some(ck.map_into()), + mck: Some(mck.map_into()), + } + } + + pub async fn write(&mut self, data: &[W]) -> Result<(), Error> + where + Tx: TxDma, + { + self._peri.write(data).await + } + + pub async fn read(&mut self, data: &mut [W]) -> Result<(), Error> + where + Tx: TxDma, + Rx: RxDma, + { + self._peri.read(data).await + } +} + +impl<'d, T: Instance, Tx, Rx> Drop for I2S<'d, T, Tx, Rx> { + fn drop(&mut self) { + self.sd.as_ref().map(|x| x.set_as_disconnected()); + self.ws.as_ref().map(|x| x.set_as_disconnected()); + self.ck.as_ref().map(|x| x.set_as_disconnected()); + self.mck.as_ref().map(|x| x.set_as_disconnected()); + } +} + +// Note, calculation details: +// Fs = i2s_clock / [256 * ((2 * div) + odd)] when master clock is enabled +// Fs = i2s_clock / [(channel_length * 2) * ((2 * div) + odd)]` when master clock is disabled +// channel_length is 16 or 32 +// +// can be rewritten as +// Fs = i2s_clock / (coef * division) +// where coef is a constant equal to 256, 64 or 32 depending channel length and master clock +// and where division = (2 * div) + odd +// +// Equation can be rewritten as +// division = i2s_clock/ (coef * Fs) +// +// note: division = (2 * div) + odd = (div << 1) + odd +// in other word, from bits point of view, division[8:1] = div[7:0] and division[0] = odd +fn compute_baud_rate(i2s_clock: Hertz, request_freq: Hertz, mclk: bool, data_format: Format) -> (bool, u8) { + let coef = if mclk { + 256 + } else if let Format::Data16Channel16 = data_format { + 32 + } else { + 64 + }; + + let (n, d) = (i2s_clock.0, coef * request_freq.0); + let division = (n + (d >> 1)) / d; + + if division < 4 { + (false, 2) + } else if division > 511 { + (true, 255) + } else { + ((division & 1) == 1, (division >> 1) as u8) + } +} diff --git a/embassy-stm32/src/interrupt.rs b/embassy-stm32/src/interrupt.rs deleted file mode 100644 index b66e4c7ef..000000000 --- a/embassy-stm32/src/interrupt.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub use critical_section::{CriticalSection, Mutex}; -pub use embassy_cortex_m::interrupt::*; - -pub use crate::_generated::interrupt::*; diff --git a/embassy-stm32/src/ipcc.rs b/embassy-stm32/src/ipcc.rs new file mode 100644 index 000000000..a24cba9f0 --- /dev/null +++ b/embassy-stm32/src/ipcc.rs @@ -0,0 +1,334 @@ +use core::future::poll_fn; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; + +use self::sealed::Instance; +use crate::interrupt; +use crate::interrupt::typelevel::Interrupt; +use crate::peripherals::IPCC; +use crate::rcc::sealed::RccPeripheral; + +/// Interrupt handler. +pub struct ReceiveInterruptHandler {} + +impl interrupt::typelevel::Handler for ReceiveInterruptHandler { + unsafe fn on_interrupt() { + let regs = IPCC::regs(); + + let channels = [ + IpccChannel::Channel1, + IpccChannel::Channel2, + IpccChannel::Channel3, + IpccChannel::Channel4, + IpccChannel::Channel5, + IpccChannel::Channel6, + ]; + + // Status register gives channel occupied status. For rx, use cpu1. + let sr = regs.cpu(1).sr().read(); + regs.cpu(0).mr().modify(|w| { + for channel in channels { + if sr.chf(channel as usize) { + // If bit is set to 1 then interrupt is disabled; we want to disable the interrupt + w.set_chom(channel as usize, true); + + // There shouldn't be a race because the channel is masked only if the interrupt has fired + IPCC::state().rx_waker_for(channel).wake(); + } + } + }) + } +} + +pub struct TransmitInterruptHandler {} + +impl interrupt::typelevel::Handler for TransmitInterruptHandler { + unsafe fn on_interrupt() { + let regs = IPCC::regs(); + + let channels = [ + IpccChannel::Channel1, + IpccChannel::Channel2, + IpccChannel::Channel3, + IpccChannel::Channel4, + IpccChannel::Channel5, + IpccChannel::Channel6, + ]; + + // Status register gives channel occupied status. For tx, use cpu0. + let sr = regs.cpu(0).sr().read(); + regs.cpu(0).mr().modify(|w| { + for channel in channels { + if !sr.chf(channel as usize) { + // If bit is set to 1 then interrupt is disabled; we want to disable the interrupt + w.set_chfm(channel as usize, true); + + // There shouldn't be a race because the channel is masked only if the interrupt has fired + IPCC::state().tx_waker_for(channel).wake(); + } + } + }); + } +} + +#[non_exhaustive] +#[derive(Clone, Copy, Default)] +pub struct Config { + // TODO: add IPCC peripheral configuration, if any, here + // reserved for future use +} + +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub enum IpccChannel { + Channel1 = 0, + Channel2 = 1, + Channel3 = 2, + Channel4 = 3, + Channel5 = 4, + Channel6 = 5, +} + +pub struct Ipcc; + +impl Ipcc { + pub fn enable(_config: Config) { + IPCC::enable(); + IPCC::reset(); + IPCC::set_cpu2(true); + + _configure_pwr(); + + let regs = IPCC::regs(); + + regs.cpu(0).cr().modify(|w| { + w.set_rxoie(true); + w.set_txfie(true); + }); + + // enable interrupts + crate::interrupt::typelevel::IPCC_C1_RX::unpend(); + crate::interrupt::typelevel::IPCC_C1_TX::unpend(); + + unsafe { crate::interrupt::typelevel::IPCC_C1_RX::enable() }; + unsafe { crate::interrupt::typelevel::IPCC_C1_TX::enable() }; + } + + /// Send data to an IPCC channel. The closure is called to write the data when appropriate. + pub async fn send(channel: IpccChannel, f: impl FnOnce()) { + let regs = IPCC::regs(); + + Self::flush(channel).await; + + f(); + + compiler_fence(Ordering::SeqCst); + + trace!("ipcc: ch {}: send data", channel as u8); + regs.cpu(0).scr().write(|w| w.set_chs(channel as usize, true)); + } + + /// Wait for the tx channel to become clear + pub async fn flush(channel: IpccChannel) { + let regs = IPCC::regs(); + + // This is a race, but is nice for debugging + if regs.cpu(0).sr().read().chf(channel as usize) { + trace!("ipcc: ch {}: wait for tx free", channel as u8); + } + + poll_fn(|cx| { + IPCC::state().tx_waker_for(channel).register(cx.waker()); + // If bit is set to 1 then interrupt is disabled; we want to enable the interrupt + regs.cpu(0).mr().modify(|w| w.set_chfm(channel as usize, false)); + + compiler_fence(Ordering::SeqCst); + + if !regs.cpu(0).sr().read().chf(channel as usize) { + // If bit is set to 1 then interrupt is disabled; we want to disable the interrupt + regs.cpu(0).mr().modify(|w| w.set_chfm(channel as usize, true)); + + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + } + + /// Receive data from an IPCC channel. The closure is called to read the data when appropriate. + pub async fn receive(channel: IpccChannel, mut f: impl FnMut() -> Option) -> R { + let regs = IPCC::regs(); + + loop { + // This is a race, but is nice for debugging + if !regs.cpu(1).sr().read().chf(channel as usize) { + trace!("ipcc: ch {}: wait for rx occupied", channel as u8); + } + + poll_fn(|cx| { + IPCC::state().rx_waker_for(channel).register(cx.waker()); + // If bit is set to 1 then interrupt is disabled; we want to enable the interrupt + regs.cpu(0).mr().modify(|w| w.set_chom(channel as usize, false)); + + compiler_fence(Ordering::SeqCst); + + if regs.cpu(1).sr().read().chf(channel as usize) { + // If bit is set to 1 then interrupt is disabled; we want to disable the interrupt + regs.cpu(0).mr().modify(|w| w.set_chfm(channel as usize, true)); + + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + + trace!("ipcc: ch {}: read data", channel as u8); + + match f() { + Some(ret) => return ret, + None => {} + } + + trace!("ipcc: ch {}: clear rx", channel as u8); + compiler_fence(Ordering::SeqCst); + // If the channel is clear and the read function returns none, fetch more data + regs.cpu(0).scr().write(|w| w.set_chc(channel as usize, true)); + } + } +} + +impl sealed::Instance for crate::peripherals::IPCC { + fn regs() -> crate::pac::ipcc::Ipcc { + crate::pac::IPCC + } + + fn set_cpu2(enabled: bool) { + crate::pac::PWR.cr4().modify(|w| w.set_c2boot(enabled)); + } + + fn state() -> &'static self::sealed::State { + static STATE: self::sealed::State = self::sealed::State::new(); + &STATE + } +} + +pub(crate) mod sealed { + use embassy_sync::waitqueue::AtomicWaker; + + use super::*; + + pub struct State { + rx_wakers: [AtomicWaker; 6], + tx_wakers: [AtomicWaker; 6], + } + + impl State { + pub const fn new() -> Self { + const WAKER: AtomicWaker = AtomicWaker::new(); + + Self { + rx_wakers: [WAKER; 6], + tx_wakers: [WAKER; 6], + } + } + + pub const fn rx_waker_for(&self, channel: IpccChannel) -> &AtomicWaker { + match channel { + IpccChannel::Channel1 => &self.rx_wakers[0], + IpccChannel::Channel2 => &self.rx_wakers[1], + IpccChannel::Channel3 => &self.rx_wakers[2], + IpccChannel::Channel4 => &self.rx_wakers[3], + IpccChannel::Channel5 => &self.rx_wakers[4], + IpccChannel::Channel6 => &self.rx_wakers[5], + } + } + + pub const fn tx_waker_for(&self, channel: IpccChannel) -> &AtomicWaker { + match channel { + IpccChannel::Channel1 => &self.tx_wakers[0], + IpccChannel::Channel2 => &self.tx_wakers[1], + IpccChannel::Channel3 => &self.tx_wakers[2], + IpccChannel::Channel4 => &self.tx_wakers[3], + IpccChannel::Channel5 => &self.tx_wakers[4], + IpccChannel::Channel6 => &self.tx_wakers[5], + } + } + } + + pub trait Instance: crate::rcc::RccPeripheral { + fn regs() -> crate::pac::ipcc::Ipcc; + fn set_cpu2(enabled: bool); + fn state() -> &'static State; + } +} + +fn _configure_pwr() { + // TODO: move this to RCC + + let pwr = crate::pac::PWR; + let rcc = crate::pac::RCC; + + rcc.cfgr().modify(|w| w.set_stopwuck(true)); + + pwr.cr1().modify(|w| w.set_dbp(true)); + pwr.cr1().modify(|w| w.set_dbp(true)); + + // configure LSE + rcc.bdcr().modify(|w| w.set_lseon(true)); + + // select system clock source = PLL + // set PLL coefficients + // m: 2, + // n: 12, + // r: 3, + // q: 4, + // p: 3, + let src_bits = 0b11; + let pllp = (3 - 1) & 0b11111; + let pllq = (4 - 1) & 0b111; + let pllr = (3 - 1) & 0b111; + let plln = 12 & 0b1111111; + let pllm = (2 - 1) & 0b111; + rcc.pllcfgr().modify(|w| { + w.set_pllsrc(src_bits); + w.set_pllm(pllm); + w.set_plln(plln); + w.set_pllr(pllr); + w.set_pllp(pllp); + w.set_pllpen(true); + w.set_pllq(pllq); + w.set_pllqen(true); + }); + // enable PLL + rcc.cr().modify(|w| w.set_pllon(true)); + rcc.cr().write(|w| w.set_hsion(false)); + // while !rcc.cr().read().pllrdy() {} + + // configure SYSCLK mux to use PLL clocl + rcc.cfgr().modify(|w| w.set_sw(0b11)); + + // configure CPU1 & CPU2 dividers + rcc.cfgr().modify(|w| w.set_hpre(0)); // not divided + rcc.extcfgr().modify(|w| { + w.set_c2hpre(0b1000); // div2 + w.set_shdhpre(0); // not divided + }); + + // apply APB1 / APB2 values + rcc.cfgr().modify(|w| { + w.set_ppre1(0b000); // not divided + w.set_ppre2(0b000); // not divided + }); + + // TODO: required + // set RF wake-up clock = LSE + rcc.csr().modify(|w| w.set_rfwkpsel(0b01)); + + // set LPTIM1 & LPTIM2 clock source + rcc.ccipr().modify(|w| { + w.set_lptim1sel(0b00); // PCLK + w.set_lptim2sel(0b00); // PCLK + }); +} diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index 30ff02d56..45a7b5476 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -1,12 +1,11 @@ -#![no_std] -#![cfg_attr(feature = "nightly", feature(generic_associated_types, type_alias_impl_trait))] +#![cfg_attr(not(test), no_std)] +#![cfg_attr(feature = "nightly", feature(async_fn_in_trait, impl_trait_projections))] // This must go FIRST so that all the other modules see its macros. pub mod fmt; include!(concat!(env!("OUT_DIR"), "/_macros.rs")); // Utilities -pub mod interrupt; pub mod time; mod traits; @@ -39,13 +38,18 @@ pub mod i2c; #[cfg(crc)] pub mod crc; -#[cfg(any( - flash_l0, flash_l1, flash_wl, flash_wb, flash_l4, flash_f3, flash_f4, flash_f7, flash_h7 -))] pub mod flash; +#[cfg(all(spi_v1, rcc_f4))] +pub mod i2s; +#[cfg(stm32wb)] +pub mod ipcc; pub mod pwm; +#[cfg(quadspi)] +pub mod qspi; #[cfg(rng)] pub mod rng; +#[cfg(all(rtc, not(rtc_v1)))] +pub mod rtc; #[cfg(sdmmc)] pub mod sdmmc; #[cfg(spi)] @@ -54,15 +58,11 @@ pub mod spi; pub mod usart; #[cfg(usb)] pub mod usb; -#[cfg(any(otgfs, otghs))] +#[cfg(otg)] pub mod usb_otg; - #[cfg(iwdg)] pub mod wdg; -#[cfg(feature = "subghz")] -pub mod subghz; - // This must go last, so that it sees all the impl_foo! macros defined earlier. pub(crate) mod _generated { #![allow(dead_code)] @@ -72,21 +72,58 @@ pub(crate) mod _generated { include!(concat!(env!("OUT_DIR"), "/_generated.rs")); } +pub use crate::_generated::interrupt; + +/// Macro to bind interrupts to handlers. +/// +/// This defines the right interrupt handlers, and creates a unit struct (like `struct Irqs;`) +/// and implements the right [`Binding`]s for it. You can pass this struct to drivers to +/// prove at compile-time that the right interrupts have been bound. +// developer note: this macro can't be in `embassy-hal-common` due to the use of `$crate`. +#[macro_export] +macro_rules! bind_interrupts { + ($vis:vis struct $name:ident { $($irq:ident => $($handler:ty),*;)* }) => { + $vis struct $name; + + $( + #[allow(non_snake_case)] + #[no_mangle] + unsafe extern "C" fn $irq() { + $( + <$handler as $crate::interrupt::typelevel::Handler<$crate::interrupt::typelevel::$irq>>::on_interrupt(); + )* + } + + $( + unsafe impl $crate::interrupt::typelevel::Binding<$crate::interrupt::typelevel::$irq, $handler> for $name {} + )* + )* + }; +} + // Reexports pub use _generated::{peripherals, Peripherals}; -pub use embassy_cortex_m::executor; -pub use embassy_cortex_m::interrupt::_export::interrupt; pub use embassy_hal_common::{into_ref, Peripheral, PeripheralRef}; #[cfg(feature = "unstable-pac")] pub use stm32_metapac as pac; #[cfg(not(feature = "unstable-pac"))] pub(crate) use stm32_metapac as pac; +use crate::interrupt::Priority; +#[cfg(feature = "rt")] +pub use crate::pac::NVIC_PRIO_BITS; + #[non_exhaustive] pub struct Config { pub rcc: rcc::Config, #[cfg(dbgmcu)] pub enable_debug_during_sleep: bool, + #[cfg(bdma)] + pub bdma_interrupt_priority: Priority, + #[cfg(dma)] + pub dma_interrupt_priority: Priority, + #[cfg(gpdma)] + pub gpdma_interrupt_priority: Priority, } impl Default for Config { @@ -95,6 +132,12 @@ impl Default for Config { rcc: Default::default(), #[cfg(dbgmcu)] enable_debug_during_sleep: true, + #[cfg(bdma)] + bdma_interrupt_priority: Priority::P0, + #[cfg(dma)] + dma_interrupt_priority: Priority::P0, + #[cfg(gpdma)] + gpdma_interrupt_priority: Priority::P0, } } } @@ -103,37 +146,44 @@ impl Default for Config { pub fn init(config: Config) -> Peripherals { let p = Peripherals::take(); - unsafe { - #[cfg(dbgmcu)] - if config.enable_debug_during_sleep { - crate::pac::DBGMCU.cr().modify(|cr| { - #[cfg(any(dbgmcu_f0, dbgmcu_g0, dbgmcu_u5))] - { - cr.set_dbg_stop(true); - cr.set_dbg_standby(true); - } - #[cfg(any( - dbgmcu_f1, dbgmcu_f2, dbgmcu_f3, dbgmcu_f4, dbgmcu_f7, dbgmcu_g4, dbgmcu_f7, dbgmcu_l0, dbgmcu_l1, - dbgmcu_l4, dbgmcu_wb, dbgmcu_wl - ))] - { - cr.set_dbg_sleep(true); - cr.set_dbg_stop(true); - cr.set_dbg_standby(true); - } - #[cfg(dbgmcu_h7)] - { - cr.set_d1dbgcken(true); - cr.set_d3dbgcken(true); - cr.set_dbgsleep_d1(true); - cr.set_dbgstby_d1(true); - cr.set_dbgstop_d1(true); - } - }); - } + #[cfg(dbgmcu)] + if config.enable_debug_during_sleep { + crate::pac::DBGMCU.cr().modify(|cr| { + #[cfg(any(dbgmcu_f0, dbgmcu_c0, dbgmcu_g0, dbgmcu_u5))] + { + cr.set_dbg_stop(true); + cr.set_dbg_standby(true); + } + #[cfg(any( + dbgmcu_f1, dbgmcu_f2, dbgmcu_f3, dbgmcu_f4, dbgmcu_f7, dbgmcu_g4, dbgmcu_f7, dbgmcu_l0, dbgmcu_l1, + dbgmcu_l4, dbgmcu_wb, dbgmcu_wl + ))] + { + cr.set_dbg_sleep(true); + cr.set_dbg_stop(true); + cr.set_dbg_standby(true); + } + #[cfg(dbgmcu_h7)] + { + cr.set_d1dbgcken(true); + cr.set_d3dbgcken(true); + cr.set_dbgsleep_d1(true); + cr.set_dbgstby_d1(true); + cr.set_dbgstop_d1(true); + } + }); + } + unsafe { gpio::init(); - dma::init(); + dma::init( + #[cfg(bdma)] + config.bdma_interrupt_priority, + #[cfg(dma)] + config.dma_interrupt_priority, + #[cfg(gpdma)] + config.gpdma_interrupt_priority, + ); #[cfg(feature = "exti")] exti::init(); diff --git a/embassy-stm32/src/pwm/complementary_pwm.rs b/embassy-stm32/src/pwm/complementary_pwm.rs new file mode 100644 index 000000000..4d64d005c --- /dev/null +++ b/embassy-stm32/src/pwm/complementary_pwm.rs @@ -0,0 +1,250 @@ +use core::marker::PhantomData; + +use embassy_hal_common::{into_ref, PeripheralRef}; +use stm32_metapac::timer::vals::Ckd; + +use super::simple_pwm::*; +use super::*; +#[allow(unused_imports)] +use crate::gpio::sealed::{AFType, Pin}; +use crate::gpio::AnyPin; +use crate::time::Hertz; +use crate::Peripheral; + +pub struct ComplementaryPwmPin<'d, Perip, Channel> { + _pin: PeripheralRef<'d, AnyPin>, + phantom: PhantomData<(Perip, Channel)>, +} + +macro_rules! complementary_channel_impl { + ($new_chx:ident, $channel:ident, $pin_trait:ident, $complementary_pin_trait:ident) => { + impl<'d, Perip: CaptureCompare16bitInstance> ComplementaryPwmPin<'d, Perip, $channel> { + pub fn $new_chx(pin: impl Peripheral

> + 'd) -> Self { + into_ref!(pin); + critical_section::with(|_| { + pin.set_low(); + pin.set_as_af(pin.af_num(), AFType::OutputPushPull); + #[cfg(gpio_v2)] + pin.set_speed(crate::gpio::Speed::VeryHigh); + }); + ComplementaryPwmPin { + _pin: pin.map_into(), + phantom: PhantomData, + } + } + } + }; +} + +complementary_channel_impl!(new_ch1, Ch1, Channel1Pin, Channel1ComplementaryPin); +complementary_channel_impl!(new_ch2, Ch2, Channel2Pin, Channel2ComplementaryPin); +complementary_channel_impl!(new_ch3, Ch3, Channel3Pin, Channel3ComplementaryPin); +complementary_channel_impl!(new_ch4, Ch4, Channel4Pin, Channel4ComplementaryPin); + +pub struct ComplementaryPwm<'d, T> { + inner: PeripheralRef<'d, T>, +} + +impl<'d, T: ComplementaryCaptureCompare16bitInstance> ComplementaryPwm<'d, T> { + pub fn new( + tim: impl Peripheral

+ 'd, + _ch1: Option>, + _ch1n: Option>, + _ch2: Option>, + _ch2n: Option>, + _ch3: Option>, + _ch3n: Option>, + _ch4: Option>, + _ch4n: Option>, + freq: Hertz, + ) -> Self { + Self::new_inner(tim, freq) + } + + fn new_inner(tim: impl Peripheral

+ 'd, freq: Hertz) -> Self { + into_ref!(tim); + + T::enable(); + ::reset(); + + let mut this = Self { inner: tim }; + + this.inner.set_frequency(freq); + this.inner.start(); + + this.inner.enable_outputs(true); + + this.inner + .set_output_compare_mode(Channel::Ch1, OutputCompareMode::PwmMode1); + this.inner + .set_output_compare_mode(Channel::Ch2, OutputCompareMode::PwmMode1); + this.inner + .set_output_compare_mode(Channel::Ch3, OutputCompareMode::PwmMode1); + this.inner + .set_output_compare_mode(Channel::Ch4, OutputCompareMode::PwmMode1); + this + } + + pub fn enable(&mut self, channel: Channel) { + self.inner.enable_channel(channel, true); + self.inner.enable_complementary_channel(channel, true); + } + + pub fn disable(&mut self, channel: Channel) { + self.inner.enable_complementary_channel(channel, false); + self.inner.enable_channel(channel, false); + } + + pub fn set_freq(&mut self, freq: Hertz) { + self.inner.set_frequency(freq); + } + + pub fn get_max_duty(&self) -> u16 { + self.inner.get_max_compare_value() + } + + pub fn set_duty(&mut self, channel: Channel, duty: u16) { + assert!(duty < self.get_max_duty()); + self.inner.set_compare_value(channel, duty) + } + + /// Set the dead time as a proportion of max_duty + pub fn set_dead_time(&mut self, value: u16) { + let (ckd, value) = compute_dead_time_value(value); + + self.inner.set_dead_time_clock_division(ckd); + self.inner.set_dead_time_value(value); + } +} + +fn compute_dead_time_value(value: u16) -> (Ckd, u8) { + /* + Dead-time = T_clk * T_dts * T_dtg + + T_dts: + This bit-field indicates the division ratio between the timer clock (CK_INT) frequency and the + dead-time and sampling clock (tDTS)used by the dead-time generators and the digital filters + (ETR, TIx), + 00: tDTS=tCK_INT + 01: tDTS=2*tCK_INT + 10: tDTS=4*tCK_INT + + T_dtg: + This bit-field defines the duration of the dead-time inserted between the complementary + outputs. DT correspond to this duration. + DTG[7:5]=0xx => DT=DTG[7:0]x tdtg with tdtg=tDTS. + DTG[7:5]=10x => DT=(64+DTG[5:0])xtdtg with Tdtg=2xtDTS. + DTG[7:5]=110 => DT=(32+DTG[4:0])xtdtg with Tdtg=8xtDTS. + DTG[7:5]=111 => DT=(32+DTG[4:0])xtdtg with Tdtg=16xtDTS. + Example if TDTS=125ns (8MHz), dead-time possible values are: + 0 to 15875 ns by 125 ns steps, + 16 us to 31750 ns by 250 ns steps, + 32 us to 63us by 1 us steps, + 64 us to 126 us by 2 us steps + */ + + let mut error = u16::MAX; + let mut ckd = Ckd::DIV1; + let mut bits = 0u8; + + for this_ckd in [Ckd::DIV1, Ckd::DIV2, Ckd::DIV4] { + let outdiv = match this_ckd { + Ckd::DIV1 => 1, + Ckd::DIV2 => 2, + Ckd::DIV4 => 4, + _ => unreachable!(), + }; + + // 127 + // 128 + // .. + // 254 + // 256 + // .. + // 504 + // 512 + // .. + // 1008 + + let target = value / outdiv; + let (these_bits, result) = if target < 128 { + (target as u8, target) + } else if target < 255 { + (64 + (target / 2) as u8, (target - target % 2)) + } else if target < 508 { + (32 + (target / 8) as u8, (target - target % 8)) + } else if target < 1008 { + (32 + (target / 16) as u8, (target - target % 16)) + } else { + (u8::MAX, 1008) + }; + + let this_error = value.abs_diff(result * outdiv); + if error > this_error { + ckd = this_ckd; + bits = these_bits; + error = this_error; + } + + match error { + 0 => break, + _ => {} + } + } + + (ckd, bits) +} + +#[cfg(test)] +mod tests { + use super::{compute_dead_time_value, Ckd}; + + #[test] + fn test_compute_dead_time_value() { + struct TestRun { + value: u16, + ckd: Ckd, + bits: u8, + } + + let fn_results = [ + TestRun { + value: 1, + ckd: Ckd::DIV1, + bits: 1, + }, + TestRun { + value: 125, + ckd: Ckd::DIV1, + bits: 125, + }, + TestRun { + value: 245, + ckd: Ckd::DIV1, + bits: 64 + 245 / 2, + }, + TestRun { + value: 255, + ckd: Ckd::DIV2, + bits: 127, + }, + TestRun { + value: 400, + ckd: Ckd::DIV1, + bits: 32 + (400u16 / 8) as u8, + }, + TestRun { + value: 600, + ckd: Ckd::DIV4, + bits: 64 + (600u16 / 8) as u8, + }, + ]; + + for test_run in fn_results { + let (ckd, bits) = compute_dead_time_value(test_run.value); + + assert_eq!(ckd.to_bits(), test_run.ckd.to_bits()); + assert_eq!(bits, test_run.bits); + } + } +} diff --git a/embassy-stm32/src/pwm/mod.rs b/embassy-stm32/src/pwm/mod.rs index d3713391c..5aba2663e 100644 --- a/embassy-stm32/src/pwm/mod.rs +++ b/embassy-stm32/src/pwm/mod.rs @@ -1,5 +1,8 @@ +pub mod complementary_pwm; pub mod simple_pwm; +use stm32_metapac::timer::vals::Ckd; + #[cfg(feature = "unstable-pac")] pub mod low_level { pub use super::sealed::*; @@ -56,25 +59,33 @@ pub(crate) mod sealed { pub trait CaptureCompare16bitInstance: crate::timer::sealed::GeneralPurpose16bitInstance { /// Global output enable. Does not do anything on non-advanced timers. - unsafe fn enable_outputs(&mut self, enable: bool); + fn enable_outputs(&mut self, enable: bool); - unsafe fn set_output_compare_mode(&mut self, channel: Channel, mode: OutputCompareMode); + fn set_output_compare_mode(&mut self, channel: Channel, mode: OutputCompareMode); - unsafe fn enable_channel(&mut self, channel: Channel, enable: bool); + fn enable_channel(&mut self, channel: Channel, enable: bool); - unsafe fn set_compare_value(&mut self, channel: Channel, value: u16); + fn set_compare_value(&mut self, channel: Channel, value: u16); - unsafe fn get_max_compare_value(&self) -> u16; + fn get_max_compare_value(&self) -> u16; + } + + pub trait ComplementaryCaptureCompare16bitInstance: CaptureCompare16bitInstance { + fn set_dead_time_clock_division(&mut self, value: Ckd); + + fn set_dead_time_value(&mut self, value: u8); + + fn enable_complementary_channel(&mut self, channel: Channel, enable: bool); } pub trait CaptureCompare32bitInstance: crate::timer::sealed::GeneralPurpose32bitInstance { - unsafe fn set_output_compare_mode(&mut self, channel: Channel, mode: OutputCompareMode); + fn set_output_compare_mode(&mut self, channel: Channel, mode: OutputCompareMode); - unsafe fn enable_channel(&mut self, channel: Channel, enable: bool); + fn enable_channel(&mut self, channel: Channel, enable: bool); - unsafe fn set_compare_value(&mut self, channel: Channel, value: u32); + fn set_compare_value(&mut self, channel: Channel, value: u32); - unsafe fn get_max_compare_value(&self) -> u32; + fn get_max_compare_value(&self) -> u32; } } @@ -82,6 +93,12 @@ pub trait CaptureCompare16bitInstance: sealed::CaptureCompare16bitInstance + crate::timer::GeneralPurpose16bitInstance + 'static { } + +pub trait ComplementaryCaptureCompare16bitInstance: + sealed::ComplementaryCaptureCompare16bitInstance + crate::timer::AdvancedControlInstance + 'static +{ +} + pub trait CaptureCompare32bitInstance: sealed::CaptureCompare32bitInstance + CaptureCompare16bitInstance + crate::timer::GeneralPurpose32bitInstance + 'static { @@ -91,9 +108,9 @@ pub trait CaptureCompare32bitInstance: macro_rules! impl_compare_capable_16bit { ($inst:ident) => { impl crate::pwm::sealed::CaptureCompare16bitInstance for crate::peripherals::$inst { - unsafe fn enable_outputs(&mut self, _enable: bool) {} + fn enable_outputs(&mut self, _enable: bool) {} - unsafe fn set_output_compare_mode(&mut self, channel: crate::pwm::Channel, mode: OutputCompareMode) { + fn set_output_compare_mode(&mut self, channel: crate::pwm::Channel, mode: OutputCompareMode) { use crate::timer::sealed::GeneralPurpose16bitInstance; let r = Self::regs_gp16(); let raw_channel: usize = channel.raw(); @@ -101,19 +118,19 @@ macro_rules! impl_compare_capable_16bit { .modify(|w| w.set_ocm(raw_channel % 2, mode.into())); } - unsafe fn enable_channel(&mut self, channel: Channel, enable: bool) { + fn enable_channel(&mut self, channel: Channel, enable: bool) { use crate::timer::sealed::GeneralPurpose16bitInstance; Self::regs_gp16() .ccer() .modify(|w| w.set_cce(channel.raw(), enable)); } - unsafe fn set_compare_value(&mut self, channel: Channel, value: u16) { + fn set_compare_value(&mut self, channel: Channel, value: u16) { use crate::timer::sealed::GeneralPurpose16bitInstance; Self::regs_gp16().ccr(channel.raw()).modify(|w| w.set_ccr(value)); } - unsafe fn get_max_compare_value(&self) -> u16 { + fn get_max_compare_value(&self) -> u16 { use crate::timer::sealed::GeneralPurpose16bitInstance; Self::regs_gp16().arr().read().arr() } @@ -133,7 +150,7 @@ foreach_interrupt! { ($inst:ident, timer, TIM_GP32, UP, $irq:ident) => { impl_compare_capable_16bit!($inst); impl crate::pwm::sealed::CaptureCompare32bitInstance for crate::peripherals::$inst { - unsafe fn set_output_compare_mode( + fn set_output_compare_mode( &mut self, channel: crate::pwm::Channel, mode: OutputCompareMode, @@ -143,17 +160,17 @@ foreach_interrupt! { Self::regs_gp32().ccmr_output(raw_channel / 2).modify(|w| w.set_ocm(raw_channel % 2, mode.into())); } - unsafe fn enable_channel(&mut self, channel: Channel, enable: bool) { + fn enable_channel(&mut self, channel: Channel, enable: bool) { use crate::timer::sealed::GeneralPurpose32bitInstance; Self::regs_gp32().ccer().modify(|w| w.set_cce(channel.raw(), enable)); } - unsafe fn set_compare_value(&mut self, channel: Channel, value: u32) { + fn set_compare_value(&mut self, channel: Channel, value: u32) { use crate::timer::sealed::GeneralPurpose32bitInstance; Self::regs_gp32().ccr(channel.raw()).modify(|w| w.set_ccr(value)); } - unsafe fn get_max_compare_value(&self) -> u32 { + fn get_max_compare_value(&self) -> u32 { use crate::timer::sealed::GeneralPurpose32bitInstance; Self::regs_gp32().arr().read().arr() as u32 } @@ -168,13 +185,13 @@ foreach_interrupt! { ($inst:ident, timer, TIM_ADV, UP, $irq:ident) => { impl crate::pwm::sealed::CaptureCompare16bitInstance for crate::peripherals::$inst { - unsafe fn enable_outputs(&mut self, enable: bool) { + fn enable_outputs(&mut self, enable: bool) { use crate::timer::sealed::AdvancedControlInstance; let r = Self::regs_advanced(); r.bdtr().modify(|w| w.set_moe(enable)); } - unsafe fn set_output_compare_mode( + fn set_output_compare_mode( &mut self, channel: crate::pwm::Channel, mode: OutputCompareMode, @@ -186,21 +203,21 @@ foreach_interrupt! { .modify(|w| w.set_ocm(raw_channel % 2, mode.into())); } - unsafe fn enable_channel(&mut self, channel: Channel, enable: bool) { + fn enable_channel(&mut self, channel: Channel, enable: bool) { use crate::timer::sealed::AdvancedControlInstance; Self::regs_advanced() .ccer() .modify(|w| w.set_cce(channel.raw(), enable)); } - unsafe fn set_compare_value(&mut self, channel: Channel, value: u16) { + fn set_compare_value(&mut self, channel: Channel, value: u16) { use crate::timer::sealed::AdvancedControlInstance; Self::regs_advanced() .ccr(channel.raw()) .modify(|w| w.set_ccr(value)); } - unsafe fn get_max_compare_value(&self) -> u16 { + fn get_max_compare_value(&self) -> u16 { use crate::timer::sealed::AdvancedControlInstance; Self::regs_advanced().arr().read().arr() } @@ -209,6 +226,29 @@ foreach_interrupt! { impl CaptureCompare16bitInstance for crate::peripherals::$inst { } + + impl crate::pwm::sealed::ComplementaryCaptureCompare16bitInstance for crate::peripherals::$inst { + fn set_dead_time_clock_division(&mut self, value: Ckd) { + use crate::timer::sealed::AdvancedControlInstance; + Self::regs_advanced().cr1().modify(|w| w.set_ckd(value)); + } + + fn set_dead_time_value(&mut self, value: u8) { + use crate::timer::sealed::AdvancedControlInstance; + Self::regs_advanced().bdtr().modify(|w| w.set_dtg(value)); + } + + fn enable_complementary_channel(&mut self, channel: Channel, enable: bool) { + use crate::timer::sealed::AdvancedControlInstance; + Self::regs_advanced() + .ccer() + .modify(|w| w.set_ccne(channel.raw(), enable)); + } + } + + impl ComplementaryCaptureCompare16bitInstance for crate::peripherals::$inst { + + } }; } diff --git a/embassy-stm32/src/pwm/simple_pwm.rs b/embassy-stm32/src/pwm/simple_pwm.rs index b045a2d78..995f59c23 100644 --- a/embassy-stm32/src/pwm/simple_pwm.rs +++ b/embassy-stm32/src/pwm/simple_pwm.rs @@ -24,7 +24,7 @@ macro_rules! channel_impl { impl<'d, Perip: CaptureCompare16bitInstance> PwmPin<'d, Perip, $channel> { pub fn $new_chx(pin: impl Peripheral

> + 'd) -> Self { into_ref!(pin); - critical_section::with(|_| unsafe { + critical_section::with(|_| { pin.set_low(); pin.set_as_af(pin.af_num(), AFType::OutputPushPull); #[cfg(gpio_v2)] @@ -71,31 +71,25 @@ impl<'d, T: CaptureCompare16bitInstance> SimplePwm<'d, T> { this.inner.set_frequency(freq); this.inner.start(); - unsafe { - this.inner.enable_outputs(true); + this.inner.enable_outputs(true); - this.inner - .set_output_compare_mode(Channel::Ch1, OutputCompareMode::PwmMode1); - this.inner - .set_output_compare_mode(Channel::Ch2, OutputCompareMode::PwmMode1); - this.inner - .set_output_compare_mode(Channel::Ch3, OutputCompareMode::PwmMode1); - this.inner - .set_output_compare_mode(Channel::Ch4, OutputCompareMode::PwmMode1); - } + this.inner + .set_output_compare_mode(Channel::Ch1, OutputCompareMode::PwmMode1); + this.inner + .set_output_compare_mode(Channel::Ch2, OutputCompareMode::PwmMode1); + this.inner + .set_output_compare_mode(Channel::Ch3, OutputCompareMode::PwmMode1); + this.inner + .set_output_compare_mode(Channel::Ch4, OutputCompareMode::PwmMode1); this } pub fn enable(&mut self, channel: Channel) { - unsafe { - self.inner.enable_channel(channel, true); - } + self.inner.enable_channel(channel, true); } pub fn disable(&mut self, channel: Channel) { - unsafe { - self.inner.enable_channel(channel, false); - } + self.inner.enable_channel(channel, false); } pub fn set_freq(&mut self, freq: Hertz) { @@ -103,11 +97,11 @@ impl<'d, T: CaptureCompare16bitInstance> SimplePwm<'d, T> { } pub fn get_max_duty(&self) -> u16 { - unsafe { self.inner.get_max_compare_value() } + self.inner.get_max_compare_value() } pub fn set_duty(&mut self, channel: Channel, duty: u16) { assert!(duty < self.get_max_duty()); - unsafe { self.inner.set_compare_value(channel, duty) } + self.inner.set_compare_value(channel, duty) } } diff --git a/embassy-stm32/src/qspi/enums.rs b/embassy-stm32/src/qspi/enums.rs new file mode 100644 index 000000000..2dbe2b061 --- /dev/null +++ b/embassy-stm32/src/qspi/enums.rs @@ -0,0 +1,294 @@ +#[allow(dead_code)] +#[derive(Copy, Clone)] +pub(crate) enum QspiMode { + IndirectWrite, + IndirectRead, + AutoPolling, + MemoryMapped, +} + +impl Into for QspiMode { + fn into(self) -> u8 { + match self { + QspiMode::IndirectWrite => 0b00, + QspiMode::IndirectRead => 0b01, + QspiMode::AutoPolling => 0b10, + QspiMode::MemoryMapped => 0b11, + } + } +} + +#[allow(dead_code)] +#[derive(Copy, Clone)] +pub enum QspiWidth { + NONE, + SING, + DUAL, + QUAD, +} + +impl Into for QspiWidth { + fn into(self) -> u8 { + match self { + QspiWidth::NONE => 0b00, + QspiWidth::SING => 0b01, + QspiWidth::DUAL => 0b10, + QspiWidth::QUAD => 0b11, + } + } +} + +#[derive(Copy, Clone)] +pub enum MemorySize { + _1KiB, + _2KiB, + _4KiB, + _8KiB, + _16KiB, + _32KiB, + _64KiB, + _128KiB, + _256KiB, + _512KiB, + _1MiB, + _2MiB, + _4MiB, + _8MiB, + _16MiB, + _32MiB, + _64MiB, + _128MiB, + _256MiB, + _512MiB, + _1GiB, + _2GiB, + _4GiB, + Other(u8), +} + +impl Into for MemorySize { + fn into(self) -> u8 { + match self { + MemorySize::_1KiB => 9, + MemorySize::_2KiB => 10, + MemorySize::_4KiB => 11, + MemorySize::_8KiB => 12, + MemorySize::_16KiB => 13, + MemorySize::_32KiB => 14, + MemorySize::_64KiB => 15, + MemorySize::_128KiB => 16, + MemorySize::_256KiB => 17, + MemorySize::_512KiB => 18, + MemorySize::_1MiB => 19, + MemorySize::_2MiB => 20, + MemorySize::_4MiB => 21, + MemorySize::_8MiB => 22, + MemorySize::_16MiB => 23, + MemorySize::_32MiB => 24, + MemorySize::_64MiB => 25, + MemorySize::_128MiB => 26, + MemorySize::_256MiB => 27, + MemorySize::_512MiB => 28, + MemorySize::_1GiB => 29, + MemorySize::_2GiB => 30, + MemorySize::_4GiB => 31, + MemorySize::Other(val) => val, + } + } +} + +#[derive(Copy, Clone)] +pub enum AddressSize { + _8Bit, + _16Bit, + _24bit, + _32bit, +} + +impl Into for AddressSize { + fn into(self) -> u8 { + match self { + AddressSize::_8Bit => 0b00, + AddressSize::_16Bit => 0b01, + AddressSize::_24bit => 0b10, + AddressSize::_32bit => 0b11, + } + } +} + +#[derive(Copy, Clone)] +pub enum ChipSelectHightTime { + _1Cycle, + _2Cycle, + _3Cycle, + _4Cycle, + _5Cycle, + _6Cycle, + _7Cycle, + _8Cycle, +} + +impl Into for ChipSelectHightTime { + fn into(self) -> u8 { + match self { + ChipSelectHightTime::_1Cycle => 0, + ChipSelectHightTime::_2Cycle => 1, + ChipSelectHightTime::_3Cycle => 2, + ChipSelectHightTime::_4Cycle => 3, + ChipSelectHightTime::_5Cycle => 4, + ChipSelectHightTime::_6Cycle => 5, + ChipSelectHightTime::_7Cycle => 6, + ChipSelectHightTime::_8Cycle => 7, + } + } +} + +#[derive(Copy, Clone)] +pub enum FIFOThresholdLevel { + _1Bytes, + _2Bytes, + _3Bytes, + _4Bytes, + _5Bytes, + _6Bytes, + _7Bytes, + _8Bytes, + _9Bytes, + _10Bytes, + _11Bytes, + _12Bytes, + _13Bytes, + _14Bytes, + _15Bytes, + _16Bytes, + _17Bytes, + _18Bytes, + _19Bytes, + _20Bytes, + _21Bytes, + _22Bytes, + _23Bytes, + _24Bytes, + _25Bytes, + _26Bytes, + _27Bytes, + _28Bytes, + _29Bytes, + _30Bytes, + _31Bytes, + _32Bytes, +} + +impl Into for FIFOThresholdLevel { + fn into(self) -> u8 { + match self { + FIFOThresholdLevel::_1Bytes => 0, + FIFOThresholdLevel::_2Bytes => 1, + FIFOThresholdLevel::_3Bytes => 2, + FIFOThresholdLevel::_4Bytes => 3, + FIFOThresholdLevel::_5Bytes => 4, + FIFOThresholdLevel::_6Bytes => 5, + FIFOThresholdLevel::_7Bytes => 6, + FIFOThresholdLevel::_8Bytes => 7, + FIFOThresholdLevel::_9Bytes => 8, + FIFOThresholdLevel::_10Bytes => 9, + FIFOThresholdLevel::_11Bytes => 10, + FIFOThresholdLevel::_12Bytes => 11, + FIFOThresholdLevel::_13Bytes => 12, + FIFOThresholdLevel::_14Bytes => 13, + FIFOThresholdLevel::_15Bytes => 14, + FIFOThresholdLevel::_16Bytes => 15, + FIFOThresholdLevel::_17Bytes => 16, + FIFOThresholdLevel::_18Bytes => 17, + FIFOThresholdLevel::_19Bytes => 18, + FIFOThresholdLevel::_20Bytes => 19, + FIFOThresholdLevel::_21Bytes => 20, + FIFOThresholdLevel::_22Bytes => 21, + FIFOThresholdLevel::_23Bytes => 22, + FIFOThresholdLevel::_24Bytes => 23, + FIFOThresholdLevel::_25Bytes => 24, + FIFOThresholdLevel::_26Bytes => 25, + FIFOThresholdLevel::_27Bytes => 26, + FIFOThresholdLevel::_28Bytes => 27, + FIFOThresholdLevel::_29Bytes => 28, + FIFOThresholdLevel::_30Bytes => 29, + FIFOThresholdLevel::_31Bytes => 30, + FIFOThresholdLevel::_32Bytes => 31, + } + } +} + +#[derive(Copy, Clone)] +pub enum DummyCycles { + _0, + _1, + _2, + _3, + _4, + _5, + _6, + _7, + _8, + _9, + _10, + _11, + _12, + _13, + _14, + _15, + _16, + _17, + _18, + _19, + _20, + _21, + _22, + _23, + _24, + _25, + _26, + _27, + _28, + _29, + _30, + _31, +} + +impl Into for DummyCycles { + fn into(self) -> u8 { + match self { + DummyCycles::_0 => 0, + DummyCycles::_1 => 1, + DummyCycles::_2 => 2, + DummyCycles::_3 => 3, + DummyCycles::_4 => 4, + DummyCycles::_5 => 5, + DummyCycles::_6 => 6, + DummyCycles::_7 => 7, + DummyCycles::_8 => 8, + DummyCycles::_9 => 9, + DummyCycles::_10 => 10, + DummyCycles::_11 => 11, + DummyCycles::_12 => 12, + DummyCycles::_13 => 13, + DummyCycles::_14 => 14, + DummyCycles::_15 => 15, + DummyCycles::_16 => 16, + DummyCycles::_17 => 17, + DummyCycles::_18 => 18, + DummyCycles::_19 => 19, + DummyCycles::_20 => 20, + DummyCycles::_21 => 21, + DummyCycles::_22 => 22, + DummyCycles::_23 => 23, + DummyCycles::_24 => 24, + DummyCycles::_25 => 25, + DummyCycles::_26 => 26, + DummyCycles::_27 => 27, + DummyCycles::_28 => 28, + DummyCycles::_29 => 29, + DummyCycles::_30 => 30, + DummyCycles::_31 => 31, + } + } +} diff --git a/embassy-stm32/src/qspi/mod.rs b/embassy-stm32/src/qspi/mod.rs new file mode 100644 index 000000000..e9db934bf --- /dev/null +++ b/embassy-stm32/src/qspi/mod.rs @@ -0,0 +1,332 @@ +#![macro_use] + +pub mod enums; + +use embassy_hal_common::{into_ref, PeripheralRef}; +use enums::*; + +use crate::dma::Transfer; +use crate::gpio::sealed::AFType; +use crate::gpio::AnyPin; +use crate::pac::quadspi::Quadspi as Regs; +use crate::rcc::RccPeripheral; +use crate::{peripherals, Peripheral}; + +pub struct TransferConfig { + /// Instraction width (IMODE) + pub iwidth: QspiWidth, + /// Address width (ADMODE) + pub awidth: QspiWidth, + /// Data width (DMODE) + pub dwidth: QspiWidth, + /// Instruction Id + pub instruction: u8, + /// Flash memory address + pub address: Option, + /// Number of dummy cycles (DCYC) + pub dummy: DummyCycles, + /// Length of data + pub data_len: Option, +} + +impl Default for TransferConfig { + fn default() -> Self { + Self { + iwidth: QspiWidth::NONE, + awidth: QspiWidth::NONE, + dwidth: QspiWidth::NONE, + instruction: 0, + address: None, + dummy: DummyCycles::_0, + data_len: None, + } + } +} + +pub struct Config { + /// Flash memory size representend as 2^[0-32], as reasonable minimum 1KiB(9) was chosen. + /// If you need other value the whose predefined use `Other` variant. + pub memory_size: MemorySize, + /// Address size (8/16/24/32-bit) + pub address_size: AddressSize, + /// Scalar factor for generating CLK [0-255] + pub prescaler: u8, + /// Number of bytes to trigger FIFO threshold flag. + pub fifo_threshold: FIFOThresholdLevel, + /// Minimum number of cycles that chip select must be high between issued commands + pub cs_high_time: ChipSelectHightTime, +} + +impl Default for Config { + fn default() -> Self { + Self { + memory_size: MemorySize::Other(0), + address_size: AddressSize::_24bit, + prescaler: 128, + fifo_threshold: FIFOThresholdLevel::_17Bytes, + cs_high_time: ChipSelectHightTime::_5Cycle, + } + } +} + +#[allow(dead_code)] +pub struct Qspi<'d, T: Instance, Dma> { + _peri: PeripheralRef<'d, T>, + sck: Option>, + d0: Option>, + d1: Option>, + d2: Option>, + d3: Option>, + nss: Option>, + dma: PeripheralRef<'d, Dma>, + config: Config, +} + +impl<'d, T: Instance, Dma> Qspi<'d, T, Dma> { + pub fn new( + peri: impl Peripheral

+ 'd, + d0: impl Peripheral

> + 'd, + d1: impl Peripheral

> + 'd, + d2: impl Peripheral

> + 'd, + d3: impl Peripheral

> + 'd, + sck: impl Peripheral

> + 'd, + nss: impl Peripheral

> + 'd, + dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(peri, d0, d1, d2, d3, sck, nss); + + sck.set_as_af(sck.af_num(), AFType::OutputPushPull); + sck.set_speed(crate::gpio::Speed::VeryHigh); + nss.set_as_af(nss.af_num(), AFType::OutputPushPull); + nss.set_speed(crate::gpio::Speed::VeryHigh); + d0.set_as_af(d0.af_num(), AFType::OutputPushPull); + d0.set_speed(crate::gpio::Speed::VeryHigh); + d1.set_as_af(d1.af_num(), AFType::OutputPushPull); + d1.set_speed(crate::gpio::Speed::VeryHigh); + d2.set_as_af(d2.af_num(), AFType::OutputPushPull); + d2.set_speed(crate::gpio::Speed::VeryHigh); + d3.set_as_af(d3.af_num(), AFType::OutputPushPull); + d3.set_speed(crate::gpio::Speed::VeryHigh); + + Self::new_inner( + peri, + Some(d0.map_into()), + Some(d1.map_into()), + Some(d2.map_into()), + Some(d3.map_into()), + Some(sck.map_into()), + Some(nss.map_into()), + dma, + config, + ) + } + + fn new_inner( + peri: impl Peripheral

+ 'd, + d0: Option>, + d1: Option>, + d2: Option>, + d3: Option>, + sck: Option>, + nss: Option>, + dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(peri, dma); + + T::enable(); + T::REGS.cr().write(|w| w.set_fthres(config.fifo_threshold.into())); + + while T::REGS.sr().read().busy() {} + + T::REGS.cr().write(|w| { + w.set_prescaler(config.prescaler); + w.set_en(true); + }); + T::REGS.dcr().write(|w| { + w.set_fsize(config.memory_size.into()); + w.set_csht(config.cs_high_time.into()); + w.set_ckmode(false); + }); + + Self { + _peri: peri, + sck, + d0, + d1, + d2, + d3, + nss, + dma, + config, + } + } + + pub fn command(&mut self, transaction: TransferConfig) { + T::REGS.cr().modify(|v| v.set_dmaen(false)); + self.setup_transaction(QspiMode::IndirectWrite, &transaction); + + while !T::REGS.sr().read().tcf() {} + T::REGS.fcr().modify(|v| v.set_ctcf(true)); + } + + pub fn blocking_read(&mut self, buf: &mut [u8], transaction: TransferConfig) { + T::REGS.cr().modify(|v| v.set_dmaen(false)); + self.setup_transaction(QspiMode::IndirectWrite, &transaction); + + if let Some(len) = transaction.data_len { + let current_ar = T::REGS.ar().read().address(); + T::REGS.ccr().modify(|v| { + v.set_fmode(QspiMode::IndirectRead.into()); + }); + T::REGS.ar().write(|v| { + v.set_address(current_ar); + }); + + for idx in 0..len { + while !T::REGS.sr().read().tcf() && !T::REGS.sr().read().ftf() {} + buf[idx] = unsafe { (T::REGS.dr().as_ptr() as *mut u8).read_volatile() }; + } + } + + while !T::REGS.sr().read().tcf() {} + T::REGS.fcr().modify(|v| v.set_ctcf(true)); + } + + pub fn blocking_write(&mut self, buf: &[u8], transaction: TransferConfig) { + T::REGS.cr().modify(|v| v.set_dmaen(false)); + self.setup_transaction(QspiMode::IndirectWrite, &transaction); + + if let Some(len) = transaction.data_len { + T::REGS.ccr().modify(|v| { + v.set_fmode(QspiMode::IndirectWrite.into()); + }); + + for idx in 0..len { + while !T::REGS.sr().read().ftf() {} + unsafe { (T::REGS.dr().as_ptr() as *mut u8).write_volatile(buf[idx]) }; + } + } + + while !T::REGS.sr().read().tcf() {} + T::REGS.fcr().modify(|v| v.set_ctcf(true)); + } + + pub fn blocking_read_dma(&mut self, buf: &mut [u8], transaction: TransferConfig) + where + Dma: QuadDma, + { + self.setup_transaction(QspiMode::IndirectWrite, &transaction); + + T::REGS.ccr().modify(|v| { + v.set_fmode(QspiMode::IndirectRead.into()); + }); + let current_ar = T::REGS.ar().read().address(); + T::REGS.ar().write(|v| { + v.set_address(current_ar); + }); + + let request = self.dma.request(); + let transfer = unsafe { + Transfer::new_read( + &mut self.dma, + request, + T::REGS.dr().as_ptr() as *mut u8, + buf, + Default::default(), + ) + }; + + T::REGS.cr().modify(|v| v.set_dmaen(true)); + + transfer.blocking_wait(); + } + + pub fn blocking_write_dma(&mut self, buf: &[u8], transaction: TransferConfig) + where + Dma: QuadDma, + { + self.setup_transaction(QspiMode::IndirectWrite, &transaction); + + T::REGS.ccr().modify(|v| { + v.set_fmode(QspiMode::IndirectWrite.into()); + }); + + let request = self.dma.request(); + let transfer = unsafe { + Transfer::new_write( + &mut self.dma, + request, + buf, + T::REGS.dr().as_ptr() as *mut u8, + Default::default(), + ) + }; + + T::REGS.cr().modify(|v| v.set_dmaen(true)); + + transfer.blocking_wait(); + } + + fn setup_transaction(&mut self, fmode: QspiMode, transaction: &TransferConfig) { + T::REGS.fcr().modify(|v| { + v.set_csmf(true); + v.set_ctcf(true); + v.set_ctef(true); + v.set_ctof(true); + }); + + while T::REGS.sr().read().busy() {} + + if let Some(len) = transaction.data_len { + T::REGS.dlr().write(|v| v.set_dl(len as u32 - 1)); + } + + T::REGS.ccr().write(|v| { + v.set_fmode(fmode.into()); + v.set_imode(transaction.iwidth.into()); + v.set_instruction(transaction.instruction); + v.set_admode(transaction.awidth.into()); + v.set_adsize(self.config.address_size.into()); + v.set_dmode(transaction.dwidth.into()); + v.set_abmode(QspiWidth::NONE.into()); + v.set_dcyc(transaction.dummy.into()); + }); + + if let Some(addr) = transaction.address { + T::REGS.ar().write(|v| { + v.set_address(addr); + }); + } + } +} + +pub(crate) mod sealed { + use super::*; + + pub trait Instance { + const REGS: Regs; + } +} + +pub trait Instance: Peripheral

+ sealed::Instance + RccPeripheral {} + +pin_trait!(SckPin, Instance); +pin_trait!(D0Pin, Instance); +pin_trait!(D1Pin, Instance); +pin_trait!(D2Pin, Instance); +pin_trait!(D3Pin, Instance); +pin_trait!(NSSPin, Instance); + +dma_trait!(QuadDma, Instance); + +foreach_peripheral!( + (quadspi, $inst:ident) => { + impl sealed::Instance for peripherals::$inst { + const REGS: Regs = crate::pac::$inst; + } + + impl Instance for peripherals::$inst {} + }; +); diff --git a/embassy-stm32/src/rcc/c0.rs b/embassy-stm32/src/rcc/c0.rs new file mode 100644 index 000000000..df6e9047c --- /dev/null +++ b/embassy-stm32/src/rcc/c0.rs @@ -0,0 +1,233 @@ +use crate::pac::flash::vals::Latency; +use crate::pac::rcc::vals::{Hpre, Hsidiv, Ppre, Sw}; +use crate::pac::{FLASH, RCC}; +use crate::rcc::{set_freqs, Clocks}; +use crate::time::Hertz; + +/// HSI speed +pub const HSI_FREQ: Hertz = Hertz(48_000_000); + +/// LSI speed +pub const LSI_FREQ: Hertz = Hertz(32_000); + +/// System clock mux source +#[derive(Clone, Copy)] +pub enum ClockSrc { + HSE(Hertz), + HSI(HSIPrescaler), + LSI, +} + +#[derive(Clone, Copy)] +pub enum HSIPrescaler { + NotDivided, + Div2, + Div4, + Div8, + Div16, + Div32, + Div64, + Div128, +} + +impl Into for HSIPrescaler { + fn into(self) -> Hsidiv { + match self { + HSIPrescaler::NotDivided => Hsidiv::DIV1, + HSIPrescaler::Div2 => Hsidiv::DIV2, + HSIPrescaler::Div4 => Hsidiv::DIV4, + HSIPrescaler::Div8 => Hsidiv::DIV8, + HSIPrescaler::Div16 => Hsidiv::DIV16, + HSIPrescaler::Div32 => Hsidiv::DIV32, + HSIPrescaler::Div64 => Hsidiv::DIV64, + HSIPrescaler::Div128 => Hsidiv::DIV128, + } + } +} + +/// AHB prescaler +#[derive(Clone, Copy, PartialEq)] +pub enum AHBPrescaler { + NotDivided, + Div2, + Div4, + Div8, + Div16, + Div64, + Div128, + Div256, + Div512, +} + +/// APB prescaler +#[derive(Clone, Copy)] +pub enum APBPrescaler { + NotDivided, + Div2, + Div4, + Div8, + Div16, +} + +impl Into for APBPrescaler { + fn into(self) -> Ppre { + match self { + APBPrescaler::NotDivided => Ppre::DIV1, + APBPrescaler::Div2 => Ppre::DIV2, + APBPrescaler::Div4 => Ppre::DIV4, + APBPrescaler::Div8 => Ppre::DIV8, + APBPrescaler::Div16 => Ppre::DIV16, + } + } +} + +impl Into for AHBPrescaler { + fn into(self) -> Hpre { + match self { + AHBPrescaler::NotDivided => Hpre::DIV1, + AHBPrescaler::Div2 => Hpre::DIV2, + AHBPrescaler::Div4 => Hpre::DIV4, + AHBPrescaler::Div8 => Hpre::DIV8, + AHBPrescaler::Div16 => Hpre::DIV16, + AHBPrescaler::Div64 => Hpre::DIV64, + AHBPrescaler::Div128 => Hpre::DIV128, + AHBPrescaler::Div256 => Hpre::DIV256, + AHBPrescaler::Div512 => Hpre::DIV512, + } + } +} + +/// Clocks configutation +pub struct Config { + pub mux: ClockSrc, + pub ahb_pre: AHBPrescaler, + pub apb_pre: APBPrescaler, +} + +impl Default for Config { + #[inline] + fn default() -> Config { + Config { + mux: ClockSrc::HSI(HSIPrescaler::NotDivided), + ahb_pre: AHBPrescaler::NotDivided, + apb_pre: APBPrescaler::NotDivided, + } + } +} + +pub(crate) unsafe fn init(config: Config) { + let (sys_clk, sw) = match config.mux { + ClockSrc::HSI(div) => { + // Enable HSI + let div: Hsidiv = div.into(); + RCC.cr().write(|w| { + w.set_hsidiv(div); + w.set_hsion(true) + }); + while !RCC.cr().read().hsirdy() {} + + (HSI_FREQ.0 >> div.to_bits(), Sw::HSI) + } + ClockSrc::HSE(freq) => { + // Enable HSE + RCC.cr().write(|w| w.set_hseon(true)); + while !RCC.cr().read().hserdy() {} + + (freq.0, Sw::HSE) + } + ClockSrc::LSI => { + // Enable LSI + RCC.csr2().write(|w| w.set_lsion(true)); + while !RCC.csr2().read().lsirdy() {} + (LSI_FREQ.0, Sw::LSI) + } + }; + + // Determine the flash latency implied by the target clock speed + // RM0454 § 3.3.4: + let target_flash_latency = if sys_clk <= 24_000_000 { + Latency::WS0 + } else { + Latency::WS1 + }; + + // Increase the number of cycles we wait for flash if the new value is higher + // There's no harm in waiting a little too much before the clock change, but we'll + // crash immediately if we don't wait enough after the clock change + let mut set_flash_latency_after = false; + FLASH.acr().modify(|w| { + // Is the current flash latency less than what we need at the new SYSCLK? + if w.latency().to_bits() <= target_flash_latency.to_bits() { + // We must increase the number of wait states now + w.set_latency(target_flash_latency) + } else { + // We may decrease the number of wait states later + set_flash_latency_after = true; + } + + // RM0490 § 3.3.4: + // > Prefetch is enabled by setting the PRFTEN bit of the FLASH access control register + // > (FLASH_ACR). This feature is useful if at least one wait state is needed to access the + // > Flash memory. + // + // Enable flash prefetching if we have at least one wait state, and disable it otherwise. + w.set_prften(target_flash_latency.to_bits() > 0); + }); + + if !set_flash_latency_after { + // Spin until the effective flash latency is compatible with the clock change + while FLASH.acr().read().latency().to_bits() < target_flash_latency.to_bits() {} + } + + // Configure SYSCLK source, HCLK divisor, and PCLK divisor all at once + let (sw, hpre, ppre) = (sw.into(), config.ahb_pre.into(), config.apb_pre.into()); + RCC.cfgr().modify(|w| { + w.set_sw(sw); + w.set_hpre(hpre); + w.set_ppre(ppre); + }); + + if set_flash_latency_after { + // We can make the flash require fewer wait states + // Spin until the SYSCLK changes have taken effect + loop { + let cfgr = RCC.cfgr().read(); + if cfgr.sw() == sw && cfgr.hpre() == hpre && cfgr.ppre() == ppre { + break; + } + } + + // Set the flash latency to require fewer wait states + FLASH.acr().modify(|w| w.set_latency(target_flash_latency)); + } + + let ahb_div = match config.ahb_pre { + AHBPrescaler::NotDivided => 1, + AHBPrescaler::Div2 => 2, + AHBPrescaler::Div4 => 4, + AHBPrescaler::Div8 => 8, + AHBPrescaler::Div16 => 16, + AHBPrescaler::Div64 => 64, + AHBPrescaler::Div128 => 128, + AHBPrescaler::Div256 => 256, + AHBPrescaler::Div512 => 512, + }; + let ahb_freq = sys_clk / ahb_div; + + let (apb_freq, apb_tim_freq) = match config.apb_pre { + APBPrescaler::NotDivided => (ahb_freq, ahb_freq), + pre => { + let pre: Ppre = pre.into(); + let pre: u8 = 1 << (pre.to_bits() - 3); + let freq = ahb_freq / pre as u32; + (freq, freq * 2) + } + }; + + set_freqs(Clocks { + sys: Hertz(sys_clk), + ahb1: Hertz(ahb_freq), + apb1: Hertz(apb_freq), + apb1_tim: Hertz(apb_tim_freq), + }); +} diff --git a/embassy-stm32/src/rcc/f0.rs b/embassy-stm32/src/rcc/f0.rs index eb62ab661..ca6eed284 100644 --- a/embassy-stm32/src/rcc/f0.rs +++ b/embassy-stm32/src/rcc/f0.rs @@ -1,3 +1,5 @@ +use stm32_metapac::flash::vals::Latency; + use super::{set_freqs, Clocks}; use crate::pac::rcc::vals::{Hpre, Pllmul, Pllsrc, Ppre, Sw, Usbsw}; use crate::pac::{FLASH, RCC}; @@ -85,14 +87,11 @@ pub(crate) unsafe fn init(config: Config) { let timer_mul = if ppre == 1 { 1 } else { 2 }; FLASH.acr().write(|w| { - let latency = if real_sysclk <= 24_000_000 { - 0 - } else if real_sysclk <= 48_000_000 { - 1 + w.set_latency(if real_sysclk <= 24_000_000 { + Latency::WS0 } else { - 2 - }; - w.latency().0 = latency; + Latency::WS1 + }); }); match (config.hse.is_some(), use_hsi48) { @@ -134,20 +133,20 @@ pub(crate) unsafe fn init(config: Config) { // TODO: Option to use CRS (Clock Recovery) if let Some(pllmul_bits) = pllmul_bits { - RCC.cfgr().modify(|w| w.set_pllmul(Pllmul(pllmul_bits))); + RCC.cfgr().modify(|w| w.set_pllmul(Pllmul::from_bits(pllmul_bits))); RCC.cr().modify(|w| w.set_pllon(true)); while !RCC.cr().read().pllrdy() {} RCC.cfgr().modify(|w| { - w.set_ppre(Ppre(ppre_bits)); - w.set_hpre(Hpre(hpre_bits)); + w.set_ppre(Ppre::from_bits(ppre_bits)); + w.set_hpre(Hpre::from_bits(hpre_bits)); w.set_sw(Sw::PLL) }); } else { RCC.cfgr().modify(|w| { - w.set_ppre(Ppre(ppre_bits)); - w.set_hpre(Hpre(hpre_bits)); + w.set_ppre(Ppre::from_bits(ppre_bits)); + w.set_hpre(Hpre::from_bits(hpre_bits)); if config.hse.is_some() { w.set_sw(Sw::HSE); diff --git a/embassy-stm32/src/rcc/f1.rs b/embassy-stm32/src/rcc/f1.rs index e667dbf90..b6200231e 100644 --- a/embassy-stm32/src/rcc/f1.rs +++ b/embassy-stm32/src/rcc/f1.rs @@ -24,10 +24,13 @@ pub struct Config { pub pclk1: Option, pub pclk2: Option, pub adcclk: Option, + pub pllxtpre: bool, } pub(crate) unsafe fn init(config: Config) { - let pllsrcclk = config.hse.map(|hse| hse.0).unwrap_or(HSI_FREQ.0 / 2); + let pllxtpre_div = if config.pllxtpre { 2 } else { 1 }; + let pllsrcclk = config.hse.map(|hse| hse.0 / pllxtpre_div).unwrap_or(HSI_FREQ.0 / 2); + let sysclk = config.sys_ck.map(|sys| sys.0).unwrap_or(pllsrcclk); let pllmul = sysclk / pllsrcclk; @@ -103,11 +106,11 @@ pub(crate) unsafe fn init(config: Config) { // Only needed for stm32f103? FLASH.acr().write(|w| { w.set_latency(if real_sysclk <= 24_000_000 { - Latency(0b000) + Latency::WS0 } else if real_sysclk <= 48_000_000 { - Latency(0b001) + Latency::WS1 } else { - Latency(0b010) + Latency::WS2 }); }); @@ -143,10 +146,14 @@ pub(crate) unsafe fn init(config: Config) { } if let Some(pllmul_bits) = pllmul_bits { + let pllctpre_flag: u8 = if config.pllxtpre { 1 } else { 0 }; + RCC.cfgr() + .modify(|w| w.set_pllxtpre(Pllxtpre::from_bits(pllctpre_flag))); + // enable PLL and wait for it to be ready RCC.cfgr().modify(|w| { - w.set_pllmul(Pllmul(pllmul_bits)); - w.set_pllsrc(Pllsrc(config.hse.is_some() as u8)); + w.set_pllmul(Pllmul::from_bits(pllmul_bits)); + w.set_pllsrc(Pllsrc::from_bits(config.hse.is_some() as u8)); }); RCC.cr().modify(|w| w.set_pllon(true)); @@ -155,22 +162,19 @@ pub(crate) unsafe fn init(config: Config) { // Only needed for stm32f103? RCC.cfgr().modify(|w| { - w.set_adcpre(Adcpre(apre_bits)); - w.set_ppre2(Ppre1(ppre2_bits)); - w.set_ppre1(Ppre1(ppre1_bits)); - w.set_hpre(Hpre(hpre_bits)); + w.set_adcpre(Adcpre::from_bits(apre_bits)); + w.set_ppre2(Ppre1::from_bits(ppre2_bits)); + w.set_ppre1(Ppre1::from_bits(ppre1_bits)); + w.set_hpre(Hpre::from_bits(hpre_bits)); #[cfg(not(rcc_f100))] - w.set_usbpre(Usbpre(usbpre as u8)); - w.set_sw(Sw(if pllmul_bits.is_some() { - // PLL - 0b10 + w.set_usbpre(Usbpre::from_bits(usbpre as u8)); + w.set_sw(if pllmul_bits.is_some() { + Sw::PLL } else if config.hse.is_some() { - // HSE - 0b1 + Sw::HSE } else { - // HSI - 0b0 - })); + Sw::HSI + }); }); set_freqs(Clocks { diff --git a/embassy-stm32/src/rcc/f2.rs b/embassy-stm32/src/rcc/f2.rs index d543888c1..1525cc3c3 100644 --- a/embassy-stm32/src/rcc/f2.rs +++ b/embassy-stm32/src/rcc/f2.rs @@ -148,7 +148,7 @@ impl Into for PLLMainDiv { match self { PLLMainDiv::Div2 => Pllp::DIV2, PLLMainDiv::Div4 => Pllp::DIV4, - PLLMainDiv::Div6 => Pllp::DIV8, + PLLMainDiv::Div6 => Pllp::DIV6, PLLMainDiv::Div8 => Pllp::DIV8, } } @@ -485,7 +485,7 @@ pub(crate) unsafe fn init(config: Config) { w.set_ppre1(config.apb1_pre.into()); w.set_ppre2(config.apb2_pre.into()); }); - while RCC.cfgr().read().sws() != sw.0 {} + while RCC.cfgr().read().sws().to_bits() != sw.to_bits() {} // Turn off HSI to save power if we don't need it if !config.hsi { diff --git a/embassy-stm32/src/rcc/f4.rs b/embassy-stm32/src/rcc/f4.rs index 200bcce9c..b84470440 100644 --- a/embassy-stm32/src/rcc/f4.rs +++ b/embassy-stm32/src/rcc/f4.rs @@ -1,8 +1,16 @@ +use core::marker::PhantomData; + +use embassy_hal_common::into_ref; +use stm32_metapac::rcc::vals::{Mco1, Mco2, Mcopre}; + use super::sealed::RccPeripheral; +use crate::gpio::sealed::AFType; +use crate::gpio::Speed; use crate::pac::rcc::vals::{Hpre, Ppre, Sw}; use crate::pac::{FLASH, PWR, RCC}; use crate::rcc::{set_freqs, Clocks}; use crate::time::Hertz; +use crate::{peripherals, Peripheral}; /// HSI speed pub const HSI_FREQ: Hertz = Hertz(16_000_000); @@ -21,20 +29,71 @@ pub struct Config { pub pclk1: Option, pub pclk2: Option, + #[cfg(not(any(stm32f410, stm32f411, stm32f412, stm32f413, stm32f423, stm32f446)))] + pub plli2s: Option, + pub pll48: bool, } -unsafe fn setup_pll(pllsrcclk: u32, use_hse: bool, pllsysclk: Option, pll48clk: bool) -> PllResults { +#[cfg(stm32f410)] +fn setup_i2s_pll(_vco_in: u32, _plli2s: Option) -> Option { + None +} + +// Not currently implemented, but will be in the future +#[cfg(any(stm32f411, stm32f412, stm32f413, stm32f423, stm32f446))] +fn setup_i2s_pll(_vco_in: u32, _plli2s: Option) -> Option { + None +} + +#[cfg(not(any(stm32f410, stm32f411, stm32f412, stm32f413, stm32f423, stm32f446)))] +fn setup_i2s_pll(vco_in: u32, plli2s: Option) -> Option { + let min_div = 2; + let max_div = 7; + let target = match plli2s { + Some(target) => target, + None => return None, + }; + + // We loop through the possible divider values to find the best configuration. Looping + // through all possible "N" values would result in more iterations. + let (n, outdiv, output, _error) = (min_div..=max_div) + .filter_map(|outdiv| { + let target_vco_out = match target.checked_mul(outdiv) { + Some(x) => x, + None => return None, + }; + let n = (target_vco_out + (vco_in >> 1)) / vco_in; + let vco_out = vco_in * n; + if !(100_000_000..=432_000_000).contains(&vco_out) { + return None; + } + let output = vco_out / outdiv; + let error = (output as i32 - target as i32).unsigned_abs(); + Some((n, outdiv, output, error)) + }) + .min_by_key(|(_, _, _, error)| *error)?; + + RCC.plli2scfgr().modify(|w| { + w.set_plli2sn(n as u16); + w.set_plli2sr(outdiv as u8); + }); + + Some(output) +} + +fn setup_pll(pllsrcclk: u32, use_hse: bool, pllsysclk: Option, plli2s: Option, pll48clk: bool) -> PllResults { use crate::pac::rcc::vals::{Pllp, Pllsrc}; let sysclk = pllsysclk.unwrap_or(pllsrcclk); if pllsysclk.is_none() && !pll48clk { - RCC.pllcfgr().modify(|w| w.set_pllsrc(Pllsrc(use_hse as u8))); + RCC.pllcfgr().modify(|w| w.set_pllsrc(Pllsrc::from_bits(use_hse as u8))); return PllResults { use_pll: false, pllsysclk: None, pll48clk: None, + plli2sclk: None, }; } // Input divisor from PLL source clock, must result to frequency in @@ -82,9 +141,9 @@ unsafe fn setup_pll(pllsrcclk: u32, use_hse: bool, pllsysclk: Option, pll48 RCC.pllcfgr().modify(|w| { w.set_pllm(pllm as u8); w.set_plln(plln as u16); - w.set_pllp(Pllp(pllp as u8)); + w.set_pllp(Pllp::from_bits(pllp as u8)); w.set_pllq(pllq as u8); - w.set_pllsrc(Pllsrc(use_hse as u8)); + w.set_pllsrc(Pllsrc::from_bits(use_hse as u8)); }); let real_pllsysclk = vco_in * plln / sysclk_div; @@ -93,10 +152,169 @@ unsafe fn setup_pll(pllsrcclk: u32, use_hse: bool, pllsysclk: Option, pll48 use_pll: true, pllsysclk: Some(real_pllsysclk), pll48clk: if pll48clk { Some(real_pll48clk) } else { None }, + plli2sclk: setup_i2s_pll(vco_in, plli2s), } } -unsafe fn flash_setup(sysclk: u32) { +pub enum McoClock { + DIV1, + DIV2, + DIV3, + DIV4, + DIV5, +} + +impl McoClock { + fn into_raw(&self) -> Mcopre { + match self { + McoClock::DIV1 => Mcopre::DIV1, + McoClock::DIV2 => Mcopre::DIV2, + McoClock::DIV3 => Mcopre::DIV3, + McoClock::DIV4 => Mcopre::DIV4, + McoClock::DIV5 => Mcopre::DIV5, + } + } +} + +#[derive(Copy, Clone)] +pub enum Mco1Source { + Hsi, + Lse, + Hse, + Pll, +} + +impl Default for Mco1Source { + fn default() -> Self { + Self::Hsi + } +} + +pub trait McoSource { + type Raw; + + fn into_raw(&self) -> Self::Raw; +} + +impl McoSource for Mco1Source { + type Raw = Mco1; + fn into_raw(&self) -> Self::Raw { + match self { + Mco1Source::Hsi => Mco1::HSI, + Mco1Source::Lse => Mco1::LSE, + Mco1Source::Hse => Mco1::HSE, + Mco1Source::Pll => Mco1::PLL, + } + } +} + +#[derive(Copy, Clone)] +pub enum Mco2Source { + SysClk, + Plli2s, + Hse, + Pll, +} + +impl Default for Mco2Source { + fn default() -> Self { + Self::SysClk + } +} + +impl McoSource for Mco2Source { + type Raw = Mco2; + fn into_raw(&self) -> Self::Raw { + match self { + Mco2Source::SysClk => Mco2::SYSCLK, + Mco2Source::Plli2s => Mco2::PLLI2S, + Mco2Source::Hse => Mco2::HSE, + Mco2Source::Pll => Mco2::PLL, + } + } +} + +pub(crate) mod sealed { + use stm32_metapac::rcc::vals::Mcopre; + pub trait McoInstance { + type Source; + unsafe fn apply_clock_settings(source: Self::Source, prescaler: Mcopre); + } +} + +pub trait McoInstance: sealed::McoInstance + 'static {} + +pin_trait!(McoPin, McoInstance); + +impl sealed::McoInstance for peripherals::MCO1 { + type Source = Mco1; + unsafe fn apply_clock_settings(source: Self::Source, prescaler: Mcopre) { + RCC.cfgr().modify(|w| { + w.set_mco1(source); + w.set_mco1pre(prescaler); + }); + match source { + Mco1::PLL => { + RCC.cr().modify(|w| w.set_pllon(true)); + while !RCC.cr().read().pllrdy() {} + } + Mco1::HSI => { + RCC.cr().modify(|w| w.set_hsion(true)); + while !RCC.cr().read().hsirdy() {} + } + _ => {} + } + } +} +impl McoInstance for peripherals::MCO1 {} + +impl sealed::McoInstance for peripherals::MCO2 { + type Source = Mco2; + unsafe fn apply_clock_settings(source: Self::Source, prescaler: Mcopre) { + RCC.cfgr().modify(|w| { + w.set_mco2(source); + w.set_mco2pre(prescaler); + }); + match source { + Mco2::PLL => { + RCC.cr().modify(|w| w.set_pllon(true)); + while !RCC.cr().read().pllrdy() {} + } + #[cfg(not(stm32f410))] + Mco2::PLLI2S => { + RCC.cr().modify(|w| w.set_plli2son(true)); + while !RCC.cr().read().plli2srdy() {} + } + _ => {} + } + } +} +impl McoInstance for peripherals::MCO2 {} + +pub struct Mco<'d, T: McoInstance> { + phantom: PhantomData<&'d mut T>, +} + +impl<'d, T: McoInstance> Mco<'d, T> { + pub fn new( + _peri: impl Peripheral

+ 'd, + pin: impl Peripheral

> + 'd, + source: impl McoSource, + prescaler: McoClock, + ) -> Self { + into_ref!(pin); + + critical_section::with(|_| unsafe { + T::apply_clock_settings(source.into_raw(), prescaler.into_raw()); + pin.set_as_af(pin.af_num(), AFType::OutputPushPull); + pin.set_speed(Speed::VeryHigh); + }); + + Self { phantom: PhantomData } + } +} + +fn flash_setup(sysclk: u32) { use crate::pac::flash::vals::Latency; // Be conservative with voltage ranges @@ -105,7 +323,7 @@ unsafe fn flash_setup(sysclk: u32) { critical_section::with(|_| { FLASH .acr() - .modify(|w| w.set_latency(Latency(((sysclk - 1) / FLASH_LATENCY_STEP) as u8))); + .modify(|w| w.set_latency(Latency::from_bits(((sysclk - 1) / FLASH_LATENCY_STEP) as u8))); }); } @@ -120,6 +338,10 @@ pub(crate) unsafe fn init(config: Config) { pllsrcclk, config.hse.is_some(), if sysclk_on_pll { Some(sysclk) } else { None }, + #[cfg(not(any(stm32f410, stm32f411, stm32f412, stm32f413, stm32f423, stm32f446)))] + config.plli2s.map(|i2s| i2s.0), + #[cfg(any(stm32f410, stm32f411, stm32f412, stm32f413, stm32f423, stm32f446))] + None, config.pll48, ); @@ -210,9 +432,16 @@ pub(crate) unsafe fn init(config: Config) { while !RCC.cr().read().pllrdy() {} } + #[cfg(not(stm32f410))] + if plls.plli2sclk.is_some() { + RCC.cr().modify(|w| w.set_plli2son(true)); + + while !RCC.cr().read().plli2srdy() {} + } + RCC.cfgr().modify(|w| { - w.set_ppre2(Ppre(ppre2_bits)); - w.set_ppre1(Ppre(ppre1_bits)); + w.set_ppre2(Ppre::from_bits(ppre2_bits)); + w.set_ppre1(Ppre::from_bits(ppre1_bits)); w.set_hpre(hpre_bits); }); @@ -243,6 +472,12 @@ pub(crate) unsafe fn init(config: Config) { ahb3: Hertz(hclk), pll48: plls.pll48clk.map(Hertz), + + #[cfg(not(stm32f410))] + plli2s: plls.plli2sclk.map(Hertz), + + #[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479))] + pllsai: None, }); } @@ -250,6 +485,8 @@ struct PllResults { use_pll: bool, pllsysclk: Option, pll48clk: Option, + #[allow(dead_code)] + plli2sclk: Option, } mod max { diff --git a/embassy-stm32/src/rcc/f7.rs b/embassy-stm32/src/rcc/f7.rs index 2d21326a3..85cb9c661 100644 --- a/embassy-stm32/src/rcc/f7.rs +++ b/embassy-stm32/src/rcc/f7.rs @@ -25,12 +25,12 @@ pub struct Config { pub pll48: bool, } -unsafe fn setup_pll(pllsrcclk: u32, use_hse: bool, pllsysclk: Option, pll48clk: bool) -> PllResults { +fn setup_pll(pllsrcclk: u32, use_hse: bool, pllsysclk: Option, pll48clk: bool) -> PllResults { use crate::pac::rcc::vals::{Pllp, Pllsrc}; let sysclk = pllsysclk.unwrap_or(pllsrcclk); if pllsysclk.is_none() && !pll48clk { - RCC.pllcfgr().modify(|w| w.set_pllsrc(Pllsrc(use_hse as u8))); + RCC.pllcfgr().modify(|w| w.set_pllsrc(Pllsrc::from_bits(use_hse as u8))); return PllResults { use_pll: false, @@ -83,9 +83,9 @@ unsafe fn setup_pll(pllsrcclk: u32, use_hse: bool, pllsysclk: Option, pll48 RCC.pllcfgr().modify(|w| { w.set_pllm(pllm as u8); w.set_plln(plln as u16); - w.set_pllp(Pllp(pllp as u8)); + w.set_pllp(Pllp::from_bits(pllp as u8)); w.set_pllq(pllq as u8); - w.set_pllsrc(Pllsrc(use_hse as u8)); + w.set_pllsrc(Pllsrc::from_bits(use_hse as u8)); }); let real_pllsysclk = vco_in * plln / sysclk_div; @@ -97,7 +97,7 @@ unsafe fn setup_pll(pllsrcclk: u32, use_hse: bool, pllsysclk: Option, pll48 } } -unsafe fn flash_setup(sysclk: u32) { +fn flash_setup(sysclk: u32) { use crate::pac::flash::vals::Latency; // Be conservative with voltage ranges @@ -106,7 +106,7 @@ unsafe fn flash_setup(sysclk: u32) { critical_section::with(|_| { FLASH .acr() - .modify(|w| w.set_latency(Latency(((sysclk - 1) / FLASH_LATENCY_STEP) as u8))); + .modify(|w| w.set_latency(Latency::from_bits(((sysclk - 1) / FLASH_LATENCY_STEP) as u8))); }); } @@ -246,8 +246,8 @@ pub(crate) unsafe fn init(config: Config) { } RCC.cfgr().modify(|w| { - w.set_ppre2(Ppre(ppre2_bits)); - w.set_ppre1(Ppre(ppre1_bits)); + w.set_ppre2(Ppre::from_bits(ppre2_bits)); + w.set_ppre1(Ppre::from_bits(ppre1_bits)); w.set_hpre(hpre_bits); }); diff --git a/embassy-stm32/src/rcc/g0.rs b/embassy-stm32/src/rcc/g0.rs index 3e138c7ab..5e3a7911a 100644 --- a/embassy-stm32/src/rcc/g0.rs +++ b/embassy-stm32/src/rcc/g0.rs @@ -245,7 +245,7 @@ impl Default for Config { } impl PllConfig { - pub(crate) unsafe fn init(self) -> u32 { + pub(crate) fn init(self) -> u32 { assert!(self.n >= 8 && self.n <= 86); let (src, input_freq) = match self.source { PllSrc::HSI16 => (vals::Pllsrc::HSI16, HSI_FREQ.0), @@ -344,7 +344,7 @@ pub(crate) unsafe fn init(config: Config) { }); while !RCC.cr().read().hsirdy() {} - (HSI_FREQ.0 >> div.0, Sw::HSI) + (HSI_FREQ.0 >> div.to_bits(), Sw::HSI) } ClockSrc::HSE(freq) => { // Enable HSE @@ -381,7 +381,7 @@ pub(crate) unsafe fn init(config: Config) { let mut set_flash_latency_after = false; FLASH.acr().modify(|w| { // Is the current flash latency less than what we need at the new SYSCLK? - if w.latency().0 <= target_flash_latency.0 { + if w.latency().to_bits() <= target_flash_latency.to_bits() { // We must increase the number of wait states now w.set_latency(target_flash_latency) } else { @@ -395,12 +395,12 @@ pub(crate) unsafe fn init(config: Config) { // > Flash memory. // // Enable flash prefetching if we have at least one wait state, and disable it otherwise. - w.set_prften(target_flash_latency.0 > 0); + w.set_prften(target_flash_latency.to_bits() > 0); }); if !set_flash_latency_after { // Spin until the effective flash latency is compatible with the clock change - while FLASH.acr().read().latency().0 < target_flash_latency.0 {} + while FLASH.acr().read().latency().to_bits() < target_flash_latency.to_bits() {} } // Configure SYSCLK source, HCLK divisor, and PCLK divisor all at once @@ -442,7 +442,7 @@ pub(crate) unsafe fn init(config: Config) { APBPrescaler::NotDivided => (ahb_freq, ahb_freq), pre => { let pre: Ppre = pre.into(); - let pre: u8 = 1 << (pre.0 - 3); + let pre: u8 = 1 << (pre.to_bits() - 3); let freq = ahb_freq / pre as u32; (freq, freq * 2) } diff --git a/embassy-stm32/src/rcc/g4.rs b/embassy-stm32/src/rcc/g4.rs index 0f078d328..ff8f97541 100644 --- a/embassy-stm32/src/rcc/g4.rs +++ b/embassy-stm32/src/rcc/g4.rs @@ -1,4 +1,9 @@ +use stm32_metapac::flash::vals::Latency; +use stm32_metapac::rcc::vals::{Hpre, Pllsrc, Ppre, Sw}; +use stm32_metapac::FLASH; + use crate::pac::{PWR, RCC}; +use crate::rcc::sealed::RccPeripheral; use crate::rcc::{set_freqs, Clocks}; use crate::time::Hertz; @@ -13,6 +18,7 @@ pub const LSI_FREQ: Hertz = Hertz(32_000); pub enum ClockSrc { HSE(Hertz), HSI16, + PLL, } /// AHB prescaler @@ -39,32 +45,297 @@ pub enum APBPrescaler { Div16, } -impl Into for APBPrescaler { - fn into(self) -> u8 { +/// PLL clock input source +#[derive(Clone, Copy, Debug)] +pub enum PllSrc { + HSI16, + HSE(Hertz), +} + +impl Into for PllSrc { + fn into(self) -> Pllsrc { match self { - APBPrescaler::NotDivided => 1, - APBPrescaler::Div2 => 0x04, - APBPrescaler::Div4 => 0x05, - APBPrescaler::Div8 => 0x06, - APBPrescaler::Div16 => 0x07, + PllSrc::HSE(..) => Pllsrc::HSE, + PllSrc::HSI16 => Pllsrc::HSI16, } } } -impl Into for AHBPrescaler { - fn into(self) -> u8 { - match self { - AHBPrescaler::NotDivided => 1, - AHBPrescaler::Div2 => 0x08, - AHBPrescaler::Div4 => 0x09, - AHBPrescaler::Div8 => 0x0a, - AHBPrescaler::Div16 => 0x0b, - AHBPrescaler::Div64 => 0x0c, - AHBPrescaler::Div128 => 0x0d, - AHBPrescaler::Div256 => 0x0e, - AHBPrescaler::Div512 => 0x0f, +seq_macro::seq!(P in 2..=31 { + /// Output divider for the PLL P output. + #[derive(Clone, Copy)] + pub enum PllP { + // Note: If PLL P is set to 0 the PLLP bit controls the output division. There does not seem to + // a good reason to do this so the API does not support it. + // Div1 is invalid + #( + Div~P, + )* + } + + impl From for u8 { + /// Returns the register value for the P output divider. + fn from(val: PllP) -> u8 { + match val { + #( + PllP::Div~P => P, + )* + } } } +}); + +impl PllP { + /// Returns the numeric value of the P output divider. + pub fn to_div(self) -> u32 { + let val: u8 = self.into(); + val as u32 + } +} + +/// Output divider for the PLL Q output. +#[derive(Clone, Copy)] +pub enum PllQ { + Div2, + Div4, + Div6, + Div8, +} + +impl PllQ { + /// Returns the numeric value of the Q output divider. + pub fn to_div(self) -> u32 { + let val: u8 = self.into(); + (val as u32 + 1) * 2 + } +} + +impl From for u8 { + /// Returns the register value for the Q output divider. + fn from(val: PllQ) -> u8 { + match val { + PllQ::Div2 => 0b00, + PllQ::Div4 => 0b01, + PllQ::Div6 => 0b10, + PllQ::Div8 => 0b11, + } + } +} + +/// Output divider for the PLL R output. +#[derive(Clone, Copy)] +pub enum PllR { + Div2, + Div4, + Div6, + Div8, +} + +impl PllR { + /// Returns the numeric value of the R output divider. + pub fn to_div(self) -> u32 { + let val: u8 = self.into(); + (val as u32 + 1) * 2 + } +} + +impl From for u8 { + /// Returns the register value for the R output divider. + fn from(val: PllR) -> u8 { + match val { + PllR::Div2 => 0b00, + PllR::Div4 => 0b01, + PllR::Div6 => 0b10, + PllR::Div8 => 0b11, + } + } +} + +seq_macro::seq!(N in 8..=127 { + /// Multiplication factor for the PLL VCO input clock. + #[derive(Clone, Copy)] + pub enum PllN { + #( + Mul~N, + )* + } + + impl From for u8 { + /// Returns the register value for the N multiplication factor. + fn from(val: PllN) -> u8 { + match val { + #( + PllN::Mul~N => N, + )* + } + } + } + + impl PllN { + /// Returns the numeric value of the N multiplication factor. + pub fn to_mul(self) -> u32 { + match self { + #( + PllN::Mul~N => N, + )* + } + } + } +}); + +/// PLL Pre-division. This must be set such that the PLL input is between 2.66 MHz and 16 MHz. +#[derive(Copy, Clone)] +pub enum PllM { + Div1, + Div2, + Div3, + Div4, + Div5, + Div6, + Div7, + Div8, + Div9, + Div10, + Div11, + Div12, + Div13, + Div14, + Div15, + Div16, +} + +impl PllM { + /// Returns the numeric value of the M pre-division. + pub fn to_div(self) -> u32 { + let val: u8 = self.into(); + val as u32 + 1 + } +} + +impl From for u8 { + /// Returns the register value for the M pre-division. + fn from(val: PllM) -> u8 { + match val { + PllM::Div1 => 0b0000, + PllM::Div2 => 0b0001, + PllM::Div3 => 0b0010, + PllM::Div4 => 0b0011, + PllM::Div5 => 0b0100, + PllM::Div6 => 0b0101, + PllM::Div7 => 0b0110, + PllM::Div8 => 0b0111, + PllM::Div9 => 0b1000, + PllM::Div10 => 0b1001, + PllM::Div11 => 0b1010, + PllM::Div12 => 0b1011, + PllM::Div13 => 0b1100, + PllM::Div14 => 0b1101, + PllM::Div15 => 0b1110, + PllM::Div16 => 0b1111, + } + } +} + +/// PLL Configuration +/// +/// Use this struct to configure the PLL source, input frequency, multiplication factor, and output +/// dividers. Be sure to keep check the datasheet for your specific part for the appropriate +/// frequency ranges for each of these settings. +pub struct Pll { + /// PLL Source clock selection. + pub source: PllSrc, + + /// PLL pre-divider + pub prediv_m: PllM, + + /// PLL multiplication factor for VCO + pub mul_n: PllN, + + /// PLL division factor for P clock (ADC Clock) + pub div_p: Option, + + /// PLL division factor for Q clock (USB, I2S23, SAI1, FDCAN, QSPI) + pub div_q: Option, + + /// PLL division factor for R clock (SYSCLK) + pub div_r: Option, +} + +impl AHBPrescaler { + const fn div(self) -> u32 { + match self { + AHBPrescaler::NotDivided => 1, + AHBPrescaler::Div2 => 2, + AHBPrescaler::Div4 => 4, + AHBPrescaler::Div8 => 8, + AHBPrescaler::Div16 => 16, + AHBPrescaler::Div64 => 64, + AHBPrescaler::Div128 => 128, + AHBPrescaler::Div256 => 256, + AHBPrescaler::Div512 => 512, + } + } +} + +impl APBPrescaler { + const fn div(self) -> u32 { + match self { + APBPrescaler::NotDivided => 1, + APBPrescaler::Div2 => 2, + APBPrescaler::Div4 => 4, + APBPrescaler::Div8 => 8, + APBPrescaler::Div16 => 16, + } + } +} + +impl Into for APBPrescaler { + fn into(self) -> Ppre { + match self { + APBPrescaler::NotDivided => Ppre::DIV1, + APBPrescaler::Div2 => Ppre::DIV2, + APBPrescaler::Div4 => Ppre::DIV4, + APBPrescaler::Div8 => Ppre::DIV8, + APBPrescaler::Div16 => Ppre::DIV16, + } + } +} + +impl Into for AHBPrescaler { + fn into(self) -> Hpre { + match self { + AHBPrescaler::NotDivided => Hpre::DIV1, + AHBPrescaler::Div2 => Hpre::DIV2, + AHBPrescaler::Div4 => Hpre::DIV4, + AHBPrescaler::Div8 => Hpre::DIV8, + AHBPrescaler::Div16 => Hpre::DIV16, + AHBPrescaler::Div64 => Hpre::DIV64, + AHBPrescaler::Div128 => Hpre::DIV128, + AHBPrescaler::Div256 => Hpre::DIV256, + AHBPrescaler::Div512 => Hpre::DIV512, + } + } +} + +/// Sets the source for the 48MHz clock to the USB and RNG peripherals. +pub enum Clock48MhzSrc { + /// Use the High Speed Internal Oscillator. For USB usage, the CRS must be used to calibrate the + /// oscillator to comply with the USB specification for oscillator tolerance. + Hsi48(Option), + /// Use the PLLQ output. The PLL must be configured to output a 48MHz clock. For USB usage the + /// PLL needs to be using the HSE source to comply with the USB specification for oscillator + /// tolerance. + PllQ, +} + +/// Sets the sync source for the Clock Recovery System (CRS). +pub enum CrsSyncSource { + /// Use an external GPIO to sync the CRS. + Gpio, + /// Use the Low Speed External oscillator to sync the CRS. + Lse, + /// Use the USB SOF to sync the CRS. + Usb, } /// Clocks configutation @@ -74,6 +345,17 @@ pub struct Config { pub apb1_pre: APBPrescaler, pub apb2_pre: APBPrescaler, pub low_power_run: bool, + /// Iff PLL is requested as the main clock source in the `mux` field then the PLL configuration + /// MUST turn on the PLLR output. + pub pll: Option, + /// Sets the clock source for the 48MHz clock used by the USB and RNG peripherals. + pub clock_48mhz_src: Option, +} + +/// Configuration for the Clock Recovery System (CRS) used to trim the HSI48 oscillator. +pub struct CrsConfig { + /// Sync source for the CRS. + pub sync_src: CrsSyncSource, } impl Default for Config { @@ -85,30 +367,141 @@ impl Default for Config { apb1_pre: APBPrescaler::NotDivided, apb2_pre: APBPrescaler::NotDivided, low_power_run: false, + pll: None, + clock_48mhz_src: None, } } } +pub struct PllFreq { + pub pll_p: Option, + pub pll_q: Option, + pub pll_r: Option, +} + pub(crate) unsafe fn init(config: Config) { + let pll_freq = config.pll.map(|pll_config| { + let src_freq = match pll_config.source { + PllSrc::HSI16 => { + RCC.cr().write(|w| w.set_hsion(true)); + while !RCC.cr().read().hsirdy() {} + + HSI_FREQ.0 + } + PllSrc::HSE(freq) => { + RCC.cr().write(|w| w.set_hseon(true)); + while !RCC.cr().read().hserdy() {} + freq.0 + } + }; + + // Disable PLL before configuration + RCC.cr().modify(|w| w.set_pllon(false)); + while RCC.cr().read().pllrdy() {} + + let internal_freq = src_freq / pll_config.prediv_m.to_div() * pll_config.mul_n.to_mul(); + + RCC.pllcfgr().write(|w| { + w.set_plln(pll_config.mul_n.into()); + w.set_pllm(pll_config.prediv_m.into()); + w.set_pllsrc(pll_config.source.into()); + }); + + let pll_p_freq = pll_config.div_p.map(|div_p| { + RCC.pllcfgr().modify(|w| { + w.set_pllpdiv(div_p.into()); + w.set_pllpen(true); + }); + Hertz(internal_freq / div_p.to_div()) + }); + + let pll_q_freq = pll_config.div_q.map(|div_q| { + RCC.pllcfgr().modify(|w| { + w.set_pllq(div_q.into()); + w.set_pllqen(true); + }); + Hertz(internal_freq / div_q.to_div()) + }); + + let pll_r_freq = pll_config.div_r.map(|div_r| { + RCC.pllcfgr().modify(|w| { + w.set_pllr(div_r.into()); + w.set_pllren(true); + }); + Hertz(internal_freq / div_r.to_div()) + }); + + // Enable the PLL + RCC.cr().modify(|w| w.set_pllon(true)); + while !RCC.cr().read().pllrdy() {} + + PllFreq { + pll_p: pll_p_freq, + pll_q: pll_q_freq, + pll_r: pll_r_freq, + } + }); + let (sys_clk, sw) = match config.mux { ClockSrc::HSI16 => { // Enable HSI16 RCC.cr().write(|w| w.set_hsion(true)); while !RCC.cr().read().hsirdy() {} - (HSI_FREQ.0, 0x01) + (HSI_FREQ.0, Sw::HSI16) } ClockSrc::HSE(freq) => { // Enable HSE RCC.cr().write(|w| w.set_hseon(true)); while !RCC.cr().read().hserdy() {} - (freq.0, 0x02) + (freq.0, Sw::HSE) + } + ClockSrc::PLL => { + assert!(pll_freq.is_some()); + assert!(pll_freq.as_ref().unwrap().pll_r.is_some()); + + let freq = pll_freq.as_ref().unwrap().pll_r.unwrap().0; + + assert!(freq <= 170_000_000); + + if freq >= 150_000_000 { + // Enable Core Boost mode on freq >= 150Mhz ([RM0440] p234) + PWR.cr5().modify(|w| w.set_r1mode(false)); + // Set flash wait state in boost mode based on frequency ([RM0440] p191) + if freq <= 36_000_000 { + FLASH.acr().modify(|w| w.set_latency(Latency::WS0)); + } else if freq <= 68_000_000 { + FLASH.acr().modify(|w| w.set_latency(Latency::WS1)); + } else if freq <= 102_000_000 { + FLASH.acr().modify(|w| w.set_latency(Latency::WS2)); + } else if freq <= 136_000_000 { + FLASH.acr().modify(|w| w.set_latency(Latency::WS3)); + } else { + FLASH.acr().modify(|w| w.set_latency(Latency::WS4)); + } + } else { + PWR.cr5().modify(|w| w.set_r1mode(true)); + // Set flash wait state in normal mode based on frequency ([RM0440] p191) + if freq <= 30_000_000 { + FLASH.acr().modify(|w| w.set_latency(Latency::WS0)); + } else if freq <= 60_000_000 { + FLASH.acr().modify(|w| w.set_latency(Latency::WS1)); + } else if freq <= 80_000_000 { + FLASH.acr().modify(|w| w.set_latency(Latency::WS2)); + } else if freq <= 120_000_000 { + FLASH.acr().modify(|w| w.set_latency(Latency::WS3)); + } else { + FLASH.acr().modify(|w| w.set_latency(Latency::WS4)); + } + } + + (freq, Sw::PLLRCLK) } }; RCC.cfgr().modify(|w| { - w.set_sw(sw.into()); + w.set_sw(sw); w.set_hpre(config.ahb_pre.into()); w.set_ppre1(config.apb1_pre.into()); w.set_ppre2(config.apb2_pre.into()); @@ -116,19 +509,13 @@ pub(crate) unsafe fn init(config: Config) { let ahb_freq: u32 = match config.ahb_pre { AHBPrescaler::NotDivided => sys_clk, - pre => { - let pre: u8 = pre.into(); - let pre = 1 << (pre as u32 - 7); - sys_clk / pre - } + pre => sys_clk / pre.div(), }; let (apb1_freq, apb1_tim_freq) = match config.apb1_pre { APBPrescaler::NotDivided => (ahb_freq, ahb_freq), pre => { - let pre: u8 = pre.into(); - let pre: u8 = 1 << (pre - 3); - let freq = ahb_freq / pre as u32; + let freq = ahb_freq / pre.div(); (freq, freq * 2) } }; @@ -136,13 +523,55 @@ pub(crate) unsafe fn init(config: Config) { let (apb2_freq, apb2_tim_freq) = match config.apb2_pre { APBPrescaler::NotDivided => (ahb_freq, ahb_freq), pre => { - let pre: u8 = pre.into(); - let pre: u8 = 1 << (pre - 3); - let freq = ahb_freq / pre as u32; + let freq = ahb_freq / pre.div(); (freq, freq * 2) } }; + // Setup the 48 MHz clock if needed + if let Some(clock_48mhz_src) = config.clock_48mhz_src { + let source = match clock_48mhz_src { + Clock48MhzSrc::PllQ => { + // Make sure the PLLQ is enabled and running at 48Mhz + let pllq_freq = pll_freq.as_ref().and_then(|f| f.pll_q); + assert!(pllq_freq.is_some() && pllq_freq.unwrap().0 == 48_000_000); + + crate::pac::rcc::vals::Clk48sel::PLLQCLK + } + Clock48MhzSrc::Hsi48(crs_config) => { + // Enable HSI48 + RCC.crrcr().modify(|w| w.set_hsi48on(true)); + // Wait for HSI48 to turn on + while RCC.crrcr().read().hsi48rdy() == false {} + + // Enable and setup CRS if needed + if let Some(crs_config) = crs_config { + crate::peripherals::CRS::enable(); + + let sync_src = match crs_config.sync_src { + CrsSyncSource::Gpio => crate::pac::crs::vals::Syncsrc::GPIO, + CrsSyncSource::Lse => crate::pac::crs::vals::Syncsrc::LSE, + CrsSyncSource::Usb => crate::pac::crs::vals::Syncsrc::USB, + }; + + crate::pac::CRS.cfgr().modify(|w| { + w.set_syncsrc(sync_src); + }); + + // These are the correct settings for standard USB operation. If other settings + // are needed there will need to be additional config options for the CRS. + crate::pac::CRS.cr().modify(|w| { + w.set_autotrimen(true); + w.set_cen(true); + }); + } + crate::pac::rcc::vals::Clk48sel::HSI48 + } + }; + + RCC.ccipr().modify(|w| w.set_clk48sel(source)); + } + if config.low_power_run { assert!(sys_clk <= 2_000_000); PWR.cr1().modify(|w| w.set_lpr(true)); diff --git a/embassy-stm32/src/rcc/h5.rs b/embassy-stm32/src/rcc/h5.rs new file mode 100644 index 000000000..7e2f75ab7 --- /dev/null +++ b/embassy-stm32/src/rcc/h5.rs @@ -0,0 +1,603 @@ +use core::marker::PhantomData; + +use stm32_metapac::rcc::vals::{Hpre, Ppre, Timpre}; + +use crate::pac::pwr::vals::Vos; +use crate::pac::rcc::vals::{Hseext, Hsidiv, Mco1, Mco2, Pllrge, Pllsrc, Pllvcosel, Sw}; +use crate::pac::{FLASH, PWR, RCC}; +use crate::rcc::{set_freqs, Clocks}; +use crate::time::Hertz; +use crate::{peripherals, Peripheral}; + +/// HSI speed +pub const HSI_FREQ: Hertz = Hertz(64_000_000); + +/// CSI speed +pub const CSI_FREQ: Hertz = Hertz(4_000_000); + +/// HSI48 speed +pub const HSI48_FREQ: Hertz = Hertz(48_000_000); + +/// LSI speed +pub const LSI_FREQ: Hertz = Hertz(32_000); + +const VCO_MIN: u32 = 150_000_000; +const VCO_MAX: u32 = 420_000_000; +const VCO_WIDE_MIN: u32 = 128_000_000; +const VCO_WIDE_MAX: u32 = 560_000_000; + +/// Voltage Scale +/// +/// Represents the voltage range feeding the CPU core. The maximum core +/// clock frequency depends on this value. +#[derive(Copy, Clone, PartialEq)] +pub enum VoltageScale { + /// VOS 0 range VCORE 1.30V - 1.40V + Scale0, + /// VOS 1 range VCORE 1.15V - 1.26V + Scale1, + /// VOS 2 range VCORE 1.05V - 1.15V + Scale2, + /// VOS 3 range VCORE 0.95V - 1.05V + Scale3, +} + +pub enum HseMode { + /// crystal/ceramic oscillator (HSEBYP=0) + Oscillator, + /// external analog clock (low swing) (HSEBYP=1, HSEEXT=0) + BypassAnalog, + /// external digital clock (full swing) (HSEBYP=1, HSEEXT=1) + BypassDigital, +} + +pub struct Hse { + /// HSE frequency. + pub freq: Hertz, + /// HSE mode. + pub mode: HseMode, +} + +pub enum Hsi { + /// 64Mhz + Mhz64, + /// 32Mhz (divided by 2) + Mhz32, + /// 16Mhz (divided by 4) + Mhz16, + /// 8Mhz (divided by 8) + Mhz8, +} + +pub enum Sysclk { + /// HSI selected as sysclk + HSI, + /// HSE selected as sysclk + HSE, + /// CSI selected as sysclk + CSI, + /// PLL1_P selected as sysclk + Pll1P, +} + +pub enum PllSource { + Hsi, + Csi, + Hse, +} + +pub struct Pll { + /// Source clock selection. + pub source: PllSource, + + /// PLL pre-divider (DIVM). Must be between 1 and 63. + pub prediv: u8, + + /// PLL multiplication factor. Must be between 4 and 512. + pub mul: u16, + + /// PLL P division factor. If None, PLL P output is disabled. Must be between 1 and 128. + /// On PLL1, it must be even (in particular, it cannot be 1.) + pub divp: Option, + /// PLL Q division factor. If None, PLL Q output is disabled. Must be between 1 and 128. + pub divq: Option, + /// PLL R division factor. If None, PLL R output is disabled. Must be between 1 and 128. + pub divr: Option, +} + +/// AHB prescaler +#[derive(Clone, Copy, PartialEq)] +pub enum AHBPrescaler { + NotDivided, + Div2, + Div4, + Div8, + Div16, + Div64, + Div128, + Div256, + Div512, +} + +impl AHBPrescaler { + fn div(&self, clk: Hertz) -> Hertz { + match self { + Self::NotDivided => clk, + Self::Div2 => clk / 2u32, + Self::Div4 => clk / 4u32, + Self::Div8 => clk / 8u32, + Self::Div16 => clk / 16u32, + Self::Div64 => clk / 64u32, + Self::Div128 => clk / 128u32, + Self::Div256 => clk / 256u32, + Self::Div512 => clk / 512u32, + } + } +} + +/// APB prescaler +#[derive(Clone, Copy)] +pub enum APBPrescaler { + NotDivided, + Div2, + Div4, + Div8, + Div16, +} + +impl APBPrescaler { + fn div(&self, clk: Hertz) -> Hertz { + match self { + Self::NotDivided => clk, + Self::Div2 => clk / 2u32, + Self::Div4 => clk / 4u32, + Self::Div8 => clk / 8u32, + Self::Div16 => clk / 16u32, + } + } + + fn div_tim(&self, clk: Hertz, tim: TimerPrescaler) -> Hertz { + match (tim, self) { + // The timers kernel clock is equal to rcc_hclk1 if PPRE1 or PPRE2 corresponds to a + // division by 1 or 2, else it is equal to 2 x Frcc_pclk1 or 2 x Frcc_pclk2 + (TimerPrescaler::DefaultX2, Self::NotDivided) => clk, + (TimerPrescaler::DefaultX2, Self::Div2) => clk, + (TimerPrescaler::DefaultX2, Self::Div4) => clk / 2u32, + (TimerPrescaler::DefaultX2, Self::Div8) => clk / 4u32, + (TimerPrescaler::DefaultX2, Self::Div16) => clk / 8u32, + // The timers kernel clock is equal to 2 x Frcc_pclk1 or 2 x Frcc_pclk2 if PPRE1 or PPRE2 + // corresponds to a division by 1, 2 or 4, else it is equal to 4 x Frcc_pclk1 or 4 x Frcc_pclk2 + // this makes NO SENSE and is different than in the H7. Mistake in the RM?? + (TimerPrescaler::DefaultX4, Self::NotDivided) => clk * 2u32, + (TimerPrescaler::DefaultX4, Self::Div2) => clk, + (TimerPrescaler::DefaultX4, Self::Div4) => clk / 2u32, + (TimerPrescaler::DefaultX4, Self::Div8) => clk / 2u32, + (TimerPrescaler::DefaultX4, Self::Div16) => clk / 4u32, + } + } +} + +/// APB prescaler +#[derive(Clone, Copy)] +pub enum TimerPrescaler { + DefaultX2, + DefaultX4, +} + +impl From for Timpre { + fn from(value: TimerPrescaler) -> Self { + match value { + TimerPrescaler::DefaultX2 => Timpre::DEFAULTX2, + TimerPrescaler::DefaultX4 => Timpre::DEFAULTX4, + } + } +} + +impl From for Ppre { + fn from(val: APBPrescaler) -> Ppre { + match val { + APBPrescaler::NotDivided => Ppre::DIV1, + APBPrescaler::Div2 => Ppre::DIV2, + APBPrescaler::Div4 => Ppre::DIV4, + APBPrescaler::Div8 => Ppre::DIV8, + APBPrescaler::Div16 => Ppre::DIV16, + } + } +} + +impl From for Hpre { + fn from(val: AHBPrescaler) -> Hpre { + match val { + AHBPrescaler::NotDivided => Hpre::DIV1, + AHBPrescaler::Div2 => Hpre::DIV2, + AHBPrescaler::Div4 => Hpre::DIV4, + AHBPrescaler::Div8 => Hpre::DIV8, + AHBPrescaler::Div16 => Hpre::DIV16, + AHBPrescaler::Div64 => Hpre::DIV64, + AHBPrescaler::Div128 => Hpre::DIV128, + AHBPrescaler::Div256 => Hpre::DIV256, + AHBPrescaler::Div512 => Hpre::DIV512, + } + } +} + +/// Configuration of the core clocks +#[non_exhaustive] +pub struct Config { + pub hsi: Option, + pub hse: Option, + pub csi: bool, + pub hsi48: bool, + pub sys: Sysclk, + + pub pll1: Option, + pub pll2: Option, + #[cfg(rcc_h5)] + pub pll3: Option, + + pub ahb_pre: AHBPrescaler, + pub apb1_pre: APBPrescaler, + pub apb2_pre: APBPrescaler, + pub apb3_pre: APBPrescaler, + pub timer_prescaler: TimerPrescaler, + + pub voltage_scale: VoltageScale, +} + +impl Default for Config { + fn default() -> Self { + Self { + hsi: Some(Hsi::Mhz64), + hse: None, + csi: false, + hsi48: false, + sys: Sysclk::HSI, + pll1: None, + pll2: None, + #[cfg(rcc_h5)] + pll3: None, + + ahb_pre: AHBPrescaler::NotDivided, + apb1_pre: APBPrescaler::NotDivided, + apb2_pre: APBPrescaler::NotDivided, + apb3_pre: APBPrescaler::NotDivided, + timer_prescaler: TimerPrescaler::DefaultX2, + + voltage_scale: VoltageScale::Scale3, + } + } +} + +pub(crate) mod sealed { + pub trait McoInstance { + type Source; + unsafe fn apply_clock_settings(source: Self::Source, prescaler: u8); + } +} + +pub trait McoInstance: sealed::McoInstance + 'static {} + +pin_trait!(McoPin, McoInstance); + +macro_rules! impl_peri { + ($peri:ident, $source:ident, $set_source:ident, $set_prescaler:ident) => { + impl sealed::McoInstance for peripherals::$peri { + type Source = $source; + + unsafe fn apply_clock_settings(source: Self::Source, prescaler: u8) { + RCC.cfgr().modify(|w| { + w.$set_source(source); + w.$set_prescaler(prescaler); + }); + } + } + + impl McoInstance for peripherals::$peri {} + }; +} + +impl_peri!(MCO1, Mco1, set_mco1, set_mco1pre); +impl_peri!(MCO2, Mco2, set_mco2, set_mco2pre); + +pub struct Mco<'d, T: McoInstance> { + phantom: PhantomData<&'d mut T>, +} + +impl<'d, T: McoInstance> Mco<'d, T> { + pub fn new( + _peri: impl Peripheral

+ 'd, + _pin: impl Peripheral

> + 'd, + _source: T::Source, + ) -> Self { + todo!(); + } +} + +pub(crate) unsafe fn init(config: Config) { + let (vos, max_clk) = match config.voltage_scale { + VoltageScale::Scale0 => (Vos::SCALE0, Hertz(250_000_000)), + VoltageScale::Scale1 => (Vos::SCALE1, Hertz(200_000_000)), + VoltageScale::Scale2 => (Vos::SCALE2, Hertz(150_000_000)), + VoltageScale::Scale3 => (Vos::SCALE3, Hertz(100_000_000)), + }; + + // Configure voltage scale. + PWR.voscr().modify(|w| w.set_vos(vos)); + while !PWR.vossr().read().vosrdy() {} + + // Configure HSI + let hsi = match config.hsi { + None => { + RCC.cr().modify(|w| w.set_hsion(false)); + None + } + Some(hsi) => { + let (freq, hsidiv) = match hsi { + Hsi::Mhz64 => (HSI_FREQ / 1u32, Hsidiv::DIV1), + Hsi::Mhz32 => (HSI_FREQ / 2u32, Hsidiv::DIV2), + Hsi::Mhz16 => (HSI_FREQ / 4u32, Hsidiv::DIV4), + Hsi::Mhz8 => (HSI_FREQ / 8u32, Hsidiv::DIV8), + }; + RCC.cr().modify(|w| { + w.set_hsidiv(hsidiv); + w.set_hsion(true); + }); + while !RCC.cr().read().hsirdy() {} + Some(freq) + } + }; + + // Configure HSE + let hse = match config.hse { + None => { + RCC.cr().modify(|w| w.set_hseon(false)); + None + } + Some(hse) => { + let (byp, ext) = match hse.mode { + HseMode::Oscillator => (false, Hseext::ANALOG), + HseMode::BypassAnalog => (true, Hseext::ANALOG), + HseMode::BypassDigital => (true, Hseext::DIGITAL), + }; + + RCC.cr().modify(|w| { + w.set_hsebyp(byp); + w.set_hseext(ext); + }); + RCC.cr().modify(|w| w.set_hseon(true)); + while !RCC.cr().read().hserdy() {} + Some(hse.freq) + } + }; + + // Configure HSI48. + RCC.cr().modify(|w| w.set_hsi48on(config.hsi48)); + let _hsi48 = match config.hsi48 { + false => None, + true => { + while !RCC.cr().read().hsi48rdy() {} + Some(CSI_FREQ) + } + }; + + // Configure CSI. + RCC.cr().modify(|w| w.set_csion(config.csi)); + let csi = match config.csi { + false => None, + true => { + while !RCC.cr().read().csirdy() {} + Some(CSI_FREQ) + } + }; + + // Configure PLLs. + let pll_input = PllInput { csi, hse, hsi }; + let pll1 = init_pll(0, config.pll1, &pll_input); + let _pll2 = init_pll(1, config.pll2, &pll_input); + #[cfg(rcc_h5)] + let _pll3 = init_pll(2, config.pll3, &pll_input); + + // Configure sysclk + let (sys, sw) = match config.sys { + Sysclk::HSI => (unwrap!(hsi), Sw::HSI), + Sysclk::HSE => (unwrap!(hse), Sw::HSE), + Sysclk::CSI => (unwrap!(csi), Sw::CSI), + Sysclk::Pll1P => (unwrap!(pll1.p), Sw::PLL1), + }; + assert!(sys <= max_clk); + + let hclk = config.ahb_pre.div(sys); + + let apb1 = config.apb1_pre.div(hclk); + let apb1_tim = config.apb1_pre.div_tim(hclk, config.timer_prescaler); + let apb2 = config.apb2_pre.div(hclk); + let apb2_tim = config.apb2_pre.div_tim(hclk, config.timer_prescaler); + let apb3 = config.apb3_pre.div(hclk); + + flash_setup(hclk, config.voltage_scale); + + // Set hpre + let hpre = config.ahb_pre.into(); + RCC.cfgr2().modify(|w| w.set_hpre(hpre)); + while RCC.cfgr2().read().hpre() != hpre {} + + // set ppre + RCC.cfgr2().modify(|w| { + w.set_ppre1(config.apb1_pre.into()); + w.set_ppre2(config.apb2_pre.into()); + w.set_ppre3(config.apb3_pre.into()); + }); + + RCC.cfgr().modify(|w| w.set_timpre(config.timer_prescaler.into())); + + RCC.cfgr().modify(|w| w.set_sw(sw)); + while RCC.cfgr().read().sws() != sw {} + + set_freqs(Clocks { + sys, + ahb1: hclk, + ahb2: hclk, + ahb3: hclk, + ahb4: hclk, + apb1, + apb2, + apb3, + apb1_tim, + apb2_tim, + adc: None, + }); +} + +struct PllInput { + hsi: Option, + hse: Option, + csi: Option, +} + +struct PllOutput { + p: Option, + #[allow(dead_code)] + q: Option, + #[allow(dead_code)] + r: Option, +} + +fn init_pll(num: usize, config: Option, input: &PllInput) -> PllOutput { + let Some(config) = config else { + // Stop PLL + RCC.cr().modify(|w| w.set_pllon(num, false)); + while RCC.cr().read().pllrdy(num) {} + + // "To save power when PLL1 is not used, the value of PLL1M must be set to 0."" + RCC.pllcfgr(num).write(|w| { + w.set_divm(0); + }); + + return PllOutput { + p: None, + q: None, + r: None, + }; + }; + + assert!(1 <= config.prediv && config.prediv <= 63); + assert!(4 <= config.mul && config.mul <= 512); + + let (in_clk, src) = match config.source { + PllSource::Hsi => (unwrap!(input.hsi), Pllsrc::HSI), + PllSource::Hse => (unwrap!(input.hse), Pllsrc::HSE), + PllSource::Csi => (unwrap!(input.csi), Pllsrc::CSI), + }; + + let ref_clk = in_clk / config.prediv as u32; + + let ref_range = match ref_clk.0 { + ..=1_999_999 => Pllrge::RANGE1, + ..=3_999_999 => Pllrge::RANGE2, + ..=7_999_999 => Pllrge::RANGE4, + ..=16_000_000 => Pllrge::RANGE8, + x => panic!("pll ref_clk out of range: {} mhz", x), + }; + + // The smaller range (150 to 420 MHz) must + // be chosen when the reference clock frequency is lower than 2 MHz. + let wide_allowed = ref_range != Pllrge::RANGE1; + + let vco_clk = ref_clk * config.mul; + let vco_range = match vco_clk.0 { + VCO_MIN..=VCO_MAX => Pllvcosel::MEDIUMVCO, + VCO_WIDE_MIN..=VCO_WIDE_MAX if wide_allowed => Pllvcosel::WIDEVCO, + x => panic!("pll vco_clk out of range: {} mhz", x), + }; + + let p = config.divp.map(|div| { + assert!(1 <= div && div <= 128); + if num == 0 { + // on PLL1, DIVP must be even. + assert!(div % 2 == 0); + } + + vco_clk / div + }); + let q = config.divq.map(|div| { + assert!(1 <= div && div <= 128); + vco_clk / div + }); + let r = config.divr.map(|div| { + assert!(1 <= div && div <= 128); + vco_clk / div + }); + + RCC.pllcfgr(num).write(|w| { + w.set_pllsrc(src); + w.set_divm(config.prediv); + w.set_pllvcosel(vco_range); + w.set_pllrge(ref_range); + w.set_pllfracen(false); + w.set_pllpen(p.is_some()); + w.set_pllqen(q.is_some()); + w.set_pllren(r.is_some()); + }); + RCC.plldivr(num).write(|w| { + w.set_plln(config.mul - 1); + w.set_pllp((config.divp.unwrap_or(1) - 1) as u8); + w.set_pllq((config.divq.unwrap_or(1) - 1) as u8); + w.set_pllr((config.divr.unwrap_or(1) - 1) as u8); + }); + + RCC.cr().modify(|w| w.set_pllon(num, true)); + while !RCC.cr().read().pllrdy(num) {} + + PllOutput { p, q, r } +} + +fn flash_setup(clk: Hertz, vos: VoltageScale) { + // RM0481 Rev 1, table 37 + // LATENCY WRHIGHFREQ VOS3 VOS2 VOS1 VOS0 + // 0 0 0 to 20 MHz 0 to 30 MHz 0 to 34 MHz 0 to 42 MHz + // 1 0 20 to 40 MHz 30 to 60 MHz 34 to 68 MHz 42 to 84 MHz + // 2 1 40 to 60 MHz 60 to 90 MHz 68 to 102 MHz 84 to 126 MHz + // 3 1 60 to 80 MHz 90 to 120 MHz 102 to 136 MHz 126 to 168 MHz + // 4 2 80 to 100 MHz 120 to 150 MHz 136 to 170 MHz 168 to 210 MHz + // 5 2 170 to 200 MHz 210 to 250 MHz + + // See RM0433 Rev 7 Table 17. FLASH recommended number of wait + // states and programming delay + let (latency, wrhighfreq) = match (vos, clk.0) { + (VoltageScale::Scale0, ..=42_000_000) => (0, 0), + (VoltageScale::Scale0, ..=84_000_000) => (1, 0), + (VoltageScale::Scale0, ..=126_000_000) => (2, 1), + (VoltageScale::Scale0, ..=168_000_000) => (3, 1), + (VoltageScale::Scale0, ..=210_000_000) => (4, 2), + (VoltageScale::Scale0, ..=250_000_000) => (5, 2), + + (VoltageScale::Scale1, ..=34_000_000) => (0, 0), + (VoltageScale::Scale1, ..=68_000_000) => (1, 0), + (VoltageScale::Scale1, ..=102_000_000) => (2, 1), + (VoltageScale::Scale1, ..=136_000_000) => (3, 1), + (VoltageScale::Scale1, ..=170_000_000) => (4, 2), + (VoltageScale::Scale1, ..=200_000_000) => (5, 2), + + (VoltageScale::Scale2, ..=30_000_000) => (0, 0), + (VoltageScale::Scale2, ..=60_000_000) => (1, 0), + (VoltageScale::Scale2, ..=90_000_000) => (2, 1), + (VoltageScale::Scale2, ..=120_000_000) => (3, 1), + (VoltageScale::Scale2, ..=150_000_000) => (4, 2), + + (VoltageScale::Scale3, ..=20_000_000) => (0, 0), + (VoltageScale::Scale3, ..=40_000_000) => (1, 0), + (VoltageScale::Scale3, ..=60_000_000) => (2, 1), + (VoltageScale::Scale3, ..=80_000_000) => (3, 1), + (VoltageScale::Scale3, ..=100_000_000) => (4, 2), + + _ => unreachable!(), + }; + + defmt::debug!("flash: latency={} wrhighfreq={}", latency, wrhighfreq); + + FLASH.acr().write(|w| { + w.set_wrhighfreq(wrhighfreq); + w.set_latency(latency); + }); + while FLASH.acr().read().latency() != latency {} +} diff --git a/embassy-stm32/src/rcc/h7.rs b/embassy-stm32/src/rcc/h7.rs index 0185f7ae8..7e5cd0d1a 100644 --- a/embassy-stm32/src/rcc/h7.rs +++ b/embassy-stm32/src/rcc/h7.rs @@ -253,14 +253,11 @@ fn flash_setup(rcc_aclk: u32, vos: VoltageScale) { }, }; - // NOTE(unsafe) Atomic write - unsafe { - FLASH.acr().write(|w| { - w.set_wrhighfreq(progr_delay); - w.set_latency(wait_states) - }); - while FLASH.acr().read().latency() != wait_states {} - } + FLASH.acr().write(|w| { + w.set_wrhighfreq(progr_delay); + w.set_latency(wait_states) + }); + while FLASH.acr().read().latency() != wait_states {} } pub enum McoClock { @@ -474,7 +471,6 @@ pub(crate) unsafe fn init(mut config: Config) { // Configure traceclk from PLL if needed traceclk_setup(&mut config, sys_use_pll1_p); - // NOTE(unsafe) We have exclusive access to the RCC let (pll1_p_ck, pll1_q_ck, pll1_r_ck) = pll::pll_setup(srcclk.0, &config.pll1, 0); let (pll2_p_ck, pll2_q_ck, pll2_r_ck) = pll::pll_setup(srcclk.0, &config.pll2, 1); let (pll3_p_ck, pll3_q_ck, pll3_r_ck) = pll::pll_setup(srcclk.0, &config.pll3, 2); @@ -605,22 +601,22 @@ pub(crate) unsafe fn init(mut config: Config) { // Core Prescaler / AHB Prescaler / APB3 Prescaler RCC.d1cfgr().modify(|w| { - w.set_d1cpre(Hpre(d1cpre_bits)); - w.set_d1ppre(Dppre(ppre3_bits)); + w.set_d1cpre(Hpre::from_bits(d1cpre_bits)); + w.set_d1ppre(Dppre::from_bits(ppre3_bits)); w.set_hpre(hpre_bits) }); // Ensure core prescaler value is valid before future lower // core voltage - while RCC.d1cfgr().read().d1cpre().0 != d1cpre_bits {} + while RCC.d1cfgr().read().d1cpre().to_bits() != d1cpre_bits {} // APB1 / APB2 Prescaler RCC.d2cfgr().modify(|w| { - w.set_d2ppre1(Dppre(ppre1_bits)); - w.set_d2ppre2(Dppre(ppre2_bits)); + w.set_d2ppre1(Dppre::from_bits(ppre1_bits)); + w.set_d2ppre2(Dppre::from_bits(ppre2_bits)); }); // APB4 Prescaler - RCC.d3cfgr().modify(|w| w.set_d3ppre(Dppre(ppre4_bits))); + RCC.d3cfgr().modify(|w| w.set_d3ppre(Dppre::from_bits(ppre4_bits))); // Peripheral Clock (per_ck) RCC.d1ccipr().modify(|w| w.set_ckpersel(ckpersel)); @@ -644,7 +640,7 @@ pub(crate) unsafe fn init(mut config: Config) { _ => Sw::HSI, }; RCC.cfgr().modify(|w| w.set_sw(sw)); - while RCC.cfgr().read().sws() != sw.0 {} + while RCC.cfgr().read().sws().to_bits() != sw.to_bits() {} // IO compensation cell - Requires CSI clock and SYSCFG assert!(RCC.cr().read().csirdy()); @@ -744,7 +740,7 @@ mod pll { } }; - let vco_ck = output + pll_x_p; + let vco_ck = output * pll_x_p; assert!(pll_x_p < 128); assert!(vco_ck >= VCO_MIN); @@ -756,7 +752,7 @@ mod pll { /// # Safety /// /// Must have exclusive access to the RCC register block - unsafe fn vco_setup(pll_src: u32, requested_output: u32, plln: usize) -> PllConfigResults { + fn vco_setup(pll_src: u32, requested_output: u32, plln: usize) -> PllConfigResults { use crate::pac::rcc::vals::{Pllrge, Pllvcosel}; let (vco_ck_target, pll_x_p) = vco_output_divider_setup(requested_output, plln); @@ -785,11 +781,7 @@ mod pll { /// # Safety /// /// Must have exclusive access to the RCC register block - pub(super) unsafe fn pll_setup( - pll_src: u32, - config: &PllConfig, - plln: usize, - ) -> (Option, Option, Option) { + pub(super) fn pll_setup(pll_src: u32, config: &PllConfig, plln: usize) -> (Option, Option, Option) { use crate::pac::rcc::vals::Divp; match config.p_ck { @@ -814,7 +806,8 @@ mod pll { RCC.pllcfgr().modify(|w| w.set_pllfracen(plln, false)); let vco_ck = ref_x_ck * pll_x_n; - RCC.plldivr(plln).modify(|w| w.set_divp1(Divp((pll_x_p - 1) as u8))); + RCC.plldivr(plln) + .modify(|w| w.set_divp1(Divp::from_bits((pll_x_p - 1) as u8))); RCC.pllcfgr().modify(|w| w.set_divpen(plln, true)); // Calulate additional output dividers diff --git a/embassy-stm32/src/rcc/l0.rs b/embassy-stm32/src/rcc/l0.rs index c3d29f164..46a528e31 100644 --- a/embassy-stm32/src/rcc/l0.rs +++ b/embassy-stm32/src/rcc/l0.rs @@ -1,7 +1,7 @@ use crate::pac::rcc::vals::{Hpre, Msirange, Plldiv, Pllmul, Pllsrc, Ppre, Sw}; use crate::pac::RCC; #[cfg(crs)] -use crate::pac::{CRS, SYSCFG}; +use crate::pac::{crs, CRS, SYSCFG}; use crate::rcc::{set_freqs, Clocks}; use crate::time::Hertz; @@ -293,7 +293,7 @@ pub(crate) unsafe fn init(config: Config) { AHBPrescaler::NotDivided => sys_clk, pre => { let pre: Hpre = pre.into(); - let pre = 1 << (pre.0 as u32 - 7); + let pre = 1 << (pre.to_bits() as u32 - 7); sys_clk / pre } }; @@ -302,7 +302,7 @@ pub(crate) unsafe fn init(config: Config) { APBPrescaler::NotDivided => (ahb_freq, ahb_freq), pre => { let pre: Ppre = pre.into(); - let pre: u8 = 1 << (pre.0 - 3); + let pre: u8 = 1 << (pre.to_bits() - 3); let freq = ahb_freq / pre as u32; (freq, freq * 2) } @@ -312,8 +312,8 @@ pub(crate) unsafe fn init(config: Config) { APBPrescaler::NotDivided => (ahb_freq, ahb_freq), pre => { let pre: Ppre = pre.into(); - let pre: u8 = 1 << (pre.0 - 3); - let freq = ahb_freq / (1 << (pre as u8 - 3)); + let pre: u8 = 1 << (pre.to_bits() - 3); + let freq = ahb_freq / pre as u32; (freq, freq * 2) } }; @@ -338,7 +338,7 @@ pub(crate) unsafe fn init(config: Config) { CRS.cfgr().write(|w| // Select LSE as synchronization source - w.set_syncsrc(0b01)); + w.set_syncsrc(crs::vals::Syncsrc::LSE)); CRS.cr().modify(|w| { w.set_autotrimen(true); w.set_cen(true); diff --git a/embassy-stm32/src/rcc/l1.rs b/embassy-stm32/src/rcc/l1.rs index e0180b24f..59a6eac8f 100644 --- a/embassy-stm32/src/rcc/l1.rs +++ b/embassy-stm32/src/rcc/l1.rs @@ -294,7 +294,7 @@ pub(crate) unsafe fn init(config: Config) { AHBPrescaler::NotDivided => sys_clk, pre => { let pre: Hpre = pre.into(); - let pre = 1 << (pre.0 as u32 - 7); + let pre = 1 << (pre.to_bits() as u32 - 7); sys_clk / pre } }; @@ -303,7 +303,7 @@ pub(crate) unsafe fn init(config: Config) { APBPrescaler::NotDivided => (ahb_freq, ahb_freq), pre => { let pre: Ppre = pre.into(); - let pre: u8 = 1 << (pre.0 - 3); + let pre: u8 = 1 << (pre.to_bits() - 3); let freq = ahb_freq / pre as u32; (freq, freq * 2) } @@ -313,8 +313,8 @@ pub(crate) unsafe fn init(config: Config) { APBPrescaler::NotDivided => (ahb_freq, ahb_freq), pre => { let pre: Ppre = pre.into(); - let pre: u8 = 1 << (pre.0 - 3); - let freq = ahb_freq / (1 << (pre as u8 - 3)); + let pre: u8 = 1 << (pre.to_bits() - 3); + let freq = ahb_freq / pre as u32; (freq, freq * 2) } }; diff --git a/embassy-stm32/src/rcc/l4.rs b/embassy-stm32/src/rcc/l4.rs index c820018ac..8a9b4adbf 100644 --- a/embassy-stm32/src/rcc/l4.rs +++ b/embassy-stm32/src/rcc/l4.rs @@ -1,7 +1,16 @@ +use core::marker::PhantomData; + +use embassy_hal_common::into_ref; +use stm32_metapac::rcc::regs::Cfgr; +use stm32_metapac::rcc::vals::{Lsedrv, Mcopre, Mcosel}; + +use crate::gpio::sealed::AFType; +use crate::gpio::Speed; use crate::pac::rcc::vals::{Hpre, Msirange, Pllsrc, Ppre, Sw}; -use crate::pac::{FLASH, RCC}; +use crate::pac::{FLASH, PWR, RCC}; use crate::rcc::{set_freqs, Clocks}; use crate::time::Hertz; +use crate::{peripherals, Peripheral}; /// HSI speed pub const HSI_FREQ: Hertz = Hertz(16_000_000); @@ -281,6 +290,7 @@ pub struct Config { )>, #[cfg(not(any(stm32l471, stm32l475, stm32l476, stm32l486)))] pub hsi48: bool, + pub rtc_mux: RtcClockSource, } impl Default for Config { @@ -294,20 +304,203 @@ impl Default for Config { pllsai1: None, #[cfg(not(any(stm32l471, stm32l475, stm32l476, stm32l486)))] hsi48: false, + rtc_mux: RtcClockSource::LSI32, } } } +pub enum RtcClockSource { + LSE32, + LSI32, +} + +pub enum McoClock { + DIV1, + DIV2, + DIV4, + DIV8, + DIV16, +} + +impl McoClock { + fn into_raw(&self) -> Mcopre { + match self { + McoClock::DIV1 => Mcopre::DIV1, + McoClock::DIV2 => Mcopre::DIV2, + McoClock::DIV4 => Mcopre::DIV4, + McoClock::DIV8 => Mcopre::DIV8, + McoClock::DIV16 => Mcopre::DIV16, + } + } +} + +#[derive(Copy, Clone)] +pub enum Mco1Source { + Disabled, + Lse, + Lsi, + Hse, + Hsi16, + PllClk, + SysClk, + Msi, + #[cfg(not(any(stm32l471, stm32l475, stm32l476, stm32l486)))] + Hsi48, +} + +impl Default for Mco1Source { + fn default() -> Self { + Self::Hsi16 + } +} + +pub trait McoSource { + type Raw; + + fn into_raw(&self) -> Self::Raw; +} + +impl McoSource for Mco1Source { + type Raw = Mcosel; + fn into_raw(&self) -> Self::Raw { + match self { + Mco1Source::Disabled => Mcosel::NOCLOCK, + Mco1Source::Lse => Mcosel::LSE, + Mco1Source::Lsi => Mcosel::LSI, + Mco1Source::Hse => Mcosel::HSE, + Mco1Source::Hsi16 => Mcosel::HSI16, + Mco1Source::PllClk => Mcosel::PLL, + Mco1Source::SysClk => Mcosel::SYSCLK, + Mco1Source::Msi => Mcosel::MSI, + #[cfg(not(any(stm32l471, stm32l475, stm32l476, stm32l486)))] + Mco1Source::Hsi48 => Mcosel::HSI48, + } + } +} + +pub(crate) mod sealed { + use stm32_metapac::rcc::vals::Mcopre; + pub trait McoInstance { + type Source; + unsafe fn apply_clock_settings(source: Self::Source, prescaler: Mcopre); + } +} + +pub trait McoInstance: sealed::McoInstance + 'static {} + +pin_trait!(McoPin, McoInstance); + +impl sealed::McoInstance for peripherals::MCO { + type Source = Mcosel; + + unsafe fn apply_clock_settings(source: Self::Source, prescaler: Mcopre) { + RCC.cfgr().modify(|w| { + w.set_mcosel(source); + w.set_mcopre(prescaler); + }); + + match source { + Mcosel::HSI16 => { + RCC.cr().modify(|w| w.set_hsion(true)); + while !RCC.cr().read().hsirdy() {} + } + #[cfg(not(any(stm32l471, stm32l475, stm32l476, stm32l486)))] + Mcosel::HSI48 => { + RCC.crrcr().modify(|w| w.set_hsi48on(true)); + while !RCC.crrcr().read().hsi48rdy() {} + } + _ => {} + } + } +} + +impl McoInstance for peripherals::MCO {} + +pub struct Mco<'d, T: McoInstance> { + phantom: PhantomData<&'d mut T>, +} + +impl<'d, T: McoInstance> Mco<'d, T> { + pub fn new( + _peri: impl Peripheral

+ 'd, + pin: impl Peripheral

> + 'd, + source: impl McoSource, + prescaler: McoClock, + ) -> Self { + into_ref!(pin); + + critical_section::with(|_| unsafe { + T::apply_clock_settings(source.into_raw(), prescaler.into_raw()); + pin.set_as_af(pin.af_num(), AFType::OutputPushPull); + pin.set_speed(Speed::VeryHigh); + }); + + Self { phantom: PhantomData } + } +} + pub(crate) unsafe fn init(config: Config) { + // Switch to MSI to prevent problems with PLL configuration. + if !RCC.cr().read().msion() { + // Turn on MSI and configure it to 4MHz. + RCC.cr().modify(|w| { + w.set_msirgsel(true); // MSI Range is provided by MSIRANGE[3:0]. + w.set_msirange(MSIRange::default().into()); + w.set_msipllen(false); + w.set_msion(true) + }); + + // Wait until MSI is running + while !RCC.cr().read().msirdy() {} + } + if RCC.cfgr().read().sws() != Sw::MSI { + // Set MSI as a clock source, reset prescalers. + RCC.cfgr().write_value(Cfgr::default()); + // Wait for clock switch status bits to change. + while RCC.cfgr().read().sws() != Sw::MSI {} + } + + match config.rtc_mux { + RtcClockSource::LSE32 => { + // 1. Unlock the backup domain + PWR.cr1().modify(|w| w.set_dbp(true)); + + // 2. Setup the LSE + RCC.bdcr().modify(|w| { + // Enable LSE + w.set_lseon(true); + // Max drive strength + // TODO: should probably be settable + w.set_lsedrv(Lsedrv::HIGH); + }); + + // Wait until LSE is running + while !RCC.bdcr().read().lserdy() {} + } + RtcClockSource::LSI32 => { + // Turn on the internal 32 kHz LSI oscillator + RCC.csr().modify(|w| w.set_lsion(true)); + + // Wait until LSI is running + while !RCC.csr().read().lsirdy() {} + } + } + let (sys_clk, sw) = match config.mux { ClockSrc::MSI(range) => { // Enable MSI RCC.cr().write(|w| { let bits: Msirange = range.into(); w.set_msirange(bits); - w.set_msipllen(false); w.set_msirgsel(true); w.set_msion(true); + + if let RtcClockSource::LSE32 = config.rtc_mux { + // If LSE is enabled, enable calibration of MSI + w.set_msipllen(true); + } else { + w.set_msipllen(false); + } }); while !RCC.cr().read().msirdy() {} @@ -463,7 +656,7 @@ pub(crate) unsafe fn init(config: Config) { AHBPrescaler::NotDivided => sys_clk, pre => { let pre: Hpre = pre.into(); - let pre = 1 << (pre.0 as u32 - 7); + let pre = 1 << (pre.to_bits() as u32 - 7); sys_clk / pre } }; @@ -472,7 +665,7 @@ pub(crate) unsafe fn init(config: Config) { APBPrescaler::NotDivided => (ahb_freq, ahb_freq), pre => { let pre: Ppre = pre.into(); - let pre: u8 = 1 << (pre.0 - 3); + let pre: u8 = 1 << (pre.to_bits() - 3); let freq = ahb_freq / pre as u32; (freq, freq * 2) } @@ -482,12 +675,14 @@ pub(crate) unsafe fn init(config: Config) { APBPrescaler::NotDivided => (ahb_freq, ahb_freq), pre => { let pre: Ppre = pre.into(); - let pre: u8 = 1 << (pre.0 - 3); - let freq = ahb_freq / (1 << (pre as u8 - 3)); + let pre: u8 = 1 << (pre.to_bits() - 3); + let freq = ahb_freq / pre as u32; (freq, freq * 2) } }; + RCC.apb1enr1().modify(|w| w.set_pwren(true)); + set_freqs(Clocks { sys: Hertz(sys_clk), ahb1: Hertz(ahb_freq), diff --git a/embassy-stm32/src/rcc/l5.rs b/embassy-stm32/src/rcc/l5.rs index 81bf36be0..16da65d5e 100644 --- a/embassy-stm32/src/rcc/l5.rs +++ b/embassy-stm32/src/rcc/l5.rs @@ -461,7 +461,7 @@ pub(crate) unsafe fn init(config: Config) { AHBPrescaler::NotDivided => sys_clk, pre => { let pre: Hpre = pre.into(); - let pre = 1 << (pre.0 as u32 - 7); + let pre = 1 << (pre.to_bits() as u32 - 7); sys_clk / pre } }; @@ -470,7 +470,7 @@ pub(crate) unsafe fn init(config: Config) { APBPrescaler::NotDivided => (ahb_freq, ahb_freq), pre => { let pre: Ppre = pre.into(); - let pre: u8 = 1 << (pre.0 - 3); + let pre: u8 = 1 << (pre.to_bits() - 3); let freq = ahb_freq / pre as u32; (freq, freq * 2) } @@ -480,8 +480,8 @@ pub(crate) unsafe fn init(config: Config) { APBPrescaler::NotDivided => (ahb_freq, ahb_freq), pre => { let pre: Ppre = pre.into(); - let pre: u8 = 1 << (pre.0 - 3); - let freq = ahb_freq / (1 << (pre as u8 - 3)); + let pre: u8 = 1 << (pre.to_bits() - 3); + let freq = ahb_freq / pre as u32; (freq, freq * 2) } }; diff --git a/embassy-stm32/src/rcc/mod.rs b/embassy-stm32/src/rcc/mod.rs index 1b1180c03..886fc0b93 100644 --- a/embassy-stm32/src/rcc/mod.rs +++ b/embassy-stm32/src/rcc/mod.rs @@ -10,6 +10,7 @@ use crate::time::Hertz; #[cfg_attr(rcc_f3, path = "f3.rs")] #[cfg_attr(any(rcc_f4, rcc_f410), path = "f4.rs")] #[cfg_attr(rcc_f7, path = "f7.rs")] +#[cfg_attr(rcc_c0, path = "c0.rs")] #[cfg_attr(rcc_g0, path = "g0.rs")] #[cfg_attr(rcc_g4, path = "g4.rs")] #[cfg_attr(any(rcc_h7, rcc_h7ab), path = "h7.rs")] @@ -20,21 +21,23 @@ use crate::time::Hertz; #[cfg_attr(rcc_u5, path = "u5.rs")] #[cfg_attr(rcc_wb, path = "wb.rs")] #[cfg_attr(any(rcc_wl5, rcc_wle), path = "wl.rs")] +#[cfg_attr(any(rcc_h5, rcc_h50), path = "h5.rs")] mod _version; pub use _version::*; -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Clocks { pub sys: Hertz, // APB pub apb1: Hertz, pub apb1_tim: Hertz, - #[cfg(not(rcc_g0))] + #[cfg(not(any(rcc_c0, rcc_g0)))] pub apb2: Hertz, - #[cfg(not(rcc_g0))] + #[cfg(not(any(rcc_c0, rcc_g0)))] pub apb2_tim: Hertz, - #[cfg(any(rcc_wl5, rcc_wle, rcc_u5))] + #[cfg(any(rcc_wl5, rcc_wle, rcc_h5, rcc_h50, rcc_u5))] pub apb3: Hertz, #[cfg(any(rcc_h7, rcc_h7ab))] pub apb4: Hertz, @@ -42,23 +45,31 @@ pub struct Clocks { // AHB pub ahb1: Hertz, #[cfg(any( - rcc_l4, rcc_l5, rcc_f2, rcc_f4, rcc_f410, rcc_f7, rcc_h7, rcc_h7ab, rcc_g4, rcc_u5, rcc_wb, rcc_wl5, rcc_wle + rcc_l4, rcc_l5, rcc_f2, rcc_f4, rcc_f410, rcc_f7, rcc_h5, rcc_h50, rcc_h7, rcc_h7ab, rcc_g4, rcc_u5, rcc_wb, + rcc_wl5, rcc_wle ))] pub ahb2: Hertz, #[cfg(any( - rcc_l4, rcc_l5, rcc_f2, rcc_f4, rcc_f410, rcc_f7, rcc_h7, rcc_h7ab, rcc_u5, rcc_wb, rcc_wl5, rcc_wle + rcc_l4, rcc_l5, rcc_f2, rcc_f4, rcc_f410, rcc_f7, rcc_h5, rcc_h50, rcc_h7, rcc_h7ab, rcc_u5, rcc_wb, rcc_wl5, + rcc_wle ))] pub ahb3: Hertz, - #[cfg(any(rcc_h7, rcc_h7ab))] + #[cfg(any(rcc_h5, rcc_h50, rcc_h7, rcc_h7ab))] pub ahb4: Hertz, #[cfg(any(rcc_f2, rcc_f4, rcc_f410, rcc_f7))] pub pll48: Option, + #[cfg(all(rcc_f4, not(stm32f410)))] + pub plli2s: Option, + + #[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479))] + pub pllsai: Option, + #[cfg(stm32f1)] pub adc: Hertz, - #[cfg(any(rcc_h7, rcc_h7ab))] + #[cfg(any(rcc_h5, rcc_h50, rcc_h7, rcc_h7ab))] pub adc: Option, } @@ -71,12 +82,13 @@ static mut CLOCK_FREQS: MaybeUninit = MaybeUninit::uninit(); /// /// Safety: Sets a mutable global. pub(crate) unsafe fn set_freqs(freqs: Clocks) { - CLOCK_FREQS.as_mut_ptr().write(freqs); + debug!("rcc: {:?}", freqs); + CLOCK_FREQS = MaybeUninit::new(freqs); } /// Safety: Reads a mutable global. pub(crate) unsafe fn get_freqs() -> &'static Clocks { - &*CLOCK_FREQS.as_ptr() + CLOCK_FREQS.assume_init_ref() } #[cfg(feature = "unstable-pac")] diff --git a/embassy-stm32/src/rcc/u5.rs b/embassy-stm32/src/rcc/u5.rs index 960c45322..cfc07f069 100644 --- a/embassy-stm32/src/rcc/u5.rs +++ b/embassy-stm32/src/rcc/u5.rs @@ -126,7 +126,7 @@ pub enum PllM { impl Into for PllM { fn into(self) -> Pllm { - Pllm(self as u8) + Pllm::from_bits(self as u8) } } @@ -295,6 +295,7 @@ pub struct Config { pub apb1_pre: APBPrescaler, pub apb2_pre: APBPrescaler, pub apb3_pre: APBPrescaler, + pub hsi48: bool, } impl Default for Config { @@ -305,6 +306,7 @@ impl Default for Config { apb1_pre: Default::default(), apb2_pre: Default::default(), apb3_pre: Default::default(), + hsi48: false, } } } @@ -320,7 +322,6 @@ pub(crate) unsafe fn init(config: Config) { RCC.cr().write(|w| { w.set_msipllen(false); w.set_msison(true); - w.set_msison(true); }); while !RCC.cr().read().msisrdy() {} @@ -340,9 +341,20 @@ pub(crate) unsafe fn init(config: Config) { } ClockSrc::PLL1R(src, m, n, div) => { let freq = match src { - PllSrc::MSI(_) => MSIRange::default().into(), - PllSrc::HSE(hertz) => hertz.0, - PllSrc::HSI16 => HSI_FREQ.0, + PllSrc::MSI(_) => { + // TODO: enable MSI + MSIRange::default().into() + } + PllSrc::HSE(hertz) => { + // TODO: enable HSE + hertz.0 + } + PllSrc::HSI16 => { + RCC.cr().write(|w| w.set_hsion(true)); + while !RCC.cr().read().hsirdy() {} + + HSI_FREQ.0 + } }; // disable @@ -355,6 +367,7 @@ pub(crate) unsafe fn init(config: Config) { RCC.pll1cfgr().write(|w| { w.set_pllm(m.into()); w.set_pllsrc(src.into()); + w.set_pllren(true); }); RCC.pll1divr().modify(|w| { @@ -365,15 +378,16 @@ pub(crate) unsafe fn init(config: Config) { // Enable PLL RCC.cr().modify(|w| w.set_pllon(0, true)); while !RCC.cr().read().pllrdy(0) {} - RCC.pll1cfgr().modify(|w| w.set_pllren(true)); - - RCC.cr().write(|w| w.set_pllon(0, true)); - while !RCC.cr().read().pllrdy(0) {} pll_ck } }; + if config.hsi48 { + RCC.cr().modify(|w| w.set_hsi48on(true)); + while !RCC.cr().read().hsi48rdy() {} + } + // TODO make configurable let power_vos = VoltageScale::Range4; @@ -467,7 +481,7 @@ pub(crate) unsafe fn init(config: Config) { pre => { let pre: u8 = pre.into(); let pre: u8 = 1 << (pre - 3); - let freq = ahb_freq / (1 << (pre as u8 - 3)); + let freq = ahb_freq / pre as u32; (freq, freq * 2) } }; @@ -477,7 +491,7 @@ pub(crate) unsafe fn init(config: Config) { pre => { let pre: u8 = pre.into(); let pre: u8 = 1 << (pre - 3); - let freq = ahb_freq / (1 << (pre as u8 - 3)); + let freq = ahb_freq / pre as u32; (freq, freq * 2) } }; diff --git a/embassy-stm32/src/rcc/wb.rs b/embassy-stm32/src/rcc/wb.rs index f6a5feea6..e6123821a 100644 --- a/embassy-stm32/src/rcc/wb.rs +++ b/embassy-stm32/src/rcc/wb.rs @@ -64,7 +64,7 @@ impl Into for APBPrescaler { impl Into for AHBPrescaler { fn into(self) -> u8 { match self { - AHBPrescaler::NotDivided => 1, + AHBPrescaler::NotDivided => 0x0, AHBPrescaler::Div2 => 0x08, AHBPrescaler::Div3 => 0x01, AHBPrescaler::Div4 => 0x09, @@ -151,7 +151,7 @@ pub(crate) unsafe fn init(config: Config) { pre => { let pre: u8 = pre.into(); let pre: u8 = 1 << (pre - 3); - let freq = ahb_freq / (1 << (pre as u8 - 3)); + let freq = ahb_freq / pre as u32; (freq, freq * 2) } }; diff --git a/embassy-stm32/src/rcc/wl.rs b/embassy-stm32/src/rcc/wl.rs index 69c192c67..6b69bb1cb 100644 --- a/embassy-stm32/src/rcc/wl.rs +++ b/embassy-stm32/src/rcc/wl.rs @@ -1,4 +1,5 @@ -use crate::pac::{FLASH, RCC}; +use crate::pac::pwr::vals::Dbp; +use crate::pac::{FLASH, PWR, RCC}; use crate::rcc::{set_freqs, Clocks}; use crate::time::Hertz; @@ -158,7 +159,7 @@ impl Into for APBPrescaler { impl Into for AHBPrescaler { fn into(self) -> u8 { match self { - AHBPrescaler::NotDivided => 1, + AHBPrescaler::NotDivided => 0x0, AHBPrescaler::Div2 => 0x08, AHBPrescaler::Div3 => 0x01, AHBPrescaler::Div4 => 0x09, @@ -184,6 +185,8 @@ pub struct Config { pub apb1_pre: APBPrescaler, pub apb2_pre: APBPrescaler, pub enable_lsi: bool, + pub enable_rtc_apb: bool, + pub rtc_mux: RtcClockSource, } impl Default for Config { @@ -196,60 +199,32 @@ impl Default for Config { apb1_pre: APBPrescaler::NotDivided, apb2_pre: APBPrescaler::NotDivided, enable_lsi: false, + enable_rtc_apb: false, + rtc_mux: RtcClockSource::LSI32, } } } +pub enum RtcClockSource { + LSE32, + LSI32, +} + +#[repr(u8)] +pub enum Lsedrv { + Low = 0, + MediumLow = 1, + MediumHigh = 2, + High = 3, +} + pub(crate) unsafe fn init(config: Config) { let (sys_clk, sw, vos) = match config.mux { - ClockSrc::HSI16 => { - // Enable HSI16 - RCC.cr().write(|w| w.set_hsion(true)); - while !RCC.cr().read().hsirdy() {} - - (HSI_FREQ.0, 0x01, VoltageScale::Range2) - } - ClockSrc::HSE32 => { - // Enable HSE32 - RCC.cr().write(|w| { - w.set_hsebyppwr(true); - w.set_hseon(true); - }); - while !RCC.cr().read().hserdy() {} - - (HSE32_FREQ.0, 0x02, VoltageScale::Range1) - } - ClockSrc::MSI(range) => { - RCC.cr().write(|w| { - w.set_msirange(range.into()); - w.set_msion(true); - }); - - while !RCC.cr().read().msirdy() {} - - (range.freq(), 0x00, range.vos()) - } + ClockSrc::HSI16 => (HSI_FREQ.0, 0x01, VoltageScale::Range2), + ClockSrc::HSE32 => (HSE32_FREQ.0, 0x02, VoltageScale::Range1), + ClockSrc::MSI(range) => (range.freq(), 0x00, range.vos()), }; - RCC.cfgr().modify(|w| { - w.set_sw(sw.into()); - if config.ahb_pre == AHBPrescaler::NotDivided { - w.set_hpre(0); - } else { - w.set_hpre(config.ahb_pre.into()); - } - w.set_ppre1(config.apb1_pre.into()); - w.set_ppre2(config.apb2_pre.into()); - }); - - RCC.extcfgr().modify(|w| { - if config.shd_ahb_pre == AHBPrescaler::NotDivided { - w.set_shdhpre(0); - } else { - w.set_shdhpre(config.shd_ahb_pre.into()); - } - }); - let ahb_freq: u32 = match config.ahb_pre { AHBPrescaler::NotDivided => sys_clk, pre => { @@ -283,21 +258,11 @@ pub(crate) unsafe fn init(config: Config) { pre => { let pre: u8 = pre.into(); let pre: u8 = 1 << (pre - 3); - let freq = ahb_freq / (1 << (pre as u8 - 3)); + let freq = ahb_freq / pre as u32; (freq, freq * 2) } }; - let apb3_freq = shd_ahb_freq; - - if config.enable_lsi { - let csr = RCC.csr().read(); - if !csr.lsion() { - RCC.csr().modify(|w| w.set_lsion(true)); - while !RCC.csr().read().lsirdy() {} - } - } - // Adjust flash latency let flash_clk_src_freq: u32 = shd_ahb_freq; let ws = match vos { @@ -319,6 +284,102 @@ pub(crate) unsafe fn init(config: Config) { while FLASH.acr().read().latency() != ws {} + match config.rtc_mux { + RtcClockSource::LSE32 => { + // 1. Unlock the backup domain + PWR.cr1().modify(|w| w.set_dbp(Dbp::ENABLED)); + + // 2. Setup the LSE + RCC.bdcr().modify(|w| { + // Enable LSE + w.set_lseon(true); + // Max drive strength + // TODO: should probably be settable + w.set_lsedrv(Lsedrv::High as u8); //---// PAM - should not be commented + }); + + // Wait until LSE is running + while !RCC.bdcr().read().lserdy() {} + } + RtcClockSource::LSI32 => { + // Turn on the internal 32 kHz LSI oscillator + RCC.csr().modify(|w| w.set_lsion(true)); + + // Wait until LSI is running + while !RCC.csr().read().lsirdy() {} + } + } + + match config.mux { + ClockSrc::HSI16 => { + // Enable HSI16 + RCC.cr().write(|w| w.set_hsion(true)); + while !RCC.cr().read().hsirdy() {} + } + ClockSrc::HSE32 => { + // Enable HSE32 + RCC.cr().write(|w| { + w.set_hsebyppwr(true); + w.set_hseon(true); + }); + while !RCC.cr().read().hserdy() {} + } + ClockSrc::MSI(range) => { + let cr = RCC.cr().read(); + assert!(!cr.msion() || cr.msirdy()); + RCC.cr().write(|w| { + w.set_msirgsel(true); + w.set_msirange(range.into()); + w.set_msion(true); + + if let RtcClockSource::LSE32 = config.rtc_mux { + // If LSE is enabled, enable calibration of MSI + w.set_msipllen(true); + } else { + w.set_msipllen(false); + } + }); + while !RCC.cr().read().msirdy() {} + } + } + + if config.enable_rtc_apb { + // enable peripheral clock for communication + crate::pac::RCC.apb1enr1().modify(|w| w.set_rtcapben(true)); + + // read to allow the pwr clock to enable + crate::pac::PWR.cr1().read(); + } + + RCC.extcfgr().modify(|w| { + if config.shd_ahb_pre == AHBPrescaler::NotDivided { + w.set_shdhpre(0); + } else { + w.set_shdhpre(config.shd_ahb_pre.into()); + } + }); + + RCC.cfgr().modify(|w| { + w.set_sw(sw.into()); + if config.ahb_pre == AHBPrescaler::NotDivided { + w.set_hpre(0); + } else { + w.set_hpre(config.ahb_pre.into()); + } + w.set_ppre1(config.apb1_pre.into()); + w.set_ppre2(config.apb2_pre.into()); + }); + + // TODO: switch voltage range + + if config.enable_lsi { + let csr = RCC.csr().read(); + if !csr.lsion() { + RCC.csr().modify(|w| w.set_lsion(true)); + while !RCC.csr().read().lsirdy() {} + } + } + set_freqs(Clocks { sys: Hertz(sys_clk), ahb1: Hertz(ahb_freq), @@ -326,7 +387,7 @@ pub(crate) unsafe fn init(config: Config) { ahb3: Hertz(shd_ahb_freq), apb1: Hertz(apb1_freq), apb2: Hertz(apb2_freq), - apb3: Hertz(apb3_freq), + apb3: Hertz(shd_ahb_freq), apb1_tim: Hertz(apb1_tim_freq), apb2_tim: Hertz(apb2_tim_freq), }); diff --git a/embassy-stm32/src/rng.rs b/embassy-stm32/src/rng.rs index 520f2ab9a..b2faec53d 100644 --- a/embassy-stm32/src/rng.rs +++ b/embassy-stm32/src/rng.rs @@ -1,10 +1,10 @@ #![macro_use] +use core::future::poll_fn; use core::task::Poll; use embassy_hal_common::{into_ref, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; -use futures::future::poll_fn; use rand_core::{CryptoRng, RngCore}; use crate::{pac, peripherals, Peripheral}; @@ -32,37 +32,36 @@ impl<'d, T: Instance> Rng<'d, T> { } pub fn reset(&mut self) { - unsafe { - T::regs().cr().modify(|reg| { - reg.set_rngen(true); - reg.set_ie(true); - }); - T::regs().sr().modify(|reg| { - reg.set_seis(false); - reg.set_ceis(false); - }); + // rng_v2 locks up on seed error, needs reset + #[cfg(rng_v2)] + if T::regs().sr().read().seis() { + T::reset(); } + T::regs().cr().modify(|reg| { + reg.set_rngen(true); + reg.set_ie(true); + }); + T::regs().sr().modify(|reg| { + reg.set_seis(false); + reg.set_ceis(false); + }); // Reference manual says to discard the first. let _ = self.next_u32(); } pub async fn async_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { - unsafe { - T::regs().cr().modify(|reg| { - reg.set_rngen(true); - }) - } + T::regs().cr().modify(|reg| { + reg.set_rngen(true); + }); for chunk in dest.chunks_mut(4) { poll_fn(|cx| { RNG_WAKER.register(cx.waker()); - unsafe { - T::regs().cr().modify(|reg| { - reg.set_ie(true); - }); - } + T::regs().cr().modify(|reg| { + reg.set_ie(true); + }); - let bits = unsafe { T::regs().sr().read() }; + let bits = T::regs().sr().read(); if bits.drdy() { Poll::Ready(Ok(())) @@ -77,7 +76,7 @@ impl<'d, T: Instance> Rng<'d, T> { } }) .await?; - let random_bytes = unsafe { T::regs().dr().read() }.to_be_bytes(); + let random_bytes = T::regs().dr().read().to_be_bytes(); for (dest, src) in chunk.iter_mut().zip(random_bytes.iter()) { *dest = *src } @@ -90,9 +89,11 @@ impl<'d, T: Instance> Rng<'d, T> { impl<'d, T: Instance> RngCore for Rng<'d, T> { fn next_u32(&mut self) -> u32 { loop { - let bits = unsafe { T::regs().sr().read() }; - if bits.drdy() { - return unsafe { T::regs().dr().read() }; + let sr = T::regs().sr().read(); + if sr.seis() | sr.ceis() { + self.reset(); + } else if sr.drdy() { + return T::regs().dr().read(); } } } @@ -142,6 +143,7 @@ foreach_peripheral!( }; ); +#[cfg(feature = "rt")] macro_rules! irq { ($irq:ident) => { mod rng_irq { @@ -159,6 +161,7 @@ macro_rules! irq { }; } +#[cfg(feature = "rt")] foreach_interrupt!( (RNG) => { irq!(RNG); diff --git a/embassy-stm32/src/rtc/datetime.rs b/embassy-stm32/src/rtc/datetime.rs new file mode 100644 index 000000000..a9c48d88d --- /dev/null +++ b/embassy-stm32/src/rtc/datetime.rs @@ -0,0 +1,199 @@ +#[cfg(feature = "chrono")] +use core::convert::From; + +#[cfg(feature = "chrono")] +use chrono::{self, Datelike, NaiveDate, Timelike, Weekday}; + +use super::byte_to_bcd2; +use crate::pac::rtc::Rtc; + +/// Errors regarding the [`DateTime`] struct. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum Error { + /// The [DateTime] contains an invalid year value. Must be between `0..=4095`. + InvalidYear, + /// The [DateTime] contains an invalid month value. Must be between `1..=12`. + InvalidMonth, + /// The [DateTime] contains an invalid day value. Must be between `1..=31`. + InvalidDay, + /// The [DateTime] contains an invalid day of week. Must be between `0..=6` where 0 is Sunday. + InvalidDayOfWeek( + /// The value of the DayOfWeek that was given. + u8, + ), + /// The [DateTime] contains an invalid hour value. Must be between `0..=23`. + InvalidHour, + /// The [DateTime] contains an invalid minute value. Must be between `0..=59`. + InvalidMinute, + /// The [DateTime] contains an invalid second value. Must be between `0..=59`. + InvalidSecond, +} + +/// Structure containing date and time information +pub struct DateTime { + /// 0..4095 + pub year: u16, + /// 1..12, 1 is January + pub month: u8, + /// 1..28,29,30,31 depending on month + pub day: u8, + /// + pub day_of_week: DayOfWeek, + /// 0..23 + pub hour: u8, + /// 0..59 + pub minute: u8, + /// 0..59 + pub second: u8, +} + +#[cfg(feature = "chrono")] +impl From for DateTime { + fn from(date_time: chrono::NaiveDateTime) -> Self { + Self { + year: date_time.year() as u16, + month: date_time.month() as u8, + day: date_time.day() as u8, + day_of_week: date_time.weekday().into(), + hour: date_time.hour() as u8, + minute: date_time.minute() as u8, + second: date_time.second() as u8, + } + } +} + +#[cfg(feature = "chrono")] +impl From for chrono::NaiveDateTime { + fn from(date_time: DateTime) -> Self { + NaiveDate::from_ymd_opt(date_time.year as i32, date_time.month as u32, date_time.day as u32) + .unwrap() + .and_hms_opt(date_time.hour as u32, date_time.minute as u32, date_time.second as u32) + .unwrap() + } +} + +/// A day of the week +#[repr(u8)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] +#[allow(missing_docs)] +pub enum DayOfWeek { + Monday = 0, + Tuesday = 1, + Wednesday = 2, + Thursday = 3, + Friday = 4, + Saturday = 5, + Sunday = 6, +} + +#[cfg(feature = "chrono")] +impl From for DayOfWeek { + fn from(weekday: Weekday) -> Self { + day_of_week_from_u8(weekday.number_from_monday() as u8).unwrap() + } +} + +#[cfg(feature = "chrono")] +impl From for chrono::Weekday { + fn from(weekday: DayOfWeek) -> Self { + match weekday { + DayOfWeek::Monday => Weekday::Mon, + DayOfWeek::Tuesday => Weekday::Tue, + DayOfWeek::Wednesday => Weekday::Wed, + DayOfWeek::Thursday => Weekday::Thu, + DayOfWeek::Friday => Weekday::Fri, + DayOfWeek::Saturday => Weekday::Sat, + DayOfWeek::Sunday => Weekday::Sun, + } + } +} + +fn day_of_week_from_u8(v: u8) -> Result { + Ok(match v { + 0 => DayOfWeek::Monday, + 1 => DayOfWeek::Tuesday, + 2 => DayOfWeek::Wednesday, + 3 => DayOfWeek::Thursday, + 4 => DayOfWeek::Friday, + 5 => DayOfWeek::Saturday, + 6 => DayOfWeek::Sunday, + x => return Err(Error::InvalidDayOfWeek(x)), + }) +} + +pub(super) fn day_of_week_to_u8(dotw: DayOfWeek) -> u8 { + dotw as u8 +} + +pub(super) fn validate_datetime(dt: &DateTime) -> Result<(), Error> { + if dt.year > 4095 { + Err(Error::InvalidYear) + } else if dt.month < 1 || dt.month > 12 { + Err(Error::InvalidMonth) + } else if dt.day < 1 || dt.day > 31 { + Err(Error::InvalidDay) + } else if dt.hour > 23 { + Err(Error::InvalidHour) + } else if dt.minute > 59 { + Err(Error::InvalidMinute) + } else if dt.second > 59 { + Err(Error::InvalidSecond) + } else { + Ok(()) + } +} + +pub(super) fn write_date_time(rtc: &Rtc, t: DateTime) { + let (ht, hu) = byte_to_bcd2(t.hour as u8); + let (mnt, mnu) = byte_to_bcd2(t.minute as u8); + let (st, su) = byte_to_bcd2(t.second as u8); + + let (dt, du) = byte_to_bcd2(t.day as u8); + let (mt, mu) = byte_to_bcd2(t.month as u8); + let yr = t.year as u16; + let yr_offset = (yr - 1970_u16) as u8; + let (yt, yu) = byte_to_bcd2(yr_offset); + + use crate::pac::rtc::vals::Ampm; + + rtc.tr().write(|w| { + w.set_ht(ht); + w.set_hu(hu); + w.set_mnt(mnt); + w.set_mnu(mnu); + w.set_st(st); + w.set_su(su); + w.set_pm(Ampm::AM); + }); + + rtc.dr().write(|w| { + w.set_dt(dt); + w.set_du(du); + w.set_mt(mt > 0); + w.set_mu(mu); + w.set_yt(yt); + w.set_yu(yu); + w.set_wdu(day_of_week_to_u8(t.day_of_week)); + }); +} + +pub(super) fn datetime( + year: u16, + month: u8, + day: u8, + day_of_week: u8, + hour: u8, + minute: u8, + second: u8, +) -> Result { + let day_of_week = day_of_week_from_u8(day_of_week)?; + Ok(DateTime { + year, + month, + day, + day_of_week, + hour, + minute, + second, + }) +} diff --git a/embassy-stm32/src/rtc/mod.rs b/embassy-stm32/src/rtc/mod.rs new file mode 100644 index 000000000..12a2ac795 --- /dev/null +++ b/embassy-stm32/src/rtc/mod.rs @@ -0,0 +1,247 @@ +//! RTC peripheral abstraction +use core::marker::PhantomData; +mod datetime; + +pub use self::datetime::{DateTime, DayOfWeek, Error as DateTimeError}; + +/// refer to AN4759 to compare features of RTC2 and RTC3 +#[cfg_attr(any(rtc_v1), path = "v1.rs")] +#[cfg_attr( + any( + rtc_v2f0, rtc_v2f2, rtc_v2f3, rtc_v2f4, rtc_v2f7, rtc_v2h7, rtc_v2l0, rtc_v2l1, rtc_v2l4, rtc_v2wb + ), + path = "v2.rs" +)] +#[cfg_attr(any(rtc_v3, rtc_v3u5), path = "v3.rs")] +mod _version; +pub use _version::*; +use embassy_hal_common::Peripheral; + +/// Errors that can occur on methods on [RtcClock] +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum RtcError { + /// An invalid DateTime was given or stored on the hardware. + InvalidDateTime(DateTimeError), + + /// The RTC clock is not running + NotRunning, +} + +/// RTC Abstraction +pub struct Rtc<'d, T: Instance> { + phantom: PhantomData<&'d mut T>, + rtc_config: RtcConfig, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +#[repr(u8)] +pub enum RtcClockSource { + /// 00: No clock + NoClock = 0b00, + /// 01: LSE oscillator clock used as RTC clock + LSE = 0b01, + /// 10: LSI oscillator clock used as RTC clock + LSI = 0b10, + /// 11: HSE oscillator clock divided by 32 used as RTC clock + HSE = 0b11, +} + +#[derive(Copy, Clone, PartialEq)] +pub struct RtcConfig { + /// RTC clock source + clock_config: RtcClockSource, + /// Asynchronous prescaler factor + /// This is the asynchronous division factor: + /// ck_apre frequency = RTCCLK frequency/(PREDIV_A+1) + /// ck_apre drives the subsecond register + async_prescaler: u8, + /// Synchronous prescaler factor + /// This is the synchronous division factor: + /// ck_spre frequency = ck_apre frequency/(PREDIV_S+1) + /// ck_spre must be 1Hz + sync_prescaler: u16, +} + +impl Default for RtcConfig { + /// LSI with prescalers assuming 32.768 kHz. + /// Raw sub-seconds in 1/256. + fn default() -> Self { + RtcConfig { + clock_config: RtcClockSource::LSI, + async_prescaler: 127, + sync_prescaler: 255, + } + } +} + +impl RtcConfig { + /// Sets the clock source of RTC config + pub fn clock_config(mut self, cfg: RtcClockSource) -> Self { + self.clock_config = cfg; + self + } + + /// Set the asynchronous prescaler of RTC config + pub fn async_prescaler(mut self, prescaler: u8) -> Self { + self.async_prescaler = prescaler; + self + } + + /// Set the synchronous prescaler of RTC config + pub fn sync_prescaler(mut self, prescaler: u16) -> Self { + self.sync_prescaler = prescaler; + self + } +} + +#[derive(Copy, Clone, Debug, PartialEq)] +#[repr(u8)] +pub enum RtcCalibrationCyclePeriod { + /// 8-second calibration period + Seconds8, + /// 16-second calibration period + Seconds16, + /// 32-second calibration period + Seconds32, +} + +impl Default for RtcCalibrationCyclePeriod { + fn default() -> Self { + RtcCalibrationCyclePeriod::Seconds32 + } +} + +impl<'d, T: Instance> Rtc<'d, T> { + pub fn new(_rtc: impl Peripheral

+ 'd, rtc_config: RtcConfig) -> Self { + T::enable_peripheral_clk(); + + let mut rtc_struct = Self { + phantom: PhantomData, + rtc_config, + }; + + rtc_struct.apply_config(rtc_config); + + rtc_struct + } + + /// Set the datetime to a new value. + /// + /// # Errors + /// + /// Will return `RtcError::InvalidDateTime` if the datetime is not a valid range. + pub fn set_datetime(&mut self, t: DateTime) -> Result<(), RtcError> { + self::datetime::validate_datetime(&t).map_err(RtcError::InvalidDateTime)?; + self.write(true, |rtc| self::datetime::write_date_time(rtc, t)); + + Ok(()) + } + + /// Return the current datetime. + /// + /// # Errors + /// + /// Will return an `RtcError::InvalidDateTime` if the stored value in the system is not a valid [`DayOfWeek`]. + pub fn now(&self) -> Result { + let r = T::regs(); + let tr = r.tr().read(); + let second = bcd2_to_byte((tr.st(), tr.su())); + let minute = bcd2_to_byte((tr.mnt(), tr.mnu())); + let hour = bcd2_to_byte((tr.ht(), tr.hu())); + // Reading either RTC_SSR or RTC_TR locks the values in the higher-order + // calendar shadow registers until RTC_DR is read. + let dr = r.dr().read(); + + let weekday = dr.wdu(); + let day = bcd2_to_byte((dr.dt(), dr.du())); + let month = bcd2_to_byte((dr.mt() as u8, dr.mu())); + let year = bcd2_to_byte((dr.yt(), dr.yu())) as u16 + 1970_u16; + + self::datetime::datetime(year, month, day, weekday, hour, minute, second).map_err(RtcError::InvalidDateTime) + } + + /// Check if daylight savings time is active. + pub fn get_daylight_savings(&self) -> bool { + let cr = T::regs().cr().read(); + cr.bkp() + } + + /// Enable/disable daylight savings time. + pub fn set_daylight_savings(&mut self, daylight_savings: bool) { + self.write(true, |rtc| { + rtc.cr().modify(|w| w.set_bkp(daylight_savings)); + }) + } + + pub fn get_config(&self) -> RtcConfig { + self.rtc_config + } + + pub const BACKUP_REGISTER_COUNT: usize = T::BACKUP_REGISTER_COUNT; + + /// Read content of the backup register. + /// + /// The registers retain their values during wakes from standby mode or system resets. They also + /// retain their value when Vdd is switched off as long as V_BAT is powered. + pub fn read_backup_register(&self, register: usize) -> Option { + T::read_backup_register(&T::regs(), register) + } + + /// Set content of the backup register. + /// + /// The registers retain their values during wakes from standby mode or system resets. They also + /// retain their value when Vdd is switched off as long as V_BAT is powered. + pub fn write_backup_register(&self, register: usize, value: u32) { + T::write_backup_register(&T::regs(), register, value) + } +} + +pub(crate) fn byte_to_bcd2(byte: u8) -> (u8, u8) { + let mut bcd_high: u8 = 0; + let mut value = byte; + + while value >= 10 { + bcd_high += 1; + value -= 10; + } + + (bcd_high, ((bcd_high << 4) | value) as u8) +} + +pub(crate) fn bcd2_to_byte(bcd: (u8, u8)) -> u8 { + let value = bcd.1 | bcd.0 << 4; + + let tmp = ((value & 0xF0) >> 0x4) * 10; + + tmp + (value & 0x0F) +} + +pub(crate) mod sealed { + use crate::pac::rtc::Rtc; + + pub trait Instance { + const BACKUP_REGISTER_COUNT: usize; + + fn regs() -> Rtc { + crate::pac::RTC + } + + fn enable_peripheral_clk() {} + + /// Read content of the backup register. + /// + /// The registers retain their values during wakes from standby mode or system resets. They also + /// retain their value when Vdd is switched off as long as V_BAT is powered. + fn read_backup_register(rtc: &Rtc, register: usize) -> Option; + + /// Set content of the backup register. + /// + /// The registers retain their values during wakes from standby mode or system resets. They also + /// retain their value when Vdd is switched off as long as V_BAT is powered. + fn write_backup_register(rtc: &Rtc, register: usize, value: u32); + + // fn apply_config(&mut self, rtc_config: RtcConfig); + } +} + +pub trait Instance: sealed::Instance + 'static {} diff --git a/embassy-stm32/src/rtc/v2.rs b/embassy-stm32/src/rtc/v2.rs new file mode 100644 index 000000000..a2eace6d3 --- /dev/null +++ b/embassy-stm32/src/rtc/v2.rs @@ -0,0 +1,221 @@ +use stm32_metapac::rtc::vals::{Init, Osel, Pol}; + +use super::{sealed, Instance, RtcConfig}; +use crate::pac::rtc::Rtc; + +impl<'d, T: Instance> super::Rtc<'d, T> { + /// Applies the RTC config + /// It this changes the RTC clock source the time will be reset + pub(super) fn apply_config(&mut self, rtc_config: RtcConfig) { + // Unlock the backup domain + let clock_config = rtc_config.clock_config as u8; + + #[cfg(not(rtc_v2wb))] + use stm32_metapac::rcc::vals::Rtcsel; + + #[cfg(any(rtc_v2f2, rtc_v2f3, rtc_v2l1))] + let cr = crate::pac::PWR.cr(); + #[cfg(any(rtc_v2f4, rtc_v2f7, rtc_v2h7, rtc_v2l4, rtc_v2wb))] + let cr = crate::pac::PWR.cr1(); + + // TODO: Missing from PAC for l0 and f0? + #[cfg(not(any(rtc_v2f0, rtc_v2l0)))] + { + cr.modify(|w| w.set_dbp(true)); + while !cr.read().dbp() {} + } + + #[cfg(not(any(rtc_v2l0, rtc_v2l1)))] + let reg = crate::pac::RCC.bdcr().read(); + #[cfg(any(rtc_v2l0, rtc_v2l1))] + let reg = crate::pac::RCC.csr().read(); + + #[cfg(any(rtc_v2h7, rtc_v2l4, rtc_v2wb))] + assert!(!reg.lsecsson(), "RTC is not compatible with LSE CSS, yet."); + + #[cfg(rtc_v2wb)] + let rtcsel = reg.rtcsel(); + #[cfg(not(rtc_v2wb))] + let rtcsel = reg.rtcsel().to_bits(); + + if !reg.rtcen() || rtcsel != clock_config { + #[cfg(not(any(rtc_v2l0, rtc_v2l1)))] + crate::pac::RCC.bdcr().modify(|w| w.set_bdrst(true)); + + #[cfg(not(any(rtc_v2l0, rtc_v2l1)))] + let cr = crate::pac::RCC.bdcr(); + #[cfg(any(rtc_v2l0, rtc_v2l1))] + let cr = crate::pac::RCC.csr(); + + cr.modify(|w| { + // Reset + #[cfg(not(any(rtc_v2l0, rtc_v2l1)))] + w.set_bdrst(false); + + // Select RTC source + #[cfg(not(rtc_v2wb))] + w.set_rtcsel(Rtcsel::from_bits(clock_config)); + #[cfg(rtc_v2wb)] + w.set_rtcsel(clock_config); + w.set_rtcen(true); + + // Restore bcdr + #[cfg(any(rtc_v2l4, rtc_v2wb))] + w.set_lscosel(reg.lscosel()); + #[cfg(any(rtc_v2l4, rtc_v2wb))] + w.set_lscoen(reg.lscoen()); + + w.set_lseon(reg.lseon()); + + #[cfg(any(rtc_v2f0, rtc_v2f7, rtc_v2h7, rtc_v2l4, rtc_v2wb))] + w.set_lsedrv(reg.lsedrv()); + w.set_lsebyp(reg.lsebyp()); + }); + } + + self.write(true, |rtc| { + rtc.cr().modify(|w| { + #[cfg(rtc_v2f2)] + w.set_fmt(false); + #[cfg(not(rtc_v2f2))] + w.set_fmt(stm32_metapac::rtc::vals::Fmt::TWENTY_FOUR_HOUR); + w.set_osel(Osel::DISABLED); + w.set_pol(Pol::HIGH); + }); + + rtc.prer().modify(|w| { + w.set_prediv_s(rtc_config.sync_prescaler); + w.set_prediv_a(rtc_config.async_prescaler); + }); + }); + + self.rtc_config = rtc_config; + } + + /// Calibrate the clock drift. + /// + /// `clock_drift` can be adjusted from -487.1 ppm to 488.5 ppm and is clamped to this range. + /// + /// ### Note + /// + /// To perform a calibration when `async_prescaler` is less then 3, `sync_prescaler` + /// has to be reduced accordingly (see RM0351 Rev 9, sec 38.3.12). + #[cfg(not(rtc_v2f2))] + pub fn calibrate(&mut self, mut clock_drift: f32, period: super::RtcCalibrationCyclePeriod) { + const RTC_CALR_MIN_PPM: f32 = -487.1; + const RTC_CALR_MAX_PPM: f32 = 488.5; + const RTC_CALR_RESOLUTION_PPM: f32 = 0.9537; + + if clock_drift < RTC_CALR_MIN_PPM { + clock_drift = RTC_CALR_MIN_PPM; + } else if clock_drift > RTC_CALR_MAX_PPM { + clock_drift = RTC_CALR_MAX_PPM; + } + + clock_drift = clock_drift / RTC_CALR_RESOLUTION_PPM; + + self.write(false, |rtc| { + rtc.calr().write(|w| { + match period { + super::RtcCalibrationCyclePeriod::Seconds8 => { + w.set_calw8(stm32_metapac::rtc::vals::Calw8::EIGHT_SECOND); + } + super::RtcCalibrationCyclePeriod::Seconds16 => { + w.set_calw16(stm32_metapac::rtc::vals::Calw16::SIXTEEN_SECOND); + } + super::RtcCalibrationCyclePeriod::Seconds32 => { + // Set neither `calw8` nor `calw16` to use 32 seconds + } + } + + // Extra pulses during calibration cycle period: CALP * 512 - CALM + // + // CALP sets whether pulses are added or omitted. + // + // CALM contains how many pulses (out of 512) are masked in a + // given calibration cycle period. + if clock_drift > 0.0 { + // Maximum (about 512.2) rounds to 512. + clock_drift += 0.5; + + // When the offset is positive (0 to 512), the opposite of + // the offset (512 - offset) is masked, i.e. for the + // maximum offset (512), 0 pulses are masked. + w.set_calp(stm32_metapac::rtc::vals::Calp::INCREASEFREQ); + w.set_calm(512 - clock_drift as u16); + } else { + // Minimum (about -510.7) rounds to -511. + clock_drift -= 0.5; + + // When the offset is negative or zero (-511 to 0), + // the absolute offset is masked, i.e. for the minimum + // offset (-511), 511 pulses are masked. + w.set_calp(stm32_metapac::rtc::vals::Calp::NOCHANGE); + w.set_calm((clock_drift * -1.0) as u16); + } + }); + }) + } + + pub(super) fn write(&mut self, init_mode: bool, f: F) -> R + where + F: FnOnce(&crate::pac::rtc::Rtc) -> R, + { + let r = T::regs(); + // Disable write protection. + // This is safe, as we're only writin the correct and expected values. + r.wpr().write(|w| w.set_key(0xca)); + r.wpr().write(|w| w.set_key(0x53)); + + // true if initf bit indicates RTC peripheral is in init mode + if init_mode && !r.isr().read().initf() { + // to update calendar date/time, time format, and prescaler configuration, RTC must be in init mode + r.isr().modify(|w| w.set_init(Init::INITMODE)); + // wait till init state entered + // ~2 RTCCLK cycles + while !r.isr().read().initf() {} + } + + let result = f(&r); + + if init_mode { + r.isr().modify(|w| w.set_init(Init::FREERUNNINGMODE)); // Exits init mode + } + + // Re-enable write protection. + // This is safe, as the field accepts the full range of 8-bit values. + r.wpr().write(|w| w.set_key(0xff)); + result + } +} + +impl sealed::Instance for crate::peripherals::RTC { + const BACKUP_REGISTER_COUNT: usize = 20; + + fn enable_peripheral_clk() { + #[cfg(any(rtc_v2l4, rtc_v2wb))] + { + // enable peripheral clock for communication + crate::pac::RCC.apb1enr1().modify(|w| w.set_rtcapben(true)); + + // read to allow the pwr clock to enable + crate::pac::PWR.cr1().read(); + } + } + + fn read_backup_register(rtc: &Rtc, register: usize) -> Option { + if register < Self::BACKUP_REGISTER_COUNT { + Some(rtc.bkpr(register).read().bkp()) + } else { + None + } + } + + fn write_backup_register(rtc: &Rtc, register: usize, value: u32) { + if register < Self::BACKUP_REGISTER_COUNT { + rtc.bkpr(register).write(|w| w.set_bkp(value)); + } + } +} + +impl Instance for crate::peripherals::RTC {} diff --git a/embassy-stm32/src/rtc/v3.rs b/embassy-stm32/src/rtc/v3.rs new file mode 100644 index 000000000..8ef0ec51d --- /dev/null +++ b/embassy-stm32/src/rtc/v3.rs @@ -0,0 +1,192 @@ +use stm32_metapac::rtc::vals::{Calp, Calw16, Calw8, Fmt, Init, Key, Osel, Pol, TampalrmPu, TampalrmType}; + +use super::{sealed, Instance, RtcCalibrationCyclePeriod, RtcConfig}; +use crate::pac::rtc::Rtc; + +impl<'d, T: Instance> super::Rtc<'d, T> { + /// Applies the RTC config + /// It this changes the RTC clock source the time will be reset + pub(super) fn apply_config(&mut self, rtc_config: RtcConfig) { + // Unlock the backup domain + #[cfg(not(any(rtc_v3u5, rcc_wl5, rcc_wle)))] + { + crate::pac::PWR.cr1().modify(|w| w.set_dbp(true)); + while !crate::pac::PWR.cr1().read().dbp() {} + } + #[cfg(any(rcc_wl5, rcc_wle))] + { + use crate::pac::pwr::vals::Dbp; + + crate::pac::PWR.cr1().modify(|w| w.set_dbp(Dbp::ENABLED)); + while crate::pac::PWR.cr1().read().dbp() != Dbp::ENABLED {} + } + + let reg = crate::pac::RCC.bdcr().read(); + assert!(!reg.lsecsson(), "RTC is not compatible with LSE CSS, yet."); + + let config_rtcsel = rtc_config.clock_config as u8; + #[cfg(not(any(rcc_wl5, rcc_wle)))] + let config_rtcsel = crate::pac::rcc::vals::Rtcsel::from_bits(config_rtcsel); + + if !reg.rtcen() || reg.rtcsel() != config_rtcsel { + crate::pac::RCC.bdcr().modify(|w| w.set_bdrst(true)); + + crate::pac::RCC.bdcr().modify(|w| { + // Reset + w.set_bdrst(false); + + // Select RTC source + w.set_rtcsel(config_rtcsel); + + w.set_rtcen(true); + + // Restore bcdr + w.set_lscosel(reg.lscosel()); + w.set_lscoen(reg.lscoen()); + + w.set_lseon(reg.lseon()); + w.set_lsedrv(reg.lsedrv()); + w.set_lsebyp(reg.lsebyp()); + }); + } + + self.write(true, |rtc| { + rtc.cr().modify(|w| { + w.set_fmt(Fmt::TWENTYFOURHOUR); + w.set_osel(Osel::DISABLED); + w.set_pol(Pol::HIGH); + }); + + rtc.prer().modify(|w| { + w.set_prediv_s(rtc_config.sync_prescaler); + w.set_prediv_a(rtc_config.async_prescaler); + }); + + // TODO: configuration for output pins + rtc.cr().modify(|w| { + w.set_out2en(false); + w.set_tampalrm_type(TampalrmType::PUSHPULL); + w.set_tampalrm_pu(TampalrmPu::NOPULLUP); + }); + }); + + self.rtc_config = rtc_config; + } + + const RTC_CALR_MIN_PPM: f32 = -487.1; + const RTC_CALR_MAX_PPM: f32 = 488.5; + const RTC_CALR_RESOLUTION_PPM: f32 = 0.9537; + + /// Calibrate the clock drift. + /// + /// `clock_drift` can be adjusted from -487.1 ppm to 488.5 ppm and is clamped to this range. + /// + /// ### Note + /// + /// To perform a calibration when `async_prescaler` is less then 3, `sync_prescaler` + /// has to be reduced accordingly (see RM0351 Rev 9, sec 38.3.12). + pub fn calibrate(&mut self, mut clock_drift: f32, period: RtcCalibrationCyclePeriod) { + if clock_drift < Self::RTC_CALR_MIN_PPM { + clock_drift = Self::RTC_CALR_MIN_PPM; + } else if clock_drift > Self::RTC_CALR_MAX_PPM { + clock_drift = Self::RTC_CALR_MAX_PPM; + } + + clock_drift = clock_drift / Self::RTC_CALR_RESOLUTION_PPM; + + self.write(false, |rtc| { + rtc.calr().write(|w| { + match period { + RtcCalibrationCyclePeriod::Seconds8 => { + w.set_calw8(Calw8::EIGHTSECONDS); + } + RtcCalibrationCyclePeriod::Seconds16 => { + w.set_calw16(Calw16::SIXTEENSECONDS); + } + RtcCalibrationCyclePeriod::Seconds32 => { + // Set neither `calw8` nor `calw16` to use 32 seconds + } + } + + // Extra pulses during calibration cycle period: CALP * 512 - CALM + // + // CALP sets whether pulses are added or omitted. + // + // CALM contains how many pulses (out of 512) are masked in a + // given calibration cycle period. + if clock_drift > 0.0 { + // Maximum (about 512.2) rounds to 512. + clock_drift += 0.5; + + // When the offset is positive (0 to 512), the opposite of + // the offset (512 - offset) is masked, i.e. for the + // maximum offset (512), 0 pulses are masked. + w.set_calp(Calp::INCREASEFREQ); + w.set_calm(512 - clock_drift as u16); + } else { + // Minimum (about -510.7) rounds to -511. + clock_drift -= 0.5; + + // When the offset is negative or zero (-511 to 0), + // the absolute offset is masked, i.e. for the minimum + // offset (-511), 511 pulses are masked. + w.set_calp(Calp::NOCHANGE); + w.set_calm((clock_drift * -1.0) as u16); + } + }); + }) + } + + pub(super) fn write(&mut self, init_mode: bool, f: F) -> R + where + F: FnOnce(&crate::pac::rtc::Rtc) -> R, + { + let r = T::regs(); + // Disable write protection. + // This is safe, as we're only writin the correct and expected values. + r.wpr().write(|w| w.set_key(Key::DEACTIVATE1)); + r.wpr().write(|w| w.set_key(Key::DEACTIVATE2)); + + if init_mode && !r.icsr().read().initf() { + r.icsr().modify(|w| w.set_init(Init::INITMODE)); + // wait till init state entered + // ~2 RTCCLK cycles + while !r.icsr().read().initf() {} + } + + let result = f(&r); + + if init_mode { + r.icsr().modify(|w| w.set_init(Init::FREERUNNINGMODE)); // Exits init mode + } + + // Re-enable write protection. + // This is safe, as the field accepts the full range of 8-bit values. + r.wpr().write(|w| w.set_key(Key::ACTIVATE)); + + result + } +} + +impl sealed::Instance for crate::peripherals::RTC { + const BACKUP_REGISTER_COUNT: usize = 32; + + fn read_backup_register(_rtc: &Rtc, register: usize) -> Option { + #[allow(clippy::if_same_then_else)] + if register < Self::BACKUP_REGISTER_COUNT { + //Some(rtc.bkpr()[register].read().bits()) + None // RTC3 backup registers come from the TAMP peripe=heral, not RTC. Not() even in the L412 PAC + } else { + None + } + } + + fn write_backup_register(_rtc: &Rtc, register: usize, _value: u32) { + if register < Self::BACKUP_REGISTER_COUNT { + // RTC3 backup registers come from the TAMP peripe=heral, not RTC. Not() even in the L412 PAC + //self.rtc.bkpr()[register].write(|w| w.bits(value)) + } + } +} + +impl Instance for crate::peripherals::RTC {} diff --git a/embassy-stm32/src/sdmmc/mod.rs b/embassy-stm32/src/sdmmc/mod.rs index 67758c492..698292bff 100644 --- a/embassy-stm32/src/sdmmc/mod.rs +++ b/embassy-stm32/src/sdmmc/mod.rs @@ -1,22 +1,53 @@ #![macro_use] use core::default::Default; +use core::future::poll_fn; +use core::marker::PhantomData; +use core::ops::{Deref, DerefMut}; use core::task::Poll; use embassy_hal_common::drop::OnDrop; use embassy_hal_common::{into_ref, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; -use futures::future::poll_fn; use sdio_host::{BusWidth, CardCapacity, CardStatus, CurrentState, SDStatus, CID, CSD, OCR, SCR}; use crate::dma::NoDma; use crate::gpio::sealed::{AFType, Pin}; use crate::gpio::{AnyPin, Pull, Speed}; -use crate::interrupt::{Interrupt, InterruptExt}; +use crate::interrupt::typelevel::Interrupt; use crate::pac::sdmmc::Sdmmc as RegBlock; use crate::rcc::RccPeripheral; use crate::time::Hertz; -use crate::{peripherals, Peripheral}; +use crate::{interrupt, peripherals, Peripheral}; + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl InterruptHandler { + fn data_interrupts(enable: bool) { + let regs = T::regs(); + regs.maskr().write(|w| { + w.set_dcrcfailie(enable); + w.set_dtimeoutie(enable); + w.set_dataendie(enable); + + #[cfg(sdmmc_v2)] + w.set_dabortie(enable); + }); + } +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + Self::data_interrupts(false); + T::state().wake(); + } +} + +/// Frequency used for SD Card initialization. Must be no higher than 400 kHz. +const SD_INIT_FREQ: Hertz = Hertz(400_000); /// The signalling scheme used on the SDMMC bus #[non_exhaustive] @@ -37,11 +68,27 @@ impl Default for Signalling { } #[repr(align(4))] -pub struct DataBlock([u8; 512]); +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct DataBlock(pub [u8; 512]); + +impl Deref for DataBlock { + type Target = [u8; 512]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for DataBlock { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} /// Errors #[non_exhaustive] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Error { Timeout, @@ -115,47 +162,77 @@ enum Response { Long = 3, } -cfg_if::cfg_if! { - if #[cfg(sdmmc_v1)] { - /// Calculate clock divisor. Returns a SDMMC_CK less than or equal to - /// `sdmmc_ck` in Hertz. - /// - /// Returns `(clk_div, clk_f)`, where `clk_div` is the divisor register - /// value and `clk_f` is the resulting new clock frequency. - fn clk_div(ker_ck: Hertz, sdmmc_ck: u32) -> Result<(u8, Hertz), Error> { - let clk_div = match ker_ck.0 / sdmmc_ck { - 0 | 1 => Ok(0), - x @ 2..=258 => { - Ok((x - 2) as u8) - } - _ => Err(Error::BadClock), - }?; +/// Calculate clock divisor. Returns a SDMMC_CK less than or equal to +/// `sdmmc_ck` in Hertz. +/// +/// Returns `(bypass, clk_div, clk_f)`, where `bypass` enables clock divisor bypass (only sdmmc_v1), +/// `clk_div` is the divisor register value and `clk_f` is the resulting new clock frequency. +#[cfg(sdmmc_v1)] +fn clk_div(ker_ck: Hertz, sdmmc_ck: u32) -> Result<(bool, u8, Hertz), Error> { + // sdmmc_v1 maximum clock is 50 MHz + if sdmmc_ck > 50_000_000 { + return Err(Error::BadClock); + } - // SDIO_CK frequency = SDIOCLK / [CLKDIV + 2] - let clk_f = Hertz(ker_ck.0 / (clk_div as u32 + 2)); - Ok((clk_div, clk_f)) - } - } else if #[cfg(sdmmc_v2)] { - /// Calculate clock divisor. Returns a SDMMC_CK less than or equal to - /// `sdmmc_ck` in Hertz. - /// - /// Returns `(clk_div, clk_f)`, where `clk_div` is the divisor register - /// value and `clk_f` is the resulting new clock frequency. - fn clk_div(ker_ck: Hertz, sdmmc_ck: u32) -> Result<(u16, Hertz), Error> { - match (ker_ck.0 + sdmmc_ck - 1) / sdmmc_ck { - 0 | 1 => Ok((0, ker_ck)), - x @ 2..=2046 => { - let clk_div = ((x + 1) / 2) as u16; - let clk = Hertz(ker_ck.0 / (clk_div as u32 * 2)); + // bypass divisor + if ker_ck.0 <= sdmmc_ck { + return Ok((true, 0, ker_ck)); + } - Ok((clk_div, clk)) - } - _ => Err(Error::BadClock), - } + // `ker_ck / sdmmc_ck` rounded up + let clk_div = match (ker_ck.0 + sdmmc_ck - 1) / sdmmc_ck { + 0 | 1 => Ok(0), + x @ 2..=258 => Ok((x - 2) as u8), + _ => Err(Error::BadClock), + }?; + + // SDIO_CK frequency = SDIOCLK / [CLKDIV + 2] + let clk_f = Hertz(ker_ck.0 / (clk_div as u32 + 2)); + Ok((false, clk_div, clk_f)) +} + +/// Calculate clock divisor. Returns a SDMMC_CK less than or equal to +/// `sdmmc_ck` in Hertz. +/// +/// Returns `(bypass, clk_div, clk_f)`, where `bypass` enables clock divisor bypass (only sdmmc_v1), +/// `clk_div` is the divisor register value and `clk_f` is the resulting new clock frequency. +#[cfg(sdmmc_v2)] +fn clk_div(ker_ck: Hertz, sdmmc_ck: u32) -> Result<(bool, u16, Hertz), Error> { + // `ker_ck / sdmmc_ck` rounded up + match (ker_ck.0 + sdmmc_ck - 1) / sdmmc_ck { + 0 | 1 => Ok((false, 0, ker_ck)), + x @ 2..=2046 => { + // SDMMC_CK frequency = SDMMCCLK / [CLKDIV * 2] + let clk_div = ((x + 1) / 2) as u16; + let clk = Hertz(ker_ck.0 / (clk_div as u32 * 2)); + + Ok((false, clk_div, clk)) } + _ => Err(Error::BadClock), } } +#[cfg(sdmmc_v1)] +type Transfer<'a, C> = crate::dma::Transfer<'a, C>; +#[cfg(sdmmc_v2)] +struct Transfer<'a, C> { + _dummy: core::marker::PhantomData<&'a mut C>, +} + +#[cfg(all(sdmmc_v1, dma))] +const DMA_TRANSFER_OPTIONS: crate::dma::TransferOptions = crate::dma::TransferOptions { + pburst: crate::dma::Burst::Incr4, + mburst: crate::dma::Burst::Incr4, + flow_ctrl: crate::dma::FlowControl::Peripheral, + fifo_threshold: Some(crate::dma::FifoThreshold::Full), +}; +#[cfg(all(sdmmc_v1, not(dma)))] +const DMA_TRANSFER_OPTIONS: crate::dma::TransferOptions = crate::dma::TransferOptions { + circular: false, + half_transfer_ir: false, + complete_transfer_ir: true, +}; + /// SDMMC configuration /// /// Default values: @@ -175,9 +252,9 @@ impl Default for Config { } /// Sdmmc device -pub struct Sdmmc<'d, T: Instance, Dma = NoDma> { +pub struct Sdmmc<'d, T: Instance, Dma: SdmmcDma = NoDma> { _peri: PeripheralRef<'d, T>, - irq: PeripheralRef<'d, T::Interrupt>, + #[allow(unused)] dma: PeripheralRef<'d, Dma>, clk: PeripheralRef<'d, AnyPin>, @@ -200,7 +277,7 @@ pub struct Sdmmc<'d, T: Instance, Dma = NoDma> { impl<'d, T: Instance, Dma: SdmmcDma> Sdmmc<'d, T, Dma> { pub fn new_1bit( sdmmc: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, dma: impl Peripheral

+ 'd, clk: impl Peripheral

> + 'd, cmd: impl Peripheral

> + 'd, @@ -209,7 +286,7 @@ impl<'d, T: Instance, Dma: SdmmcDma> Sdmmc<'d, T, Dma> { ) -> Self { into_ref!(clk, cmd, d0); - critical_section::with(|_| unsafe { + critical_section::with(|_| { clk.set_as_af_pull(clk.af_num(), AFType::OutputPushPull, Pull::None); cmd.set_as_af_pull(cmd.af_num(), AFType::OutputPushPull, Pull::Up); d0.set_as_af_pull(d0.af_num(), AFType::OutputPushPull, Pull::Up); @@ -221,7 +298,6 @@ impl<'d, T: Instance, Dma: SdmmcDma> Sdmmc<'d, T, Dma> { Self::new_inner( sdmmc, - irq, dma, clk.map_into(), cmd.map_into(), @@ -235,7 +311,7 @@ impl<'d, T: Instance, Dma: SdmmcDma> Sdmmc<'d, T, Dma> { pub fn new_4bit( sdmmc: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, dma: impl Peripheral

+ 'd, clk: impl Peripheral

> + 'd, cmd: impl Peripheral

> + 'd, @@ -247,7 +323,7 @@ impl<'d, T: Instance, Dma: SdmmcDma> Sdmmc<'d, T, Dma> { ) -> Self { into_ref!(clk, cmd, d0, d1, d2, d3); - critical_section::with(|_| unsafe { + critical_section::with(|_| { clk.set_as_af_pull(clk.af_num(), AFType::OutputPushPull, Pull::None); cmd.set_as_af_pull(cmd.af_num(), AFType::OutputPushPull, Pull::Up); d0.set_as_af_pull(d0.af_num(), AFType::OutputPushPull, Pull::Up); @@ -265,7 +341,6 @@ impl<'d, T: Instance, Dma: SdmmcDma> Sdmmc<'d, T, Dma> { Self::new_inner( sdmmc, - irq, dma, clk.map_into(), cmd.map_into(), @@ -276,56 +351,13 @@ impl<'d, T: Instance, Dma: SdmmcDma> Sdmmc<'d, T, Dma> { config, ) } - - fn new_inner( - sdmmc: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, - dma: impl Peripheral

+ 'd, - clk: PeripheralRef<'d, AnyPin>, - cmd: PeripheralRef<'d, AnyPin>, - d0: PeripheralRef<'d, AnyPin>, - d1: Option>, - d2: Option>, - d3: Option>, - config: Config, - ) -> Self { - into_ref!(sdmmc, irq, dma); - - T::enable(); - T::reset(); - - let inner = T::inner(); - let clock = unsafe { inner.new_inner(T::frequency()) }; - - irq.set_handler(Self::on_interrupt); - irq.unpend(); - irq.enable(); - - Self { - _peri: sdmmc, - irq, - dma, - - clk, - cmd, - d0, - d1, - d2, - d3, - - config, - clock, - signalling: Default::default(), - card: None, - } - } } #[cfg(sdmmc_v2)] impl<'d, T: Instance> Sdmmc<'d, T, NoDma> { pub fn new_1bit( sdmmc: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, clk: impl Peripheral

> + 'd, cmd: impl Peripheral

> + 'd, d0: impl Peripheral

> + 'd, @@ -333,7 +365,7 @@ impl<'d, T: Instance> Sdmmc<'d, T, NoDma> { ) -> Self { into_ref!(clk, cmd, d0); - critical_section::with(|_| unsafe { + critical_section::with(|_| { clk.set_as_af_pull(clk.af_num(), AFType::OutputPushPull, Pull::None); cmd.set_as_af_pull(cmd.af_num(), AFType::OutputPushPull, Pull::Up); d0.set_as_af_pull(d0.af_num(), AFType::OutputPushPull, Pull::Up); @@ -345,7 +377,7 @@ impl<'d, T: Instance> Sdmmc<'d, T, NoDma> { Self::new_inner( sdmmc, - irq, + NoDma.into_ref(), clk.map_into(), cmd.map_into(), d0.map_into(), @@ -358,7 +390,7 @@ impl<'d, T: Instance> Sdmmc<'d, T, NoDma> { pub fn new_4bit( sdmmc: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, clk: impl Peripheral

> + 'd, cmd: impl Peripheral

> + 'd, d0: impl Peripheral

> + 'd, @@ -369,7 +401,7 @@ impl<'d, T: Instance> Sdmmc<'d, T, NoDma> { ) -> Self { into_ref!(clk, cmd, d0, d1, d2, d3); - critical_section::with(|_| unsafe { + critical_section::with(|_| { clk.set_as_af_pull(clk.af_num(), AFType::OutputPushPull, Pull::None); cmd.set_as_af_pull(cmd.af_num(), AFType::OutputPushPull, Pull::Up); d0.set_as_af_pull(d0.af_num(), AFType::OutputPushPull, Pull::Up); @@ -387,7 +419,7 @@ impl<'d, T: Instance> Sdmmc<'d, T, NoDma> { Self::new_inner( sdmmc, - irq, + NoDma.into_ref(), clk.map_into(), cmd.map_into(), d0.map_into(), @@ -397,10 +429,12 @@ impl<'d, T: Instance> Sdmmc<'d, T, NoDma> { config, ) } +} +impl<'d, T: Instance, Dma: SdmmcDma + 'd> Sdmmc<'d, T, Dma> { fn new_inner( sdmmc: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, + dma: impl Peripheral

+ 'd, clk: PeripheralRef<'d, AnyPin>, cmd: PeripheralRef<'d, AnyPin>, d0: PeripheralRef<'d, AnyPin>, @@ -409,170 +443,24 @@ impl<'d, T: Instance> Sdmmc<'d, T, NoDma> { d3: Option>, config: Config, ) -> Self { - into_ref!(sdmmc, irq); + into_ref!(sdmmc, dma); T::enable(); T::reset(); - let inner = T::inner(); - let clock = unsafe { inner.new_inner(T::frequency()) }; - - irq.set_handler(Self::on_interrupt); - irq.unpend(); - irq.enable(); - - Self { - _peri: sdmmc, - irq, - dma: NoDma.into_ref(), - - clk, - cmd, - d0, - d1, - d2, - d3, - - config, - clock, - signalling: Default::default(), - card: None, - } - } -} - -impl<'d, T: Instance, Dma: SdmmcDma> Sdmmc<'d, T, Dma> { - #[inline(always)] - pub async fn init_card(&mut self, freq: Hertz) -> Result<(), Error> { - let inner = T::inner(); - let freq = freq.into(); - - let bus_width = match self.d3.is_some() { - true => BusWidth::Four, - false => BusWidth::One, - }; - - inner - .init_card( - freq, - bus_width, - &mut self.card, - &mut self.signalling, - T::frequency(), - &mut self.clock, - T::state(), - self.config.data_transfer_timeout, - &mut *self.dma, - ) - .await - } - - #[inline(always)] - pub async fn read_block(&mut self, block_idx: u32, buffer: &mut DataBlock) -> Result<(), Error> { - let card_capacity = self.card()?.card_type; - let inner = T::inner(); - let state = T::state(); - - // NOTE(unsafe) DataBlock uses align 4 - let buf = unsafe { &mut *((&mut buffer.0) as *mut [u8; 512] as *mut [u32; 128]) }; - inner - .read_block( - block_idx, - buf, - card_capacity, - state, - self.config.data_transfer_timeout, - &mut *self.dma, - ) - .await - } - - pub async fn write_block(&mut self, block_idx: u32, buffer: &DataBlock) -> Result<(), Error> { - let card = self.card.as_mut().ok_or(Error::NoCard)?; - let inner = T::inner(); - let state = T::state(); - - // NOTE(unsafe) DataBlock uses align 4 - let buf = unsafe { &*((&buffer.0) as *const [u8; 512] as *const [u32; 128]) }; - inner - .write_block( - block_idx, - buf, - card, - state, - self.config.data_transfer_timeout, - &mut *self.dma, - ) - .await - } - - /// Get a reference to the initialized card - /// - /// # Errors - /// - /// Returns Error::NoCard if [`init_card`](#method.init_card) - /// has not previously succeeded - #[inline(always)] - pub fn card(&self) -> Result<&Card, Error> { - self.card.as_ref().ok_or(Error::NoCard) - } - - /// Get the current SDMMC bus clock - pub fn clock(&self) -> Hertz { - self.clock - } - - #[inline(always)] - fn on_interrupt(_: *mut ()) { - let regs = T::inner(); - let state = T::state(); - - regs.data_interrupts(false); - state.wake(); - } -} - -impl<'d, T: Instance, Dma> Drop for Sdmmc<'d, T, Dma> { - fn drop(&mut self) { - self.irq.disable(); - let inner = T::inner(); - unsafe { inner.on_drop() }; - - critical_section::with(|_| unsafe { - self.clk.set_as_disconnected(); - self.cmd.set_as_disconnected(); - self.d0.set_as_disconnected(); - if let Some(x) = &mut self.d1 { - x.set_as_disconnected(); - } - if let Some(x) = &mut self.d2 { - x.set_as_disconnected(); - } - if let Some(x) = &mut self.d3 { - x.set_as_disconnected(); - } - }); - } -} - -pub struct SdmmcInner(pub(crate) RegBlock); - -impl SdmmcInner { - /// # Safety - /// - /// Access to `regs` registers should be exclusive - unsafe fn new_inner(&self, kernel_clk: Hertz) -> Hertz { - let regs = self.0; - - // While the SD/SDIO card or eMMC is in identification mode, - // the SDMMC_CK frequency must be less than 400 kHz. - let (clkdiv, clock) = unwrap!(clk_div(kernel_clk, 400_000)); + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + let regs = T::regs(); regs.clkcr().write(|w| { - w.set_widbus(0); - w.set_clkdiv(clkdiv); w.set_pwrsav(false); w.set_negedge(false); + + // Hardware flow control is broken on SDIOv1 and causes clock glitches, which result in CRC errors. + // See chip erratas for more details. + #[cfg(sdmmc_v1)] + w.set_hwfc_en(false); + #[cfg(sdmmc_v2)] w.set_hwfc_en(true); #[cfg(sdmmc_v1)] @@ -583,345 +471,93 @@ impl SdmmcInner { // D[7:0], CMD, and CK are driven high. regs.power().modify(|w| w.set_pwrctrl(PowerCtrl::Off as u8)); - clock - } + Self { + _peri: sdmmc, + dma, - /// Initializes card (if present) and sets the bus at the - /// specified frequency. - #[allow(clippy::too_many_arguments)] - async fn init_card>( - &self, - freq: Hertz, - bus_width: BusWidth, - old_card: &mut Option, - signalling: &mut Signalling, - ker_ck: Hertz, - clock: &mut Hertz, - waker_reg: &AtomicWaker, - data_transfer_timeout: u32, - dma: &mut Dma, - ) -> Result<(), Error> { - let regs = self.0; + clk, + cmd, + d0, + d1, + d2, + d3, - // NOTE(unsafe) We have exclusive access to the peripheral - unsafe { - regs.power().modify(|w| w.set_pwrctrl(PowerCtrl::On as u8)); - self.cmd(Cmd::idle(), false)?; - - // Check if cards supports CMD8 (with pattern) - self.cmd(Cmd::hs_send_ext_csd(0x1AA), false)?; - let r1 = regs.respr(0).read().cardstatus(); - - let mut card = if r1 == 0x1AA { - // Card echoed back the pattern. Must be at least v2 - Card::default() - } else { - return Err(Error::UnsupportedCardVersion); - }; - - let ocr = loop { - // Signal that next command is a app command - self.cmd(Cmd::app_cmd(0), false)?; // CMD55 - - let arg = CmdAppOper::VOLTAGE_WINDOW_SD as u32 - | CmdAppOper::HIGH_CAPACITY as u32 - | CmdAppOper::SD_SWITCH_1_8V_CAPACITY as u32; - - // Initialize card - match self.cmd(Cmd::app_op_cmd(arg), false) { - // ACMD41 - Ok(_) => (), - Err(Error::Crc) => (), - Err(err) => return Err(err), - } - let ocr: OCR = regs.respr(0).read().cardstatus().into(); - if !ocr.is_busy() { - // Power up done - break ocr; - } - }; - - if ocr.high_capacity() { - // Card is SDHC or SDXC or SDUC - card.card_type = CardCapacity::SDHC; - } else { - card.card_type = CardCapacity::SDSC; - } - card.ocr = ocr; - - self.cmd(Cmd::all_send_cid(), false)?; // CMD2 - let cid0 = regs.respr(0).read().cardstatus() as u128; - let cid1 = regs.respr(1).read().cardstatus() as u128; - let cid2 = regs.respr(2).read().cardstatus() as u128; - let cid3 = regs.respr(3).read().cardstatus() as u128; - let cid = (cid0 << 96) | (cid1 << 64) | (cid2 << 32) | (cid3); - card.cid = cid.into(); - - self.cmd(Cmd::send_rel_addr(), false)?; - card.rca = regs.respr(0).read().cardstatus() >> 16; - - self.cmd(Cmd::send_csd(card.rca << 16), false)?; - let csd0 = regs.respr(0).read().cardstatus() as u128; - let csd1 = regs.respr(1).read().cardstatus() as u128; - let csd2 = regs.respr(2).read().cardstatus() as u128; - let csd3 = regs.respr(3).read().cardstatus() as u128; - let csd = (csd0 << 96) | (csd1 << 64) | (csd2 << 32) | (csd3); - card.csd = csd.into(); - - self.select_card(Some(&card))?; - - self.get_scr(&mut card, waker_reg, data_transfer_timeout, dma).await?; - - // Set bus width - let (width, acmd_arg) = match bus_width { - BusWidth::Eight => unimplemented!(), - BusWidth::Four if card.scr.bus_width_four() => (BusWidth::Four, 2), - _ => (BusWidth::One, 0), - }; - self.cmd(Cmd::app_cmd(card.rca << 16), false)?; - self.cmd(Cmd::cmd6(acmd_arg), false)?; - - // CPSMACT and DPSMACT must be 0 to set WIDBUS - self.wait_idle(); - - regs.clkcr().modify(|w| { - w.set_widbus(match width { - BusWidth::One => 0, - BusWidth::Four => 1, - BusWidth::Eight => 2, - _ => panic!("Invalid Bus Width"), - }) - }); - - // Set Clock - if freq.0 <= 25_000_000 { - // Final clock frequency - self.clkcr_set_clkdiv(freq.0, width, ker_ck, clock)?; - } else { - // Switch to max clock for SDR12 - self.clkcr_set_clkdiv(25_000_000, width, ker_ck, clock)?; - } - - // Read status - self.read_sd_status(&mut card, waker_reg, data_transfer_timeout, dma) - .await?; - - if freq.0 > 25_000_000 { - // Switch to SDR25 - *signalling = self - .switch_signalling_mode(Signalling::SDR25, waker_reg, data_transfer_timeout, dma) - .await?; - - if *signalling == Signalling::SDR25 { - // Set final clock frequency - self.clkcr_set_clkdiv(freq.0, width, ker_ck, clock)?; - - if self.read_status(&card)?.state() != CurrentState::Transfer { - return Err(Error::SignalingSwitchFailed); - } - } - } - // Read status after signalling change - self.read_sd_status(&mut card, waker_reg, data_transfer_timeout, dma) - .await?; - old_card.replace(card); - } - - Ok(()) - } - - async fn read_block>( - &self, - block_idx: u32, - buffer: &mut [u32; 128], - capacity: CardCapacity, - waker_reg: &AtomicWaker, - data_transfer_timeout: u32, - dma: &mut Dma, - ) -> Result<(), Error> { - // Always read 1 block of 512 bytes - // SDSC cards are byte addressed hence the blockaddress is in multiples of 512 bytes - let address = match capacity { - CardCapacity::SDSC => block_idx * 512, - _ => block_idx, - }; - self.cmd(Cmd::set_block_length(512), false)?; // CMD16 - - let regs = self.0; - let on_drop = OnDrop::new(|| unsafe { self.on_drop() }); - - unsafe { - self.prepare_datapath_read(buffer as *mut [u32; 128], 512, 9, data_transfer_timeout, dma); - self.data_interrupts(true); - } - self.cmd(Cmd::read_single_block(address), true)?; - - let res = poll_fn(|cx| { - waker_reg.register(cx.waker()); - let status = unsafe { regs.star().read() }; - - if status.dcrcfail() { - return Poll::Ready(Err(Error::Crc)); - } else if status.dtimeout() { - return Poll::Ready(Err(Error::Timeout)); - } else if status.dataend() { - return Poll::Ready(Ok(())); - } - Poll::Pending - }) - .await; - self.clear_interrupt_flags(); - - if res.is_ok() { - on_drop.defuse(); - self.stop_datapath(); - } - res - } - - async fn write_block>( - &self, - block_idx: u32, - buffer: &[u32; 128], - card: &mut Card, - waker_reg: &AtomicWaker, - data_transfer_timeout: u32, - dma: &mut Dma, - ) -> Result<(), Error> { - // Always read 1 block of 512 bytes - // SDSC cards are byte addressed hence the blockaddress is in multiples of 512 bytes - let address = match card.card_type { - CardCapacity::SDSC => block_idx * 512, - _ => block_idx, - }; - self.cmd(Cmd::set_block_length(512), false)?; // CMD16 - - let regs = self.0; - let on_drop = OnDrop::new(|| unsafe { self.on_drop() }); - - unsafe { - self.prepare_datapath_write(buffer as *const [u32; 128], 512, 9, data_transfer_timeout, dma); - self.data_interrupts(true); - } - self.cmd(Cmd::write_single_block(address), true)?; - - let res = poll_fn(|cx| { - waker_reg.register(cx.waker()); - let status = unsafe { regs.star().read() }; - - if status.dcrcfail() { - return Poll::Ready(Err(Error::Crc)); - } else if status.dtimeout() { - return Poll::Ready(Err(Error::Timeout)); - } else if status.dataend() { - return Poll::Ready(Ok(())); - } - Poll::Pending - }) - .await; - self.clear_interrupt_flags(); - - match res { - Ok(_) => { - on_drop.defuse(); - self.stop_datapath(); - - // TODO: Make this configurable - let mut timeout: u32 = 0x00FF_FFFF; - - // Try to read card status (ACMD13) - while timeout > 0 { - match self.read_sd_status(card, waker_reg, data_transfer_timeout, dma).await { - Ok(_) => return Ok(()), - Err(Error::Timeout) => (), // Try again - Err(e) => return Err(e), - } - timeout -= 1; - } - Err(Error::SoftwareTimeout) - } - Err(e) => Err(e), + config, + clock: SD_INIT_FREQ, + signalling: Default::default(), + card: None, } } /// Data transfer is in progress #[inline(always)] - fn data_active(&self) -> bool { - let regs = self.0; + fn data_active() -> bool { + let regs = T::regs(); - // NOTE(unsafe) Atomic read with no side-effects - unsafe { - let status = regs.star().read(); - cfg_if::cfg_if! { - if #[cfg(sdmmc_v1)] { - status.rxact() || status.txact() - } else if #[cfg(sdmmc_v2)] { - status.dpsmact() - } - } - } + let status = regs.star().read(); + #[cfg(sdmmc_v1)] + return status.rxact() || status.txact(); + #[cfg(sdmmc_v2)] + return status.dpsmact(); } /// Coammand transfer is in progress #[inline(always)] - fn cmd_active(&self) -> bool { - let regs = self.0; + fn cmd_active() -> bool { + let regs = T::regs(); - // NOTE(unsafe) Atomic read with no side-effects - unsafe { - let status = regs.star().read(); - cfg_if::cfg_if! { - if #[cfg(sdmmc_v1)] { - status.cmdact() - } else if #[cfg(sdmmc_v2)] { - status.cpsmact() - } - } - } + let status = regs.star().read(); + #[cfg(sdmmc_v1)] + return status.cmdact(); + #[cfg(sdmmc_v2)] + return status.cpsmact(); } /// Wait idle on CMDACT, RXACT and TXACT (v1) or DOSNACT and CPSMACT (v2) #[inline(always)] - fn wait_idle(&self) { - while self.data_active() || self.cmd_active() {} + fn wait_idle() { + while Self::data_active() || Self::cmd_active() {} } /// # Safety /// /// `buffer` must be valid for the whole transfer and word aligned - unsafe fn prepare_datapath_read>( - &self, - buffer: *mut [u32], + fn prepare_datapath_read<'a>( + &'a mut self, + buffer: &'a mut [u32], length_bytes: u32, block_size: u8, - data_transfer_timeout: u32, - #[allow(unused_variables)] dma: &mut Dma, - ) { + ) -> Transfer<'a, Dma> { assert!(block_size <= 14, "Block size up to 2^14 bytes"); - let regs = self.0; + let regs = T::regs(); // Command AND Data state machines must be idle - self.wait_idle(); - self.clear_interrupt_flags(); + Self::wait_idle(); + Self::clear_interrupt_flags(); - // NOTE(unsafe) We have exclusive access to the regisers - - regs.dtimer().write(|w| w.set_datatime(data_transfer_timeout)); + regs.dtimer() + .write(|w| w.set_datatime(self.config.data_transfer_timeout)); regs.dlenr().write(|w| w.set_datalength(length_bytes)); - cfg_if::cfg_if! { - if #[cfg(sdmmc_v1)] { - let request = dma.request(); - dma.start_read(request, regs.fifor().ptr() as *const u32, buffer, crate::dma::TransferOptions { - pburst: crate::dma::Burst::Incr4, - flow_ctrl: crate::dma::FlowControl::Peripheral, - ..Default::default() - }); - } else if #[cfg(sdmmc_v2)] { - regs.idmabase0r().write(|w| w.set_idmabase0(buffer as *mut u32 as u32)); - regs.idmactrlr().modify(|w| w.set_idmaen(true)); + #[cfg(sdmmc_v1)] + let transfer = unsafe { + let request = self.dma.request(); + Transfer::new_read( + &mut self.dma, + request, + regs.fifor().as_ptr() as *mut u32, + buffer, + DMA_TRANSFER_OPTIONS, + ) + }; + #[cfg(sdmmc_v2)] + let transfer = { + regs.idmabase0r().write(|w| w.set_idmabase0(buffer.as_mut_ptr() as u32)); + regs.idmactrlr().modify(|w| w.set_idmaen(true)); + Transfer { + _dummy: core::marker::PhantomData, } - } + }; regs.dctrl().modify(|w| { w.set_dblocksize(block_size); @@ -932,72 +568,79 @@ impl SdmmcInner { w.set_dten(true); } }); + + transfer } /// # Safety /// /// `buffer` must be valid for the whole transfer and word aligned - unsafe fn prepare_datapath_write>( - &self, - buffer: *const [u32], + fn prepare_datapath_write<'a>( + &'a mut self, + buffer: &'a [u32], length_bytes: u32, block_size: u8, - data_transfer_timeout: u32, - #[allow(unused_variables)] dma: &mut Dma, - ) { + ) -> Transfer<'a, Dma> { assert!(block_size <= 14, "Block size up to 2^14 bytes"); - let regs = self.0; + let regs = T::regs(); // Command AND Data state machines must be idle - self.wait_idle(); - self.clear_interrupt_flags(); + Self::wait_idle(); + Self::clear_interrupt_flags(); - // NOTE(unsafe) We have exclusive access to the regisers - - regs.dtimer().write(|w| w.set_datatime(data_transfer_timeout)); + regs.dtimer() + .write(|w| w.set_datatime(self.config.data_transfer_timeout)); regs.dlenr().write(|w| w.set_datalength(length_bytes)); - cfg_if::cfg_if! { - if #[cfg(sdmmc_v1)] { - let request = dma.request(); - dma.start_write(request, buffer, regs.fifor().ptr() as *mut u32, crate::dma::TransferOptions { - pburst: crate::dma::Burst::Incr4, - flow_ctrl: crate::dma::FlowControl::Peripheral, - ..Default::default() - }); - } else if #[cfg(sdmmc_v2)] { - regs.idmabase0r().write(|w| w.set_idmabase0(buffer as *const u32 as u32)); - regs.idmactrlr().modify(|w| w.set_idmaen(true)); + #[cfg(sdmmc_v1)] + let transfer = unsafe { + let request = self.dma.request(); + Transfer::new_write( + &mut self.dma, + request, + buffer, + regs.fifor().as_ptr() as *mut u32, + DMA_TRANSFER_OPTIONS, + ) + }; + #[cfg(sdmmc_v2)] + let transfer = { + regs.idmabase0r().write(|w| w.set_idmabase0(buffer.as_ptr() as u32)); + regs.idmactrlr().modify(|w| w.set_idmaen(true)); + Transfer { + _dummy: core::marker::PhantomData, } - } + }; regs.dctrl().modify(|w| { w.set_dblocksize(block_size); w.set_dtdir(false); + #[cfg(sdmmc_v1)] + { + w.set_dmaen(true); + w.set_dten(true); + } }); + + transfer } /// Stops the DMA datapath - fn stop_datapath(&self) { - let regs = self.0; + fn stop_datapath() { + let regs = T::regs(); - unsafe { - cfg_if::cfg_if! { - if #[cfg(sdmmc_v1)] { - regs.dctrl().modify(|w| { - w.set_dmaen(false); - w.set_dten(false); - }); - } else if #[cfg(sdmmc_v2)] { - regs.idmactrlr().modify(|w| w.set_idmaen(false)); - } - } - } + #[cfg(sdmmc_v1)] + regs.dctrl().modify(|w| { + w.set_dmaen(false); + w.set_dten(false); + }); + #[cfg(sdmmc_v2)] + regs.idmactrlr().modify(|w| w.set_idmaen(false)); } /// Sets the CLKDIV field in CLKCR. Updates clock field in self - fn clkcr_set_clkdiv(&self, freq: u32, width: BusWidth, ker_ck: Hertz, clock: &mut Hertz) -> Result<(), Error> { - let regs = self.0; + fn clkcr_set_clkdiv(&mut self, freq: u32, width: BusWidth) -> Result<(), Error> { + let regs = T::regs(); let width_u32 = match width { BusWidth::One => 1u32, @@ -1006,19 +649,22 @@ impl SdmmcInner { _ => panic!("Invalid Bus Width"), }; - let (clkdiv, new_clock) = clk_div(ker_ck, freq)?; + let ker_ck = T::kernel_clk(); + let (_bypass, clkdiv, new_clock) = clk_div(ker_ck, freq)?; + // Enforce AHB and SDMMC_CK clock relation. See RM0433 Rev 7 // Section 55.5.8 let sdmmc_bus_bandwidth = new_clock.0 * width_u32; assert!(ker_ck.0 > 3 * sdmmc_bus_bandwidth / 32); - *clock = new_clock; + self.clock = new_clock; - // NOTE(unsafe) We have exclusive access to the regblock - unsafe { - // CPSMACT and DPSMACT must be 0 to set CLKDIV - self.wait_idle(); - regs.clkcr().modify(|w| w.set_clkdiv(clkdiv)); - } + // CPSMACT and DPSMACT must be 0 to set CLKDIV + Self::wait_idle(); + regs.clkcr().modify(|w| { + w.set_clkdiv(clkdiv); + #[cfg(sdmmc_v1)] + w.set_bypass(_bypass); + }); Ok(()) } @@ -1028,13 +674,7 @@ impl SdmmcInner { /// Attempt to set a new signalling mode. The selected /// signalling mode is returned. Expects the current clock /// frequency to be > 12.5MHz. - async fn switch_signalling_mode>( - &self, - signalling: Signalling, - waker_reg: &AtomicWaker, - data_transfer_timeout: u32, - dma: &mut Dma, - ) -> Result { + async fn switch_signalling_mode(&mut self, signalling: Signalling) -> Result { // NB PLSS v7_10 4.3.10.4: "the use of SET_BLK_LEN command is not // necessary" @@ -1051,18 +691,16 @@ impl SdmmcInner { let mut status = [0u32; 16]; // Arm `OnDrop` after the buffer, so it will be dropped first - let regs = self.0; - let on_drop = OnDrop::new(|| unsafe { self.on_drop() }); + let regs = T::regs(); + let on_drop = OnDrop::new(|| Self::on_drop()); - unsafe { - self.prepare_datapath_read(&mut status as *mut [u32; 16], 64, 6, data_transfer_timeout, dma); - self.data_interrupts(true); - } - self.cmd(Cmd::cmd6(set_function), true)?; // CMD6 + let transfer = self.prepare_datapath_read(&mut status, 64, 6); + InterruptHandler::::data_interrupts(true); + Self::cmd(Cmd::cmd6(set_function), true)?; // CMD6 let res = poll_fn(|cx| { - waker_reg.register(cx.waker()); - let status = unsafe { regs.star().read() }; + T::state().register(cx.waker()); + let status = regs.star().read(); if status.dcrcfail() { return Poll::Ready(Err(Error::Crc)); @@ -1074,7 +712,7 @@ impl SdmmcInner { Poll::Pending }) .await; - self.clear_interrupt_flags(); + Self::clear_interrupt_flags(); // Host is allowed to use the new functions at least 8 // clocks after the end of the switch command @@ -1087,7 +725,8 @@ impl SdmmcInner { match res { Ok(_) => { on_drop.defuse(); - self.stop_datapath(); + Self::stop_datapath(); + drop(transfer); // Function Selection of Function Group 1 let selection = (u32::from_be(status[4]) >> 24) & 0xF; @@ -1106,45 +745,37 @@ impl SdmmcInner { } /// Query the card status (CMD13, returns R1) - /// fn read_status(&self, card: &Card) -> Result { - let regs = self.0; + let regs = T::regs(); let rca = card.rca; - self.cmd(Cmd::card_status(rca << 16), false)?; // CMD13 + Self::cmd(Cmd::card_status(rca << 16), false)?; // CMD13 - // NOTE(unsafe) Atomic read with no side-effects - let r1 = unsafe { regs.respr(0).read().cardstatus() }; + let r1 = regs.respr(0).read().cardstatus(); Ok(r1.into()) } /// Reads the SD Status (ACMD13) - async fn read_sd_status>( - &self, - card: &mut Card, - waker_reg: &AtomicWaker, - data_transfer_timeout: u32, - dma: &mut Dma, - ) -> Result<(), Error> { + async fn read_sd_status(&mut self) -> Result<(), Error> { + let card = self.card.as_mut().ok_or(Error::NoCard)?; let rca = card.rca; - self.cmd(Cmd::set_block_length(64), false)?; // CMD16 - self.cmd(Cmd::app_cmd(rca << 16), false)?; // APP + + Self::cmd(Cmd::set_block_length(64), false)?; // CMD16 + Self::cmd(Cmd::app_cmd(rca << 16), false)?; // APP let mut status = [0u32; 16]; // Arm `OnDrop` after the buffer, so it will be dropped first - let regs = self.0; - let on_drop = OnDrop::new(|| unsafe { self.on_drop() }); + let regs = T::regs(); + let on_drop = OnDrop::new(|| Self::on_drop()); - unsafe { - self.prepare_datapath_read(&mut status as *mut [u32; 16], 64, 6, data_transfer_timeout, dma); - self.data_interrupts(true); - } - self.cmd(Cmd::card_status(0), true)?; + let transfer = self.prepare_datapath_read(&mut status, 64, 6); + InterruptHandler::::data_interrupts(true); + Self::cmd(Cmd::card_status(0), true)?; let res = poll_fn(|cx| { - waker_reg.register(cx.waker()); - let status = unsafe { regs.star().read() }; + T::state().register(cx.waker()); + let status = regs.star().read(); if status.dcrcfail() { return Poll::Ready(Err(Error::Crc)); @@ -1156,16 +787,17 @@ impl SdmmcInner { Poll::Pending }) .await; - self.clear_interrupt_flags(); + Self::clear_interrupt_flags(); if res.is_ok() { on_drop.defuse(); - self.stop_datapath(); + Self::stop_datapath(); + drop(transfer); for byte in status.iter_mut() { *byte = u32::from_be(*byte); } - card.status = status.into(); + self.card.as_mut().unwrap().status = status.into(); } res } @@ -1178,7 +810,7 @@ impl SdmmcInner { // Determine Relative Card Address (RCA) of given card let rca = card.map(|c| c.rca << 16).unwrap_or(0); - let r = self.cmd(Cmd::sel_desel_card(rca), false); + let r = Self::cmd(Cmd::sel_desel_card(rca), false); match (r, rca) { (Err(Error::Timeout), 0) => Ok(()), _ => r, @@ -1187,82 +819,54 @@ impl SdmmcInner { /// Clear flags in interrupt clear register #[inline(always)] - fn clear_interrupt_flags(&self) { - let regs = self.0; - // NOTE(unsafe) Atomic write - unsafe { - regs.icr().write(|w| { - w.set_ccrcfailc(true); - w.set_dcrcfailc(true); - w.set_ctimeoutc(true); - w.set_dtimeoutc(true); - w.set_txunderrc(true); - w.set_rxoverrc(true); - w.set_cmdrendc(true); - w.set_cmdsentc(true); - w.set_dataendc(true); - w.set_dbckendc(true); - w.set_sdioitc(true); + fn clear_interrupt_flags() { + let regs = T::regs(); + regs.icr().write(|w| { + w.set_ccrcfailc(true); + w.set_dcrcfailc(true); + w.set_ctimeoutc(true); + w.set_dtimeoutc(true); + w.set_txunderrc(true); + w.set_rxoverrc(true); + w.set_cmdrendc(true); + w.set_cmdsentc(true); + w.set_dataendc(true); + w.set_dbckendc(true); + w.set_sdioitc(true); - #[cfg(sdmmc_v2)] - { - w.set_dholdc(true); - w.set_dabortc(true); - w.set_busyd0endc(true); - w.set_ackfailc(true); - w.set_acktimeoutc(true); - w.set_vswendc(true); - w.set_ckstopc(true); - w.set_idmatec(true); - w.set_idmabtcc(true); - } - }); - } + #[cfg(sdmmc_v2)] + { + w.set_dholdc(true); + w.set_dabortc(true); + w.set_busyd0endc(true); + w.set_ackfailc(true); + w.set_acktimeoutc(true); + w.set_vswendc(true); + w.set_ckstopc(true); + w.set_idmatec(true); + w.set_idmabtcc(true); + } + }); } - /// Enables the interrupts for data transfer - #[inline(always)] - fn data_interrupts(&self, enable: bool) { - let regs = self.0; - // NOTE(unsafe) Atomic write - unsafe { - regs.maskr().write(|w| { - w.set_dcrcfailie(enable); - w.set_dtimeoutie(enable); - w.set_dataendie(enable); - - #[cfg(sdmmc_v2)] - w.set_dabortie(enable); - }); - } - } - - async fn get_scr>( - &self, - card: &mut Card, - waker_reg: &AtomicWaker, - data_transfer_timeout: u32, - dma: &mut Dma, - ) -> Result<(), Error> { + async fn get_scr(&mut self, card: &mut Card) -> Result<(), Error> { // Read the the 64-bit SCR register - self.cmd(Cmd::set_block_length(8), false)?; // CMD16 - self.cmd(Cmd::app_cmd(card.rca << 16), false)?; + Self::cmd(Cmd::set_block_length(8), false)?; // CMD16 + Self::cmd(Cmd::app_cmd(card.rca << 16), false)?; let mut scr = [0u32; 2]; // Arm `OnDrop` after the buffer, so it will be dropped first - let regs = self.0; - let on_drop = OnDrop::new(move || unsafe { self.on_drop() }); + let regs = T::regs(); + let on_drop = OnDrop::new(|| Self::on_drop()); - unsafe { - self.prepare_datapath_read(&mut scr as *mut [u32], 8, 3, data_transfer_timeout, dma); - self.data_interrupts(true); - } - self.cmd(Cmd::cmd51(), true)?; + let transfer = self.prepare_datapath_read(&mut scr[..], 8, 3); + InterruptHandler::::data_interrupts(true); + Self::cmd(Cmd::cmd51(), true)?; let res = poll_fn(|cx| { - waker_reg.register(cx.waker()); - let status = unsafe { regs.star().read() }; + T::state().register(cx.waker()); + let status = regs.star().read(); if status.dcrcfail() { return Poll::Ready(Err(Error::Crc)); @@ -1274,11 +878,12 @@ impl SdmmcInner { Poll::Pending }) .await; - self.clear_interrupt_flags(); + Self::clear_interrupt_flags(); if res.is_ok() { on_drop.defuse(); - self.stop_datapath(); + Self::stop_datapath(); + drop(transfer); unsafe { let scr_bytes = &*(&scr as *const [u32; 2] as *const [u8; 8]); @@ -1290,69 +895,63 @@ impl SdmmcInner { /// Send command to card #[allow(unused_variables)] - fn cmd(&self, cmd: Cmd, data: bool) -> Result<(), Error> { - let regs = self.0; + fn cmd(cmd: Cmd, data: bool) -> Result<(), Error> { + let regs = T::regs(); - self.clear_interrupt_flags(); - // NOTE(safety) Atomic operations - unsafe { - // CP state machine must be idle - while self.cmd_active() {} + Self::clear_interrupt_flags(); + // CP state machine must be idle + while Self::cmd_active() {} - // Command arg - regs.argr().write(|w| w.set_cmdarg(cmd.arg)); + // Command arg + regs.argr().write(|w| w.set_cmdarg(cmd.arg)); - // Command index and start CP State Machine - regs.cmdr().write(|w| { - w.set_waitint(false); - w.set_waitresp(cmd.resp as u8); - w.set_cmdindex(cmd.cmd); - w.set_cpsmen(true); + // Command index and start CP State Machine + regs.cmdr().write(|w| { + w.set_waitint(false); + w.set_waitresp(cmd.resp as u8); + w.set_cmdindex(cmd.cmd); + w.set_cpsmen(true); - #[cfg(sdmmc_v2)] - { - // Special mode in CP State Machine - // CMD12: Stop Transmission - let cpsm_stop_transmission = cmd.cmd == 12; - w.set_cmdstop(cpsm_stop_transmission); - w.set_cmdtrans(data); - } - }); - - let mut status; - if cmd.resp == Response::None { - // Wait for CMDSENT or a timeout - while { - status = regs.star().read(); - !(status.ctimeout() || status.cmdsent()) - } {} - } else { - // Wait for CMDREND or CCRCFAIL or a timeout - while { - status = regs.star().read(); - !(status.ctimeout() || status.cmdrend() || status.ccrcfail()) - } {} + #[cfg(sdmmc_v2)] + { + // Special mode in CP State Machine + // CMD12: Stop Transmission + let cpsm_stop_transmission = cmd.cmd == 12; + w.set_cmdstop(cpsm_stop_transmission); + w.set_cmdtrans(data); } + }); - if status.ctimeout() { - return Err(Error::Timeout); - } else if status.ccrcfail() { - return Err(Error::Crc); - } - Ok(()) + let mut status; + if cmd.resp == Response::None { + // Wait for CMDSENT or a timeout + while { + status = regs.star().read(); + !(status.ctimeout() || status.cmdsent()) + } {} + } else { + // Wait for CMDREND or CCRCFAIL or a timeout + while { + status = regs.star().read(); + !(status.ctimeout() || status.cmdrend() || status.ccrcfail()) + } {} } + + if status.ctimeout() { + return Err(Error::Timeout); + } else if status.ccrcfail() { + return Err(Error::Crc); + } + Ok(()) } - /// # Safety - /// - /// Ensure that `regs` has exclusive access to the regblocks - unsafe fn on_drop(&self) { - let regs = self.0; - if self.data_active() { - self.clear_interrupt_flags(); + fn on_drop() { + let regs = T::regs(); + if Self::data_active() { + Self::clear_interrupt_flags(); // Send abort // CP state machine must be idle - while self.cmd_active() {} + while Self::cmd_active() {} // Command arg regs.argr().write(|w| w.set_cmdarg(0)); @@ -1372,11 +971,309 @@ impl SdmmcInner { }); // Wait for the abort - while self.data_active() {} + while Self::data_active() {} } - self.data_interrupts(false); - self.clear_interrupt_flags(); - self.stop_datapath(); + InterruptHandler::::data_interrupts(false); + Self::clear_interrupt_flags(); + Self::stop_datapath(); + } + + /// Initializes card (if present) and sets the bus at the + /// specified frequency. + pub async fn init_card(&mut self, freq: Hertz) -> Result<(), Error> { + let regs = T::regs(); + let ker_ck = T::kernel_clk(); + + let bus_width = match self.d3.is_some() { + true => BusWidth::Four, + false => BusWidth::One, + }; + + // While the SD/SDIO card or eMMC is in identification mode, + // the SDMMC_CK frequency must be no more than 400 kHz. + let (_bypass, clkdiv, init_clock) = unwrap!(clk_div(ker_ck, SD_INIT_FREQ.0)); + self.clock = init_clock; + + // CPSMACT and DPSMACT must be 0 to set WIDBUS + Self::wait_idle(); + + regs.clkcr().modify(|w| { + w.set_widbus(0); + w.set_clkdiv(clkdiv); + #[cfg(sdmmc_v1)] + w.set_bypass(_bypass); + }); + + regs.power().modify(|w| w.set_pwrctrl(PowerCtrl::On as u8)); + Self::cmd(Cmd::idle(), false)?; + + // Check if cards supports CMD8 (with pattern) + Self::cmd(Cmd::hs_send_ext_csd(0x1AA), false)?; + let r1 = regs.respr(0).read().cardstatus(); + + let mut card = if r1 == 0x1AA { + // Card echoed back the pattern. Must be at least v2 + Card::default() + } else { + return Err(Error::UnsupportedCardVersion); + }; + + let ocr = loop { + // Signal that next command is a app command + Self::cmd(Cmd::app_cmd(0), false)?; // CMD55 + + let arg = CmdAppOper::VOLTAGE_WINDOW_SD as u32 + | CmdAppOper::HIGH_CAPACITY as u32 + | CmdAppOper::SD_SWITCH_1_8V_CAPACITY as u32; + + // Initialize card + match Self::cmd(Cmd::app_op_cmd(arg), false) { + // ACMD41 + Ok(_) => (), + Err(Error::Crc) => (), + Err(err) => return Err(err), + } + let ocr: OCR = regs.respr(0).read().cardstatus().into(); + if !ocr.is_busy() { + // Power up done + break ocr; + } + }; + + if ocr.high_capacity() { + // Card is SDHC or SDXC or SDUC + card.card_type = CardCapacity::SDHC; + } else { + card.card_type = CardCapacity::SDSC; + } + card.ocr = ocr; + + Self::cmd(Cmd::all_send_cid(), false)?; // CMD2 + let cid0 = regs.respr(0).read().cardstatus() as u128; + let cid1 = regs.respr(1).read().cardstatus() as u128; + let cid2 = regs.respr(2).read().cardstatus() as u128; + let cid3 = regs.respr(3).read().cardstatus() as u128; + let cid = (cid0 << 96) | (cid1 << 64) | (cid2 << 32) | (cid3); + card.cid = cid.into(); + + Self::cmd(Cmd::send_rel_addr(), false)?; + card.rca = regs.respr(0).read().cardstatus() >> 16; + + Self::cmd(Cmd::send_csd(card.rca << 16), false)?; + let csd0 = regs.respr(0).read().cardstatus() as u128; + let csd1 = regs.respr(1).read().cardstatus() as u128; + let csd2 = regs.respr(2).read().cardstatus() as u128; + let csd3 = regs.respr(3).read().cardstatus() as u128; + let csd = (csd0 << 96) | (csd1 << 64) | (csd2 << 32) | (csd3); + card.csd = csd.into(); + + self.select_card(Some(&card))?; + + self.get_scr(&mut card).await?; + + // Set bus width + let (width, acmd_arg) = match bus_width { + BusWidth::Eight => unimplemented!(), + BusWidth::Four if card.scr.bus_width_four() => (BusWidth::Four, 2), + _ => (BusWidth::One, 0), + }; + Self::cmd(Cmd::app_cmd(card.rca << 16), false)?; + Self::cmd(Cmd::cmd6(acmd_arg), false)?; + + // CPSMACT and DPSMACT must be 0 to set WIDBUS + Self::wait_idle(); + + regs.clkcr().modify(|w| { + w.set_widbus(match width { + BusWidth::One => 0, + BusWidth::Four => 1, + BusWidth::Eight => 2, + _ => panic!("Invalid Bus Width"), + }) + }); + + // Set Clock + if freq.0 <= 25_000_000 { + // Final clock frequency + self.clkcr_set_clkdiv(freq.0, width)?; + } else { + // Switch to max clock for SDR12 + self.clkcr_set_clkdiv(25_000_000, width)?; + } + + self.card = Some(card); + + // Read status + self.read_sd_status().await?; + + if freq.0 > 25_000_000 { + // Switch to SDR25 + self.signalling = self.switch_signalling_mode(Signalling::SDR25).await?; + + if self.signalling == Signalling::SDR25 { + // Set final clock frequency + self.clkcr_set_clkdiv(freq.0, width)?; + + if self.read_status(&card)?.state() != CurrentState::Transfer { + return Err(Error::SignalingSwitchFailed); + } + } + } + // Read status after signalling change + self.read_sd_status().await?; + + Ok(()) + } + + #[inline(always)] + pub async fn read_block(&mut self, block_idx: u32, buffer: &mut DataBlock) -> Result<(), Error> { + let card_capacity = self.card()?.card_type; + + // NOTE(unsafe) DataBlock uses align 4 + let buffer = unsafe { &mut *((&mut buffer.0) as *mut [u8; 512] as *mut [u32; 128]) }; + + // Always read 1 block of 512 bytes + // SDSC cards are byte addressed hence the blockaddress is in multiples of 512 bytes + let address = match card_capacity { + CardCapacity::SDSC => block_idx * 512, + _ => block_idx, + }; + Self::cmd(Cmd::set_block_length(512), false)?; // CMD16 + + let regs = T::regs(); + let on_drop = OnDrop::new(|| Self::on_drop()); + + let transfer = self.prepare_datapath_read(buffer, 512, 9); + InterruptHandler::::data_interrupts(true); + Self::cmd(Cmd::read_single_block(address), true)?; + + let res = poll_fn(|cx| { + T::state().register(cx.waker()); + let status = regs.star().read(); + + if status.dcrcfail() { + return Poll::Ready(Err(Error::Crc)); + } else if status.dtimeout() { + return Poll::Ready(Err(Error::Timeout)); + } else if status.dataend() { + return Poll::Ready(Ok(())); + } + Poll::Pending + }) + .await; + Self::clear_interrupt_flags(); + + if res.is_ok() { + on_drop.defuse(); + Self::stop_datapath(); + drop(transfer); + } + res + } + + pub async fn write_block(&mut self, block_idx: u32, buffer: &DataBlock) -> Result<(), Error> { + let card = self.card.as_mut().ok_or(Error::NoCard)?; + + // NOTE(unsafe) DataBlock uses align 4 + let buffer = unsafe { &*((&buffer.0) as *const [u8; 512] as *const [u32; 128]) }; + + // Always read 1 block of 512 bytes + // SDSC cards are byte addressed hence the blockaddress is in multiples of 512 bytes + let address = match card.card_type { + CardCapacity::SDSC => block_idx * 512, + _ => block_idx, + }; + Self::cmd(Cmd::set_block_length(512), false)?; // CMD16 + + let regs = T::regs(); + let on_drop = OnDrop::new(|| Self::on_drop()); + + // sdmmc_v1 uses different cmd/dma order than v2, but only for writes + #[cfg(sdmmc_v1)] + Self::cmd(Cmd::write_single_block(address), true)?; + + let transfer = self.prepare_datapath_write(buffer, 512, 9); + InterruptHandler::::data_interrupts(true); + + #[cfg(sdmmc_v2)] + Self::cmd(Cmd::write_single_block(address), true)?; + + let res = poll_fn(|cx| { + T::state().register(cx.waker()); + let status = regs.star().read(); + + if status.dcrcfail() { + return Poll::Ready(Err(Error::Crc)); + } else if status.dtimeout() { + return Poll::Ready(Err(Error::Timeout)); + } else if status.dataend() { + return Poll::Ready(Ok(())); + } + Poll::Pending + }) + .await; + Self::clear_interrupt_flags(); + + match res { + Ok(_) => { + on_drop.defuse(); + Self::stop_datapath(); + drop(transfer); + + // TODO: Make this configurable + let mut timeout: u32 = 0x00FF_FFFF; + + // Try to read card status (ACMD13) + while timeout > 0 { + match self.read_sd_status().await { + Ok(_) => return Ok(()), + Err(Error::Timeout) => (), // Try again + Err(e) => return Err(e), + } + timeout -= 1; + } + Err(Error::SoftwareTimeout) + } + Err(e) => Err(e), + } + } + + /// Get a reference to the initialized card + /// + /// # Errors + /// + /// Returns Error::NoCard if [`init_card`](#method.init_card) + /// has not previously succeeded + #[inline(always)] + pub fn card(&self) -> Result<&Card, Error> { + self.card.as_ref().ok_or(Error::NoCard) + } + + /// Get the current SDMMC bus clock + pub fn clock(&self) -> Hertz { + self.clock + } +} + +impl<'d, T: Instance, Dma: SdmmcDma + 'd> Drop for Sdmmc<'d, T, Dma> { + fn drop(&mut self) { + T::Interrupt::disable(); + Self::on_drop(); + + critical_section::with(|_| { + self.clk.set_as_disconnected(); + self.cmd.set_as_disconnected(); + self.d0.set_as_disconnected(); + if let Some(x) = &mut self.d1 { + x.set_as_disconnected(); + } + if let Some(x) = &mut self.d2 { + x.set_as_disconnected(); + } + if let Some(x) = &mut self.d3 { + x.set_as_disconnected(); + } + }); } } @@ -1473,10 +1370,11 @@ pub(crate) mod sealed { use super::*; pub trait Instance { - type Interrupt: Interrupt; + type Interrupt: interrupt::typelevel::Interrupt; - fn inner() -> SdmmcInner; + fn regs() -> RegBlock; fn state() -> &'static AtomicWaker; + fn kernel_clk() -> Hertz; } pub trait Pins {} @@ -1494,100 +1392,133 @@ pin_trait!(D5Pin, Instance); pin_trait!(D6Pin, Instance); pin_trait!(D7Pin, Instance); +#[cfg(sdmmc_v1)] +dma_trait!(SdmmcDma, Instance); + +// SDMMCv2 uses internal DMA +#[cfg(sdmmc_v2)] +pub trait SdmmcDma {} +#[cfg(sdmmc_v2)] +impl SdmmcDma for NoDma {} + cfg_if::cfg_if! { - if #[cfg(sdmmc_v1)] { - dma_trait!(SdmmcDma, Instance); - } else if #[cfg(sdmmc_v2)] { - // SDMMCv2 uses internal DMA - pub trait SdmmcDma {} - impl SdmmcDma for NoDma {} + // TODO, these could not be implemented, because required clocks are not exposed in RCC: + // - H7 uses pll1_q_ck or pll2_r_ck depending on SDMMCSEL + // - L1 uses pll48 + // - L4 uses clk48(pll48) + // - L4+, L5, U5 uses clk48(pll48) or PLLSAI3CLK(PLLP) depending on SDMMCSEL + if #[cfg(stm32f1)] { + // F1 uses AHB1(HCLK), which is correct in PAC + macro_rules! kernel_clk { + ($inst:ident) => { + ::frequency() + } + } + } else if #[cfg(any(stm32f2, stm32f4))] { + // F2, F4 always use pll48 + macro_rules! kernel_clk { + ($inst:ident) => { + critical_section::with(|_| unsafe { + crate::rcc::get_freqs().pll48 + }).expect("PLL48 is required for SDIO") + } + } + } else if #[cfg(stm32f7)] { + macro_rules! kernel_clk { + (SDMMC1) => { + critical_section::with(|_| unsafe { + let sdmmcsel = crate::pac::RCC.dckcfgr2().read().sdmmc1sel(); + if sdmmcsel == crate::pac::rcc::vals::Sdmmcsel::SYSCLK { + crate::rcc::get_freqs().sys + } else { + crate::rcc::get_freqs().pll48.expect("PLL48 is required for SDMMC") + } + }) + }; + (SDMMC2) => { + critical_section::with(|_| unsafe { + let sdmmcsel = crate::pac::RCC.dckcfgr2().read().sdmmc2sel(); + if sdmmcsel == crate::pac::rcc::vals::Sdmmcsel::SYSCLK { + crate::rcc::get_freqs().sys + } else { + crate::rcc::get_freqs().pll48.expect("PLL48 is required for SDMMC") + } + }) + }; + } + } else { + // Use default peripheral clock and hope it works + macro_rules! kernel_clk { + ($inst:ident) => { + ::frequency() + } + } } } foreach_peripheral!( (sdmmc, $inst:ident) => { impl sealed::Instance for peripherals::$inst { - type Interrupt = crate::interrupt::$inst; + type Interrupt = crate::interrupt::typelevel::$inst; - fn inner() -> SdmmcInner { - const INNER: SdmmcInner = SdmmcInner(crate::pac::$inst); - INNER + fn regs() -> RegBlock { + crate::pac::$inst } fn state() -> &'static ::embassy_sync::waitqueue::AtomicWaker { static WAKER: ::embassy_sync::waitqueue::AtomicWaker = ::embassy_sync::waitqueue::AtomicWaker::new(); &WAKER } + + fn kernel_clk() -> Hertz { + kernel_clk!($inst) + } } impl Instance for peripherals::$inst {} }; ); -#[cfg(feature = "sdmmc-rs")] +#[cfg(feature = "embedded-sdmmc")] mod sdmmc_rs { - use core::future::Future; - use embedded_sdmmc::{Block, BlockCount, BlockDevice, BlockIdx}; use super::*; - impl<'d, T: Instance, P: Pins> BlockDevice for Sdmmc<'d, T, P> { + impl<'d, T: Instance, Dma: SdmmcDma> BlockDevice for Sdmmc<'d, T, Dma> { type Error = Error; - type ReadFuture<'a> - where - Self: 'a, - = impl Future> + 'a; - type WriteFuture<'a> - where - Self: 'a, - = impl Future> + 'a; - fn read<'a>( - &'a mut self, - blocks: &'a mut [Block], + async fn read( + &mut self, + blocks: &mut [Block], start_block_idx: BlockIdx, _reason: &str, - ) -> Self::ReadFuture<'a> { - async move { - let card_capacity = self.card()?.card_type; - let inner = T::inner(); - let state = T::state(); - let mut address = start_block_idx.0; + ) -> Result<(), Self::Error> { + let mut address = start_block_idx.0; - for block in blocks.iter_mut() { - let block: &mut [u8; 512] = &mut block.contents; + for block in blocks.iter_mut() { + let block: &mut [u8; 512] = &mut block.contents; - // NOTE(unsafe) Block uses align(4) - let buf = unsafe { &mut *(block as *mut [u8; 512] as *mut [u32; 128]) }; - inner - .read_block(address, buf, card_capacity, state, self.config.data_transfer_timeout) - .await?; - address += 1; - } - Ok(()) + // NOTE(unsafe) Block uses align(4) + let block = unsafe { &mut *(block as *mut _ as *mut DataBlock) }; + self.read_block(address, block).await?; + address += 1; } + Ok(()) } - fn write<'a>(&'a mut self, blocks: &'a [Block], start_block_idx: BlockIdx) -> Self::WriteFuture<'a> { - async move { - let card = self.card.as_mut().ok_or(Error::NoCard)?; - let inner = T::inner(); - let state = T::state(); - let mut address = start_block_idx.0; + async fn write(&mut self, blocks: &[Block], start_block_idx: BlockIdx) -> Result<(), Self::Error> { + let mut address = start_block_idx.0; - for block in blocks.iter() { - let block: &[u8; 512] = &block.contents; + for block in blocks.iter() { + let block: &[u8; 512] = &block.contents; - // NOTE(unsafe) DataBlock uses align 4 - let buf = unsafe { &*(block as *const [u8; 512] as *const [u32; 128]) }; - inner - .write_block(address, buf, card, state, self.config.data_transfer_timeout) - .await?; - address += 1; - } - Ok(()) + // NOTE(unsafe) DataBlock uses align 4 + let block = unsafe { &*(block as *const _ as *const DataBlock) }; + self.write_block(address, block).await?; + address += 1; } + Ok(()) } fn num_blocks(&self) -> Result { diff --git a/embassy-stm32/src/spi/mod.rs b/embassy-stm32/src/spi/mod.rs index 26fb392ef..d5f63f84e 100644 --- a/embassy-stm32/src/spi/mod.rs +++ b/embassy-stm32/src/spi/mod.rs @@ -3,14 +3,13 @@ use core::ptr; use embassy_embedded_hal::SetConfig; +use embassy_futures::join::join; use embassy_hal_common::{into_ref, PeripheralRef}; pub use embedded_hal_02::spi::{Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3}; -use futures::future::join; -use self::sealed::WordSize; -use crate::dma::{slice_ptr_parts, NoDma, Transfer}; +use crate::dma::{slice_ptr_parts, word, Transfer}; use crate::gpio::sealed::{AFType, Pin as _}; -use crate::gpio::AnyPin; +use crate::gpio::{AnyPin, Pull}; use crate::pac::spi::{regs, vals, Spi as Regs}; use crate::rcc::RccPeripheral; use crate::time::Hertz; @@ -78,7 +77,7 @@ pub struct Spi<'d, T: Instance, Tx, Rx> { miso: Option>, txdma: PeripheralRef<'d, Tx>, rxdma: PeripheralRef<'d, Rx>, - current_word_size: WordSize, + current_word_size: word_impl::Config, } impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { @@ -93,17 +92,18 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { config: Config, ) -> Self { into_ref!(peri, sck, mosi, miso); - unsafe { - sck.set_as_af(sck.af_num(), AFType::OutputPushPull); - #[cfg(any(spi_v2, spi_v3, spi_v4))] - sck.set_speed(crate::gpio::Speed::VeryHigh); - mosi.set_as_af(mosi.af_num(), AFType::OutputPushPull); - #[cfg(any(spi_v2, spi_v3, spi_v4))] - mosi.set_speed(crate::gpio::Speed::VeryHigh); - miso.set_as_af(miso.af_num(), AFType::Input); - #[cfg(any(spi_v2, spi_v3, spi_v4))] - miso.set_speed(crate::gpio::Speed::VeryHigh); - } + + let sck_pull_mode = match config.mode.polarity { + Polarity::IdleLow => Pull::Down, + Polarity::IdleHigh => Pull::Up, + }; + + sck.set_as_af_pull(sck.af_num(), AFType::OutputPushPull, sck_pull_mode); + sck.set_speed(crate::gpio::Speed::VeryHigh); + mosi.set_as_af(mosi.af_num(), AFType::OutputPushPull); + mosi.set_speed(crate::gpio::Speed::VeryHigh); + miso.set_as_af(miso.af_num(), AFType::Input); + miso.set_speed(crate::gpio::Speed::VeryHigh); Self::new_inner( peri, @@ -127,14 +127,10 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { config: Config, ) -> Self { into_ref!(sck, miso); - unsafe { - sck.set_as_af(sck.af_num(), AFType::OutputPushPull); - #[cfg(any(spi_v2, spi_v3, spi_v4))] - sck.set_speed(crate::gpio::Speed::VeryHigh); - miso.set_as_af(miso.af_num(), AFType::Input); - #[cfg(any(spi_v2, spi_v3, spi_v4))] - miso.set_speed(crate::gpio::Speed::VeryHigh); - } + sck.set_as_af(sck.af_num(), AFType::OutputPushPull); + sck.set_speed(crate::gpio::Speed::VeryHigh); + miso.set_as_af(miso.af_num(), AFType::Input); + miso.set_speed(crate::gpio::Speed::VeryHigh); Self::new_inner( peri, @@ -158,14 +154,10 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { config: Config, ) -> Self { into_ref!(sck, mosi); - unsafe { - sck.set_as_af(sck.af_num(), AFType::OutputPushPull); - #[cfg(any(spi_v2, spi_v3, spi_v4))] - sck.set_speed(crate::gpio::Speed::VeryHigh); - mosi.set_as_af(mosi.af_num(), AFType::OutputPushPull); - #[cfg(any(spi_v2, spi_v3, spi_v4))] - mosi.set_speed(crate::gpio::Speed::VeryHigh); - } + sck.set_as_af(sck.af_num(), AFType::OutputPushPull); + sck.set_speed(crate::gpio::Speed::VeryHigh); + mosi.set_as_af(mosi.af_num(), AFType::OutputPushPull); + mosi.set_speed(crate::gpio::Speed::VeryHigh); Self::new_inner( peri, @@ -179,6 +171,50 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { ) } + pub fn new_txonly_nosck( + peri: impl Peripheral

+ 'd, + mosi: impl Peripheral

> + 'd, + txdma: impl Peripheral

+ 'd, + rxdma: impl Peripheral

+ 'd, // TODO: remove + freq: Hertz, + config: Config, + ) -> Self { + into_ref!(mosi); + mosi.set_as_af_pull(mosi.af_num(), AFType::OutputPushPull, Pull::Down); + mosi.set_speed(crate::gpio::Speed::Medium); + + Self::new_inner(peri, None, Some(mosi.map_into()), None, txdma, rxdma, freq, config) + } + + #[cfg(stm32wl)] + /// Useful for on chip peripherals like SUBGHZ which are hardwired. + pub fn new_subghz( + peri: impl Peripheral

+ 'd, + txdma: impl Peripheral

+ 'd, + rxdma: impl Peripheral

+ 'd, + ) -> Self { + // see RM0453 rev 1 section 7.2.13 page 291 + // The SUBGHZSPI_SCK frequency is obtained by PCLK3 divided by two. + // The SUBGHZSPI_SCK clock maximum speed must not exceed 16 MHz. + let pclk3_freq = ::frequency().0; + let freq = Hertz(core::cmp::min(pclk3_freq / 2, 16_000_000)); + let mut config = Config::default(); + config.mode = MODE_0; + config.bit_order = BitOrder::MsbFirst; + Self::new_inner(peri, None, None, None, txdma, rxdma, freq, config) + } + + #[allow(dead_code)] + pub(crate) fn new_internal( + peri: impl Peripheral

+ 'd, + txdma: impl Peripheral

+ 'd, + rxdma: impl Peripheral

+ 'd, + freq: Hertz, + config: Config, + ) -> Self { + Self::new_inner(peri, None, None, None, txdma, rxdma, freq, config) + } + fn new_inner( peri: impl Peripheral

+ 'd, sck: Option>, @@ -203,7 +239,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { T::reset(); #[cfg(any(spi_v1, spi_f1))] - unsafe { + { T::REGS.cr2().modify(|w| { w.set_ssoe(false); }); @@ -222,14 +258,15 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { if mosi.is_none() { w.set_rxonly(vals::Rxonly::OUTPUTDISABLED); } - w.set_dff(WordSize::EightBit.dff()) + w.set_dff(::CONFIG) }); } #[cfg(spi_v2)] - unsafe { + { T::REGS.cr2().modify(|w| { - w.set_frxth(WordSize::EightBit.frxth()); - w.set_ds(WordSize::EightBit.ds()); + let (ds, frxth) = ::CONFIG; + w.set_frxth(frxth); + w.set_ds(ds); w.set_ssoe(false); }); T::REGS.cr1().modify(|w| { @@ -246,8 +283,8 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { w.set_spe(true); }); } - #[cfg(any(spi_v3, spi_v4))] - unsafe { + #[cfg(any(spi_v3, spi_v4, spi_v5))] + { T::REGS.ifcr().write(|w| w.0 = 0xffff_ffff); T::REGS.cfg2().modify(|w| { //w.set_ssoe(true); @@ -267,7 +304,8 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { T::REGS.cfg1().modify(|w| { w.set_crcen(false); w.set_mbr(br); - w.set_dsize(WordSize::EightBit.dsize()); + w.set_dsize(::CONFIG); + w.set_fthlv(vals::Fthlv::ONEFRAME); }); T::REGS.cr2().modify(|w| { w.set_tsize(0); @@ -285,7 +323,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { miso, txdma, rxdma, - current_word_size: WordSize::EightBit, + current_word_size: ::CONFIG, } } @@ -297,29 +335,25 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { let lsbfirst = config.raw_byte_order(); #[cfg(any(spi_v1, spi_f1, spi_v2))] - unsafe { - T::REGS.cr1().modify(|w| { - w.set_cpha(cpha); - w.set_cpol(cpol); - w.set_lsbfirst(lsbfirst); - }); - } + T::REGS.cr1().modify(|w| { + w.set_cpha(cpha); + w.set_cpol(cpol); + w.set_lsbfirst(lsbfirst); + }); - #[cfg(any(spi_v3, spi_v4))] - unsafe { - T::REGS.cfg2().modify(|w| { - w.set_cpha(cpha); - w.set_cpol(cpol); - w.set_lsbfirst(lsbfirst); - }); - } + #[cfg(any(spi_v3, spi_v4, spi_v5))] + T::REGS.cfg2().modify(|w| { + w.set_cpha(cpha); + w.set_cpol(cpol); + w.set_lsbfirst(lsbfirst); + }); } pub fn get_current_config(&self) -> Config { #[cfg(any(spi_v1, spi_f1, spi_v2))] - let cfg = unsafe { T::REGS.cr1().read() }; - #[cfg(any(spi_v3, spi_v4))] - let cfg = unsafe { T::REGS.cfg2().read() }; + let cfg = T::REGS.cr1().read(); + #[cfg(any(spi_v3, spi_v4, spi_v5))] + let cfg = T::REGS.cfg2().read(); let polarity = if cfg.cpol() == vals::Cpol::IDLELOW { Polarity::IdleLow } else { @@ -343,36 +377,36 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { } } - fn set_word_size(&mut self, word_size: WordSize) { + fn set_word_size(&mut self, word_size: word_impl::Config) { if self.current_word_size == word_size { return; } #[cfg(any(spi_v1, spi_f1))] - unsafe { + { T::REGS.cr1().modify(|reg| { reg.set_spe(false); - reg.set_dff(word_size.dff()) + reg.set_dff(word_size) }); T::REGS.cr1().modify(|reg| { reg.set_spe(true); }); } #[cfg(spi_v2)] - unsafe { + { T::REGS.cr1().modify(|w| { w.set_spe(false); }); T::REGS.cr2().modify(|w| { - w.set_frxth(word_size.frxth()); - w.set_ds(word_size.ds()); + w.set_frxth(word_size.1); + w.set_ds(word_size.0); }); T::REGS.cr1().modify(|w| { w.set_spe(true); }); } - #[cfg(any(spi_v3, spi_v4))] - unsafe { + #[cfg(any(spi_v3, spi_v4, spi_v5))] + { T::REGS.cr1().modify(|w| { w.set_csusp(true); }); @@ -381,7 +415,7 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { w.set_spe(false); }); T::REGS.cfg1().modify(|w| { - w.set_dsize(word_size.dsize()); + w.set_dsize(word_size); }); T::REGS.cr1().modify(|w| { w.set_csusp(false); @@ -400,28 +434,23 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { return Ok(()); } - self.set_word_size(W::WORDSIZE); - unsafe { - T::REGS.cr1().modify(|w| { - w.set_spe(false); - }); - } + self.set_word_size(W::CONFIG); + T::REGS.cr1().modify(|w| { + w.set_spe(false); + }); let tx_request = self.txdma.request(); let tx_dst = T::REGS.tx_ptr(); - unsafe { self.txdma.start_write(tx_request, data, tx_dst, Default::default()) } - let tx_f = Transfer::new(&mut self.txdma); + let tx_f = unsafe { Transfer::new_write(&mut self.txdma, tx_request, data, tx_dst, Default::default()) }; - unsafe { - set_txdmaen(T::REGS, true); - T::REGS.cr1().modify(|w| { - w.set_spe(true); - }); - #[cfg(any(spi_v3, spi_v4))] - T::REGS.cr1().modify(|w| { - w.set_cstart(true); - }); - } + set_txdmaen(T::REGS, true); + T::REGS.cr1().modify(|w| { + w.set_spe(true); + }); + #[cfg(any(spi_v3, spi_v4, spi_v5))] + T::REGS.cr1().modify(|w| { + w.set_cstart(true); + }); tx_f.await; @@ -439,40 +468,45 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { return Ok(()); } - self.set_word_size(W::WORDSIZE); - unsafe { - T::REGS.cr1().modify(|w| { - w.set_spe(false); - }); - set_rxdmaen(T::REGS, true); - } + self.set_word_size(W::CONFIG); + T::REGS.cr1().modify(|w| { + w.set_spe(false); + }); // SPIv3 clears rxfifo on SPE=0 - #[cfg(not(any(spi_v3, spi_v4)))] + #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] flush_rx_fifo(T::REGS); + set_rxdmaen(T::REGS, true); + let clock_byte_count = data.len(); let rx_request = self.rxdma.request(); let rx_src = T::REGS.rx_ptr(); - unsafe { self.rxdma.start_read(rx_request, rx_src, data, Default::default()) }; - let rx_f = Transfer::new(&mut self.rxdma); + let rx_f = unsafe { Transfer::new_read(&mut self.rxdma, rx_request, rx_src, data, Default::default()) }; let tx_request = self.txdma.request(); let tx_dst = T::REGS.tx_ptr(); let clock_byte = 0x00u8; - let tx_f = crate::dma::write_repeated(&mut self.txdma, tx_request, clock_byte, clock_byte_count, tx_dst); + let tx_f = unsafe { + Transfer::new_write_repeated( + &mut self.txdma, + tx_request, + &clock_byte, + clock_byte_count, + tx_dst, + Default::default(), + ) + }; - unsafe { - set_txdmaen(T::REGS, true); - T::REGS.cr1().modify(|w| { - w.set_spe(true); - }); - #[cfg(any(spi_v3, spi_v4))] - T::REGS.cr1().modify(|w| { - w.set_cstart(true); - }); - } + set_txdmaen(T::REGS, true); + T::REGS.cr1().modify(|w| { + w.set_spe(true); + }); + #[cfg(any(spi_v3, spi_v4, spi_v5))] + T::REGS.cr1().modify(|w| { + w.set_cstart(true); + }); join(tx_f, rx_f).await; @@ -493,38 +527,33 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { return Ok(()); } - self.set_word_size(W::WORDSIZE); - unsafe { - T::REGS.cr1().modify(|w| { - w.set_spe(false); - }); - set_rxdmaen(T::REGS, true); - } + self.set_word_size(W::CONFIG); + T::REGS.cr1().modify(|w| { + w.set_spe(false); + }); // SPIv3 clears rxfifo on SPE=0 - #[cfg(not(any(spi_v3, spi_v4)))] + #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] flush_rx_fifo(T::REGS); + set_rxdmaen(T::REGS, true); + let rx_request = self.rxdma.request(); let rx_src = T::REGS.rx_ptr(); - unsafe { self.rxdma.start_read(rx_request, rx_src, read, Default::default()) }; - let rx_f = Transfer::new(&mut self.rxdma); + let rx_f = unsafe { Transfer::new_read_raw(&mut self.rxdma, rx_request, rx_src, read, Default::default()) }; let tx_request = self.txdma.request(); let tx_dst = T::REGS.tx_ptr(); - unsafe { self.txdma.start_write(tx_request, write, tx_dst, Default::default()) } - let tx_f = Transfer::new(&mut self.txdma); + let tx_f = unsafe { Transfer::new_write_raw(&mut self.txdma, tx_request, write, tx_dst, Default::default()) }; - unsafe { - set_txdmaen(T::REGS, true); - T::REGS.cr1().modify(|w| { - w.set_spe(true); - }); - #[cfg(any(spi_v3, spi_v4))] - T::REGS.cr1().modify(|w| { - w.set_cstart(true); - }); - } + set_txdmaen(T::REGS, true); + T::REGS.cr1().modify(|w| { + w.set_spe(true); + }); + #[cfg(any(spi_v3, spi_v4, spi_v5))] + T::REGS.cr1().modify(|w| { + w.set_cstart(true); + }); join(tx_f, rx_f).await; @@ -550,9 +579,9 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { } pub fn blocking_write(&mut self, words: &[W]) -> Result<(), Error> { - unsafe { T::REGS.cr1().modify(|w| w.set_spe(true)) } + T::REGS.cr1().modify(|w| w.set_spe(true)); flush_rx_fifo(T::REGS); - self.set_word_size(W::WORDSIZE); + self.set_word_size(W::CONFIG); for word in words.iter() { let _ = transfer_word(T::REGS, *word)?; } @@ -560,9 +589,9 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { } pub fn blocking_read(&mut self, words: &mut [W]) -> Result<(), Error> { - unsafe { T::REGS.cr1().modify(|w| w.set_spe(true)) } + T::REGS.cr1().modify(|w| w.set_spe(true)); flush_rx_fifo(T::REGS); - self.set_word_size(W::WORDSIZE); + self.set_word_size(W::CONFIG); for word in words.iter_mut() { *word = transfer_word(T::REGS, W::default())?; } @@ -570,9 +599,9 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { } pub fn blocking_transfer_in_place(&mut self, words: &mut [W]) -> Result<(), Error> { - unsafe { T::REGS.cr1().modify(|w| w.set_spe(true)) } + T::REGS.cr1().modify(|w| w.set_spe(true)); flush_rx_fifo(T::REGS); - self.set_word_size(W::WORDSIZE); + self.set_word_size(W::CONFIG); for word in words.iter_mut() { *word = transfer_word(T::REGS, *word)?; } @@ -580,9 +609,9 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { } pub fn blocking_transfer(&mut self, read: &mut [W], write: &[W]) -> Result<(), Error> { - unsafe { T::REGS.cr1().modify(|w| w.set_spe(true)) } + T::REGS.cr1().modify(|w| w.set_spe(true)); flush_rx_fifo(T::REGS); - self.set_word_size(W::WORDSIZE); + self.set_word_size(W::CONFIG); let len = read.len().max(write.len()); for i in 0..len { let wb = write.get(i).copied().unwrap_or_default(); @@ -597,17 +626,15 @@ impl<'d, T: Instance, Tx, Rx> Spi<'d, T, Tx, Rx> { impl<'d, T: Instance, Tx, Rx> Drop for Spi<'d, T, Tx, Rx> { fn drop(&mut self) { - unsafe { - self.sck.as_ref().map(|x| x.set_as_disconnected()); - self.mosi.as_ref().map(|x| x.set_as_disconnected()); - self.miso.as_ref().map(|x| x.set_as_disconnected()); - } + self.sck.as_ref().map(|x| x.set_as_disconnected()); + self.mosi.as_ref().map(|x| x.set_as_disconnected()); + self.miso.as_ref().map(|x| x.set_as_disconnected()); } } -#[cfg(not(any(spi_v3, spi_v4)))] +#[cfg(not(any(spi_v3, spi_v4, spi_v5)))] use vals::Br; -#[cfg(any(spi_v3, spi_v4))] +#[cfg(any(spi_v3, spi_v4, spi_v5))] use vals::Mbr as Br; fn compute_baud_rate(clocks: Hertz, freq: Hertz) -> Br { @@ -623,7 +650,7 @@ fn compute_baud_rate(clocks: Hertz, freq: Hertz) -> Br { _ => 0b111, }; - Br(val) + Br::from_bits(val) } trait RegsExt { @@ -633,19 +660,19 @@ trait RegsExt { impl RegsExt for Regs { fn tx_ptr(&self) -> *mut W { - #[cfg(not(any(spi_v3, spi_v4)))] + #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] let dr = self.dr(); - #[cfg(any(spi_v3, spi_v4))] + #[cfg(any(spi_v3, spi_v4, spi_v5))] let dr = self.txdr(); - dr.ptr() as *mut W + dr.as_ptr() as *mut W } fn rx_ptr(&self) -> *mut W { - #[cfg(not(any(spi_v3, spi_v4)))] + #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] let dr = self.dr(); - #[cfg(any(spi_v3, spi_v4))] + #[cfg(any(spi_v3, spi_v4, spi_v5))] let dr = self.rxdr(); - dr.ptr() as *mut W + dr.as_ptr() as *mut W } } @@ -653,22 +680,22 @@ fn check_error_flags(sr: regs::Sr) -> Result<(), Error> { if sr.ovr() { return Err(Error::Overrun); } - #[cfg(not(any(spi_f1, spi_v3, spi_v4)))] + #[cfg(not(any(spi_f1, spi_v3, spi_v4, spi_v5)))] if sr.fre() { return Err(Error::Framing); } - #[cfg(any(spi_v3, spi_v4))] + #[cfg(any(spi_v3, spi_v4, spi_v5))] if sr.tifre() { return Err(Error::Framing); } if sr.modf() { return Err(Error::ModeFault); } - #[cfg(not(any(spi_v3, spi_v4)))] + #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] if sr.crcerr() { return Err(Error::Crc); } - #[cfg(any(spi_v3, spi_v4))] + #[cfg(any(spi_v3, spi_v4, spi_v5))] if sr.crce() { return Err(Error::Crc); } @@ -678,15 +705,15 @@ fn check_error_flags(sr: regs::Sr) -> Result<(), Error> { fn spin_until_tx_ready(regs: Regs) -> Result<(), Error> { loop { - let sr = unsafe { regs.sr().read() }; + let sr = regs.sr().read(); check_error_flags(sr)?; - #[cfg(not(any(spi_v3, spi_v4)))] + #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] if sr.txe() { return Ok(()); } - #[cfg(any(spi_v3, spi_v4))] + #[cfg(any(spi_v3, spi_v4, spi_v5))] if sr.txp() { return Ok(()); } @@ -695,15 +722,15 @@ fn spin_until_tx_ready(regs: Regs) -> Result<(), Error> { fn spin_until_rx_ready(regs: Regs) -> Result<(), Error> { loop { - let sr = unsafe { regs.sr().read() }; + let sr = regs.sr().read(); check_error_flags(sr)?; - #[cfg(not(any(spi_v3, spi_v4)))] + #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] if sr.rxne() { return Ok(()); } - #[cfg(any(spi_v3, spi_v4))] + #[cfg(any(spi_v3, spi_v4, spi_v5))] if sr.rxp() { return Ok(()); } @@ -711,69 +738,64 @@ fn spin_until_rx_ready(regs: Regs) -> Result<(), Error> { } fn flush_rx_fifo(regs: Regs) { - unsafe { - #[cfg(not(any(spi_v3, spi_v4)))] - while regs.sr().read().rxne() { - let _ = regs.dr().read(); - } - #[cfg(any(spi_v3, spi_v4))] - while regs.sr().read().rxp() { - let _ = regs.rxdr().read(); - } + #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] + while regs.sr().read().rxne() { + let _ = regs.dr().read(); + } + #[cfg(any(spi_v3, spi_v4, spi_v5))] + while regs.sr().read().rxp() { + let _ = regs.rxdr().read(); } } fn set_txdmaen(regs: Regs, val: bool) { - unsafe { - #[cfg(not(any(spi_v3, spi_v4)))] - regs.cr2().modify(|reg| { - reg.set_txdmaen(val); - }); - #[cfg(any(spi_v3, spi_v4))] - regs.cfg1().modify(|reg| { - reg.set_txdmaen(val); - }); - } + #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] + regs.cr2().modify(|reg| { + reg.set_txdmaen(val); + }); + #[cfg(any(spi_v3, spi_v4, spi_v5))] + regs.cfg1().modify(|reg| { + reg.set_txdmaen(val); + }); } fn set_rxdmaen(regs: Regs, val: bool) { - unsafe { - #[cfg(not(any(spi_v3, spi_v4)))] - regs.cr2().modify(|reg| { - reg.set_rxdmaen(val); - }); - #[cfg(any(spi_v3, spi_v4))] - regs.cfg1().modify(|reg| { - reg.set_rxdmaen(val); - }); - } + #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] + regs.cr2().modify(|reg| { + reg.set_rxdmaen(val); + }); + #[cfg(any(spi_v3, spi_v4, spi_v5))] + regs.cfg1().modify(|reg| { + reg.set_rxdmaen(val); + }); } fn finish_dma(regs: Regs) { - unsafe { - #[cfg(spi_v2)] - while regs.sr().read().ftlvl() > 0 {} + #[cfg(spi_v2)] + while regs.sr().read().ftlvl().to_bits() > 0 {} - #[cfg(any(spi_v3, spi_v4))] - while !regs.sr().read().txc() {} - #[cfg(not(any(spi_v3, spi_v4)))] - while regs.sr().read().bsy() {} + #[cfg(any(spi_v3, spi_v4, spi_v5))] + while !regs.sr().read().txc() {} + #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] + while regs.sr().read().bsy() {} - regs.cr1().modify(|w| { - w.set_spe(false); - }); + // Disable the spi peripheral + regs.cr1().modify(|w| { + w.set_spe(false); + }); - #[cfg(not(any(spi_v3, spi_v4)))] - regs.cr2().modify(|reg| { - reg.set_txdmaen(false); - reg.set_rxdmaen(false); - }); - #[cfg(any(spi_v3, spi_v4))] - regs.cfg1().modify(|reg| { - reg.set_txdmaen(false); - reg.set_rxdmaen(false); - }); - } + // The peripheral automatically disables the DMA stream on completion without error, + // but it does not clear the RXDMAEN/TXDMAEN flag in CR2. + #[cfg(not(any(spi_v3, spi_v4, spi_v5)))] + regs.cr2().modify(|reg| { + reg.set_txdmaen(false); + reg.set_rxdmaen(false); + }); + #[cfg(any(spi_v3, spi_v4, spi_v5))] + regs.cfg1().modify(|reg| { + reg.set_txdmaen(false); + reg.set_rxdmaen(false); + }); } fn transfer_word(regs: Regs, tx_word: W) -> Result { @@ -782,7 +804,7 @@ fn transfer_word(regs: Regs, tx_word: W) -> Result { unsafe { ptr::write_volatile(regs.tx_ptr(), tx_word); - #[cfg(any(spi_v3, spi_v4))] + #[cfg(any(spi_v3, spi_v4, spi_v5))] regs.cr1().modify(|reg| reg.set_cstart(true)); } @@ -799,7 +821,7 @@ mod eh02 { // some marker traits. For details, see https://github.com/rust-embedded/embedded-hal/pull/289 macro_rules! impl_blocking { ($w:ident) => { - impl<'d, T: Instance> embedded_hal_02::blocking::spi::Write<$w> for Spi<'d, T, NoDma, NoDma> { + impl<'d, T: Instance, Tx, Rx> embedded_hal_02::blocking::spi::Write<$w> for Spi<'d, T, Tx, Rx> { type Error = Error; fn write(&mut self, words: &[$w]) -> Result<(), Self::Error> { @@ -807,7 +829,7 @@ mod eh02 { } } - impl<'d, T: Instance> embedded_hal_02::blocking::spi::Transfer<$w> for Spi<'d, T, NoDma, NoDma> { + impl<'d, T: Instance, Tx, Rx> embedded_hal_02::blocking::spi::Transfer<$w> for Spi<'d, T, Tx, Rx> { type Error = Error; fn transfer<'w>(&mut self, words: &'w mut [$w]) -> Result<&'w [$w], Self::Error> { @@ -830,25 +852,19 @@ mod eh1 { type Error = Error; } - impl<'d, T: Instance, Tx, Rx> embedded_hal_1::spi::blocking::SpiBusFlush for Spi<'d, T, Tx, Rx> { + impl<'d, T: Instance, W: Word, Tx, Rx> embedded_hal_1::spi::SpiBus for Spi<'d, T, Tx, Rx> { fn flush(&mut self) -> Result<(), Self::Error> { Ok(()) } - } - impl<'d, T: Instance, W: Word> embedded_hal_1::spi::blocking::SpiBusRead for Spi<'d, T, NoDma, NoDma> { fn read(&mut self, words: &mut [W]) -> Result<(), Self::Error> { self.blocking_read(words) } - } - impl<'d, T: Instance, W: Word> embedded_hal_1::spi::blocking::SpiBusWrite for Spi<'d, T, NoDma, NoDma> { fn write(&mut self, words: &[W]) -> Result<(), Self::Error> { self.blocking_write(words) } - } - impl<'d, T: Instance, W: Word> embedded_hal_1::spi::blocking::SpiBus for Spi<'d, T, NoDma, NoDma> { fn transfer(&mut self, read: &mut [W], write: &[W]) -> Result<(), Self::Error> { self.blocking_transfer(read, write) } @@ -870,54 +886,29 @@ mod eh1 { } } -cfg_if::cfg_if! { - if #[cfg(all(feature = "unstable-traits", feature = "nightly"))] { - use core::future::Future; - impl<'d, T: Instance, Tx, Rx> embedded_hal_async::spi::SpiBusFlush for Spi<'d, T, Tx, Rx> { - type FlushFuture<'a> = impl Future> + 'a where Self: 'a; +#[cfg(all(feature = "unstable-traits", feature = "nightly"))] +mod eha { + use super::*; - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - async { Ok(()) } - } + impl<'d, T: Instance, Tx: TxDma, Rx: RxDma, W: Word> embedded_hal_async::spi::SpiBus for Spi<'d, T, Tx, Rx> { + async fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) } - impl<'d, T: Instance, Tx: TxDma, Rx, W: Word> embedded_hal_async::spi::SpiBusWrite - for Spi<'d, T, Tx, Rx> - { - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - - fn write<'a>(&'a mut self, data: &'a [W]) -> Self::WriteFuture<'a> { - self.write(data) - } + async fn write(&mut self, words: &[W]) -> Result<(), Self::Error> { + self.write(words).await } - impl<'d, T: Instance, Tx: TxDma, Rx: RxDma, W: Word> embedded_hal_async::spi::SpiBusRead - for Spi<'d, T, Tx, Rx> - { - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn read<'a>(&'a mut self, data: &'a mut [W]) -> Self::ReadFuture<'a> { - self.read(data) - } + async fn read(&mut self, words: &mut [W]) -> Result<(), Self::Error> { + self.read(words).await } - impl<'d, T: Instance, Tx: TxDma, Rx: RxDma, W: Word> embedded_hal_async::spi::SpiBus - for Spi<'d, T, Tx, Rx> - { - type TransferFuture<'a> = impl Future> + 'a where Self: 'a; + async fn transfer(&mut self, read: &mut [W], write: &[W]) -> Result<(), Self::Error> { + self.transfer(read, write).await + } - fn transfer<'a>(&'a mut self, rx: &'a mut [W], tx: &'a [W]) -> Self::TransferFuture<'a> { - self.transfer(rx, tx) - } - - type TransferInPlaceFuture<'a> = impl Future> + 'a where Self: 'a; - - fn transfer_in_place<'a>( - &'a mut self, - words: &'a mut [W], - ) -> Self::TransferInPlaceFuture<'a> { - self.transfer_in_place(words) - } + async fn transfer_in_place(&mut self, words: &mut [W]) -> Result<(), Self::Error> { + self.transfer_in_place(words).await } } } @@ -929,75 +920,98 @@ pub(crate) mod sealed { const REGS: Regs; } - pub trait Word: Copy + 'static { - const WORDSIZE: WordSize; - } - - impl Word for u8 { - const WORDSIZE: WordSize = WordSize::EightBit; - } - impl Word for u16 { - const WORDSIZE: WordSize = WordSize::SixteenBit; - } - - #[derive(Copy, Clone, PartialOrd, PartialEq)] - pub enum WordSize { - EightBit, - SixteenBit, - } - - impl WordSize { - #[cfg(any(spi_v1, spi_f1))] - pub fn dff(&self) -> vals::Dff { - match self { - WordSize::EightBit => vals::Dff::EIGHTBIT, - WordSize::SixteenBit => vals::Dff::SIXTEENBIT, - } - } - - #[cfg(spi_v2)] - pub fn ds(&self) -> vals::Ds { - match self { - WordSize::EightBit => vals::Ds::EIGHTBIT, - WordSize::SixteenBit => vals::Ds::SIXTEENBIT, - } - } - - #[cfg(spi_v2)] - pub fn frxth(&self) -> vals::Frxth { - match self { - WordSize::EightBit => vals::Frxth::QUARTER, - WordSize::SixteenBit => vals::Frxth::HALF, - } - } - - #[cfg(any(spi_v3, spi_v4))] - pub fn dsize(&self) -> u8 { - match self { - WordSize::EightBit => 0b0111, - WordSize::SixteenBit => 0b1111, - } - } - - #[cfg(any(spi_v3, spi_v4))] - pub fn _frxth(&self) -> vals::Fthlv { - match self { - WordSize::EightBit => vals::Fthlv::ONEFRAME, - WordSize::SixteenBit => vals::Fthlv::ONEFRAME, - } - } + pub trait Word { + const CONFIG: word_impl::Config; } } -pub trait Word: Copy + 'static + sealed::Word + Default + crate::dma::Word {} +pub trait Word: word::Word + sealed::Word {} -impl Word for u8 {} -impl Word for u16 {} +macro_rules! impl_word { + ($T:ty, $config:expr) => { + impl sealed::Word for $T { + const CONFIG: Config = $config; + } + impl Word for $T {} + }; +} + +#[cfg(any(spi_v1, spi_f1))] +mod word_impl { + use super::*; + + pub type Config = vals::Dff; + + impl_word!(u8, vals::Dff::EIGHTBIT); + impl_word!(u16, vals::Dff::SIXTEENBIT); +} + +#[cfg(any(spi_v2))] +mod word_impl { + use super::*; + + pub type Config = (vals::Ds, vals::Frxth); + + impl_word!(word::U4, (vals::Ds::FOURBIT, vals::Frxth::QUARTER)); + impl_word!(word::U5, (vals::Ds::FIVEBIT, vals::Frxth::QUARTER)); + impl_word!(word::U6, (vals::Ds::SIXBIT, vals::Frxth::QUARTER)); + impl_word!(word::U7, (vals::Ds::SEVENBIT, vals::Frxth::QUARTER)); + impl_word!(u8, (vals::Ds::EIGHTBIT, vals::Frxth::QUARTER)); + impl_word!(word::U9, (vals::Ds::NINEBIT, vals::Frxth::HALF)); + impl_word!(word::U10, (vals::Ds::TENBIT, vals::Frxth::HALF)); + impl_word!(word::U11, (vals::Ds::ELEVENBIT, vals::Frxth::HALF)); + impl_word!(word::U12, (vals::Ds::TWELVEBIT, vals::Frxth::HALF)); + impl_word!(word::U13, (vals::Ds::THIRTEENBIT, vals::Frxth::HALF)); + impl_word!(word::U14, (vals::Ds::FOURTEENBIT, vals::Frxth::HALF)); + impl_word!(word::U15, (vals::Ds::FIFTEENBIT, vals::Frxth::HALF)); + impl_word!(u16, (vals::Ds::SIXTEENBIT, vals::Frxth::HALF)); +} + +#[cfg(any(spi_v3, spi_v4, spi_v5))] +mod word_impl { + use super::*; + + pub type Config = u8; + + impl_word!(word::U4, 4 - 1); + impl_word!(word::U5, 5 - 1); + impl_word!(word::U6, 6 - 1); + impl_word!(word::U7, 7 - 1); + impl_word!(u8, 8 - 1); + impl_word!(word::U9, 9 - 1); + impl_word!(word::U10, 10 - 1); + impl_word!(word::U11, 11 - 1); + impl_word!(word::U12, 12 - 1); + impl_word!(word::U13, 13 - 1); + impl_word!(word::U14, 14 - 1); + impl_word!(word::U15, 15 - 1); + impl_word!(u16, 16 - 1); + impl_word!(word::U17, 17 - 1); + impl_word!(word::U18, 18 - 1); + impl_word!(word::U19, 19 - 1); + impl_word!(word::U20, 20 - 1); + impl_word!(word::U21, 21 - 1); + impl_word!(word::U22, 22 - 1); + impl_word!(word::U23, 23 - 1); + impl_word!(word::U24, 24 - 1); + impl_word!(word::U25, 25 - 1); + impl_word!(word::U26, 26 - 1); + impl_word!(word::U27, 27 - 1); + impl_word!(word::U28, 28 - 1); + impl_word!(word::U29, 29 - 1); + impl_word!(word::U30, 30 - 1); + impl_word!(word::U31, 31 - 1); + impl_word!(u32, 32 - 1); +} pub trait Instance: Peripheral

+ sealed::Instance + RccPeripheral {} pin_trait!(SckPin, Instance); pin_trait!(MosiPin, Instance); pin_trait!(MisoPin, Instance); +pin_trait!(CsPin, Instance); +pin_trait!(MckPin, Instance); +pin_trait!(CkPin, Instance); +pin_trait!(WsPin, Instance); dma_trait!(RxDma, Instance); dma_trait!(TxDma, Instance); diff --git a/embassy-stm32/src/subghz/bit_sync.rs b/embassy-stm32/src/subghz/bit_sync.rs deleted file mode 100644 index f3cba05f9..000000000 --- a/embassy-stm32/src/subghz/bit_sync.rs +++ /dev/null @@ -1,160 +0,0 @@ -/// Bit synchronization. -/// -/// This must be cleared to `0x00` (the reset value) when using packet types -/// other than LoRa. -/// -/// Argument of [`set_bit_sync`](crate::subghz::SubGhz::set_bit_sync). -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct BitSync { - val: u8, -} - -impl BitSync { - /// Bit synchronization register reset value. - pub const RESET: BitSync = BitSync { val: 0x00 }; - - /// Create a new [`BitSync`] structure from a raw value. - /// - /// Reserved bits will be masked. - pub const fn from_raw(raw: u8) -> Self { - Self { val: raw & 0x70 } - } - - /// Get the raw value of the [`BitSync`] register. - pub const fn as_bits(&self) -> u8 { - self.val - } - - /// LoRa simple bit synchronization enable. - /// - /// # Example - /// - /// Enable simple bit synchronization. - /// - /// ``` - /// use stm32wlxx_hal::subghz::BitSync; - /// - /// const BIT_SYNC: BitSync = BitSync::RESET.set_simple_bit_sync_en(true); - /// # assert_eq!(u8::from(BIT_SYNC), 0x40u8); - /// ``` - #[must_use = "set_simple_bit_sync_en returns a modified BitSync"] - pub const fn set_simple_bit_sync_en(mut self, en: bool) -> BitSync { - if en { - self.val |= 1 << 6; - } else { - self.val &= !(1 << 6); - } - self - } - - /// Returns `true` if simple bit synchronization is enabled. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::BitSync; - /// - /// let bs: BitSync = BitSync::RESET; - /// assert_eq!(bs.simple_bit_sync_en(), false); - /// let bs: BitSync = bs.set_simple_bit_sync_en(true); - /// assert_eq!(bs.simple_bit_sync_en(), true); - /// let bs: BitSync = bs.set_simple_bit_sync_en(false); - /// assert_eq!(bs.simple_bit_sync_en(), false); - /// ``` - pub const fn simple_bit_sync_en(&self) -> bool { - self.val & (1 << 6) != 0 - } - - /// LoRa RX data inversion. - /// - /// # Example - /// - /// Invert receive data. - /// - /// ``` - /// use stm32wlxx_hal::subghz::BitSync; - /// - /// const BIT_SYNC: BitSync = BitSync::RESET.set_rx_data_inv(true); - /// # assert_eq!(u8::from(BIT_SYNC), 0x20u8); - /// ``` - #[must_use = "set_rx_data_inv returns a modified BitSync"] - pub const fn set_rx_data_inv(mut self, inv: bool) -> BitSync { - if inv { - self.val |= 1 << 5; - } else { - self.val &= !(1 << 5); - } - self - } - - /// Returns `true` if LoRa RX data is inverted. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::BitSync; - /// - /// let bs: BitSync = BitSync::RESET; - /// assert_eq!(bs.rx_data_inv(), false); - /// let bs: BitSync = bs.set_rx_data_inv(true); - /// assert_eq!(bs.rx_data_inv(), true); - /// let bs: BitSync = bs.set_rx_data_inv(false); - /// assert_eq!(bs.rx_data_inv(), false); - /// ``` - pub const fn rx_data_inv(&self) -> bool { - self.val & (1 << 5) != 0 - } - - /// LoRa normal bit synchronization enable. - /// - /// # Example - /// - /// Enable normal bit synchronization. - /// - /// ``` - /// use stm32wlxx_hal::subghz::BitSync; - /// - /// const BIT_SYNC: BitSync = BitSync::RESET.set_norm_bit_sync_en(true); - /// # assert_eq!(u8::from(BIT_SYNC), 0x10u8); - /// ``` - #[must_use = "set_norm_bit_sync_en returns a modified BitSync"] - pub const fn set_norm_bit_sync_en(mut self, en: bool) -> BitSync { - if en { - self.val |= 1 << 4; - } else { - self.val &= !(1 << 4); - } - self - } - - /// Returns `true` if normal bit synchronization is enabled. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::BitSync; - /// - /// let bs: BitSync = BitSync::RESET; - /// assert_eq!(bs.norm_bit_sync_en(), false); - /// let bs: BitSync = bs.set_norm_bit_sync_en(true); - /// assert_eq!(bs.norm_bit_sync_en(), true); - /// let bs: BitSync = bs.set_norm_bit_sync_en(false); - /// assert_eq!(bs.norm_bit_sync_en(), false); - /// ``` - pub const fn norm_bit_sync_en(&self) -> bool { - self.val & (1 << 4) != 0 - } -} - -impl From for u8 { - fn from(bs: BitSync) -> Self { - bs.val - } -} - -impl Default for BitSync { - fn default() -> Self { - Self::RESET - } -} diff --git a/embassy-stm32/src/subghz/cad_params.rs b/embassy-stm32/src/subghz/cad_params.rs deleted file mode 100644 index 1d90ff706..000000000 --- a/embassy-stm32/src/subghz/cad_params.rs +++ /dev/null @@ -1,230 +0,0 @@ -use super::Timeout; - -/// Number of symbols used for channel activity detection scans. -/// -/// Argument of [`CadParams::set_num_symbol`]. -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u8)] -pub enum NbCadSymbol { - /// 1 symbol. - S1 = 0x0, - /// 2 symbols. - S2 = 0x1, - /// 4 symbols. - S4 = 0x2, - /// 8 symbols. - S8 = 0x3, - /// 16 symbols. - S16 = 0x4, -} - -/// Mode to enter after a channel activity detection scan is finished. -/// -/// Argument of [`CadParams::set_exit_mode`]. -#[derive(Debug, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u8)] -pub enum ExitMode { - /// Standby with RC 13 MHz mode entry after CAD. - Standby = 0, - /// Standby with RC 13 MHz mode after CAD if no LoRa symbol is detected - /// during the CAD scan. - /// If a LoRa symbol is detected, the sub-GHz radio stays in RX mode - /// until a packet is received or until the CAD timeout is reached. - StandbyLoRa = 1, -} - -/// Channel activity detection (CAD) parameters. -/// -/// Argument of [`set_cad_params`]. -/// -/// # Recommended CAD settings -/// -/// This is taken directly from the datasheet. -/// -/// "The correct values selected in the table below must be carefully tested to -/// ensure a good detection at sensitivity level and to limit the number of -/// false detections" -/// -/// | SF (Spreading Factor) | [`set_det_peak`] | [`set_det_min`] | -/// |-----------------------|------------------|-----------------| -/// | 5 | 0x18 | 0x10 | -/// | 6 | 0x19 | 0x10 | -/// | 7 | 0x20 | 0x10 | -/// | 8 | 0x21 | 0x10 | -/// | 9 | 0x22 | 0x10 | -/// | 10 | 0x23 | 0x10 | -/// | 11 | 0x24 | 0x10 | -/// | 12 | 0x25 | 0x10 | -/// -/// [`set_cad_params`]: crate::subghz::SubGhz::set_cad_params -/// [`set_det_peak`]: crate::subghz::CadParams::set_det_peak -/// [`set_det_min`]: crate::subghz::CadParams::set_det_min -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct CadParams { - buf: [u8; 8], -} - -impl CadParams { - /// Create a new `CadParams`. - /// - /// This is the same as `default`, but in a `const` function. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::CadParams; - /// - /// const CAD_PARAMS: CadParams = CadParams::new(); - /// assert_eq!(CAD_PARAMS, CadParams::default()); - /// ``` - pub const fn new() -> CadParams { - CadParams { - buf: [super::OpCode::SetCadParams as u8, 0, 0, 0, 0, 0, 0, 0], - } - .set_num_symbol(NbCadSymbol::S1) - .set_det_peak(0x18) - .set_det_min(0x10) - .set_exit_mode(ExitMode::Standby) - } - - /// Number of symbols used for a CAD scan. - /// - /// # Example - /// - /// Set the number of symbols to 4. - /// - /// ``` - /// use stm32wlxx_hal::subghz::{CadParams, NbCadSymbol}; - /// - /// const CAD_PARAMS: CadParams = CadParams::new().set_num_symbol(NbCadSymbol::S4); - /// # assert_eq!(CAD_PARAMS.as_slice()[1], 0x2); - /// ``` - #[must_use = "set_num_symbol returns a modified CadParams"] - pub const fn set_num_symbol(mut self, nb: NbCadSymbol) -> CadParams { - self.buf[1] = nb as u8; - self - } - - /// Used with [`set_det_min`] to correlate the LoRa symbol. - /// - /// See the table in [`CadParams`] docs for recommended values. - /// - /// # Example - /// - /// Setting the recommended value for a spreading factor of 7. - /// - /// ``` - /// use stm32wlxx_hal::subghz::CadParams; - /// - /// const CAD_PARAMS: CadParams = CadParams::new().set_det_peak(0x20).set_det_min(0x10); - /// # assert_eq!(CAD_PARAMS.as_slice()[2], 0x20); - /// # assert_eq!(CAD_PARAMS.as_slice()[3], 0x10); - /// ``` - /// - /// [`set_det_min`]: crate::subghz::CadParams::set_det_min - #[must_use = "set_det_peak returns a modified CadParams"] - pub const fn set_det_peak(mut self, peak: u8) -> CadParams { - self.buf[2] = peak; - self - } - - /// Used with [`set_det_peak`] to correlate the LoRa symbol. - /// - /// See the table in [`CadParams`] docs for recommended values. - /// - /// # Example - /// - /// Setting the recommended value for a spreading factor of 6. - /// - /// ``` - /// use stm32wlxx_hal::subghz::CadParams; - /// - /// const CAD_PARAMS: CadParams = CadParams::new().set_det_peak(0x18).set_det_min(0x10); - /// # assert_eq!(CAD_PARAMS.as_slice()[2], 0x18); - /// # assert_eq!(CAD_PARAMS.as_slice()[3], 0x10); - /// ``` - /// - /// [`set_det_peak`]: crate::subghz::CadParams::set_det_peak - #[must_use = "set_det_min returns a modified CadParams"] - pub const fn set_det_min(mut self, min: u8) -> CadParams { - self.buf[3] = min; - self - } - - /// Mode to enter after a channel activity detection scan is finished. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{CadParams, ExitMode}; - /// - /// const CAD_PARAMS: CadParams = CadParams::new().set_exit_mode(ExitMode::Standby); - /// # assert_eq!(CAD_PARAMS.as_slice()[4], 0x00); - /// # assert_eq!(CAD_PARAMS.set_exit_mode(ExitMode::StandbyLoRa).as_slice()[4], 0x01); - /// ``` - #[must_use = "set_exit_mode returns a modified CadParams"] - pub const fn set_exit_mode(mut self, mode: ExitMode) -> CadParams { - self.buf[4] = mode as u8; - self - } - - /// Set the timeout. - /// - /// This is only used with [`ExitMode::StandbyLoRa`]. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{CadParams, ExitMode, Timeout}; - /// - /// const TIMEOUT: Timeout = Timeout::from_raw(0x123456); - /// const CAD_PARAMS: CadParams = CadParams::new() - /// .set_exit_mode(ExitMode::StandbyLoRa) - /// .set_timeout(TIMEOUT); - /// # assert_eq!(CAD_PARAMS.as_slice()[4], 0x01); - /// # assert_eq!(CAD_PARAMS.as_slice()[5], 0x12); - /// # assert_eq!(CAD_PARAMS.as_slice()[6], 0x34); - /// # assert_eq!(CAD_PARAMS.as_slice()[7], 0x56); - /// ``` - #[must_use = "set_timeout returns a modified CadParams"] - pub const fn set_timeout(mut self, to: Timeout) -> CadParams { - let to_bytes: [u8; 3] = to.as_bytes(); - self.buf[5] = to_bytes[0]; - self.buf[6] = to_bytes[1]; - self.buf[7] = to_bytes[2]; - self - } - - /// Extracts a slice containing the packet. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{CadParams, ExitMode, NbCadSymbol, Timeout}; - /// - /// const TIMEOUT: Timeout = Timeout::from_raw(0x123456); - /// const CAD_PARAMS: CadParams = CadParams::new() - /// .set_num_symbol(NbCadSymbol::S4) - /// .set_det_peak(0x18) - /// .set_det_min(0x10) - /// .set_exit_mode(ExitMode::StandbyLoRa) - /// .set_timeout(TIMEOUT); - /// - /// assert_eq!( - /// CAD_PARAMS.as_slice(), - /// &[0x88, 0x02, 0x18, 0x10, 0x01, 0x12, 0x34, 0x56] - /// ); - /// ``` - pub const fn as_slice(&self) -> &[u8] { - &self.buf - } -} - -impl Default for CadParams { - fn default() -> Self { - Self::new() - } -} diff --git a/embassy-stm32/src/subghz/calibrate.rs b/embassy-stm32/src/subghz/calibrate.rs deleted file mode 100644 index f94538f86..000000000 --- a/embassy-stm32/src/subghz/calibrate.rs +++ /dev/null @@ -1,122 +0,0 @@ -/// Image calibration. -/// -/// Argument of [`calibrate_image`]. -/// -/// [`calibrate_image`]: crate::subghz::SubGhz::calibrate_image -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct CalibrateImage(pub(crate) u8, pub(crate) u8); - -impl CalibrateImage { - /// Image calibration for the 430 - 440 MHz ISM band. - pub const ISM_430_440: CalibrateImage = CalibrateImage(0x6B, 0x6F); - - /// Image calibration for the 470 - 510 MHz ISM band. - pub const ISM_470_510: CalibrateImage = CalibrateImage(0x75, 0x81); - - /// Image calibration for the 779 - 787 MHz ISM band. - pub const ISM_779_787: CalibrateImage = CalibrateImage(0xC1, 0xC5); - - /// Image calibration for the 863 - 870 MHz ISM band. - pub const ISM_863_870: CalibrateImage = CalibrateImage(0xD7, 0xDB); - - /// Image calibration for the 902 - 928 MHz ISM band. - pub const ISM_902_928: CalibrateImage = CalibrateImage(0xE1, 0xE9); - - /// Create a new `CalibrateImage` structure from raw values. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::CalibrateImage; - /// - /// const CAL: CalibrateImage = CalibrateImage::new(0xE1, 0xE9); - /// assert_eq!(CAL, CalibrateImage::ISM_902_928); - /// ``` - pub const fn new(f1: u8, f2: u8) -> CalibrateImage { - CalibrateImage(f1, f2) - } - - /// Create a new `CalibrateImage` structure from two frequencies. - /// - /// # Arguments - /// - /// The units for `freq1` and `freq2` are in MHz. - /// - /// # Panics - /// - /// * Panics if `freq1` is less than `freq2`. - /// * Panics if `freq1` or `freq2` is not a multiple of 4MHz. - /// * Panics if `freq1` or `freq2` is greater than `1020`. - /// - /// # Example - /// - /// Create an image calibration for the 430 - 440 MHz ISM band. - /// - /// ``` - /// use stm32wlxx_hal::subghz::CalibrateImage; - /// - /// let cal: CalibrateImage = CalibrateImage::from_freq(428, 444); - /// assert_eq!(cal, CalibrateImage::ISM_430_440); - /// ``` - pub fn from_freq(freq1: u16, freq2: u16) -> CalibrateImage { - assert!(freq2 >= freq1); - assert_eq!(freq1 % 4, 0); - assert_eq!(freq2 % 4, 0); - assert!(freq1 <= 1020); - assert!(freq2 <= 1020); - CalibrateImage((freq1 / 4) as u8, (freq2 / 4) as u8) - } -} - -impl Default for CalibrateImage { - fn default() -> Self { - CalibrateImage::new(0xE1, 0xE9) - } -} - -/// Block calibration. -/// -/// Argument of [`calibrate`]. -/// -/// [`calibrate`]: crate::subghz::SubGhz::calibrate -#[derive(PartialEq, Eq, Debug, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u8)] -pub enum Calibrate { - /// Image calibration - Image = 1 << 6, - /// RF-ADC bulk P calibration - AdcBulkP = 1 << 5, - /// RF-ADC bulk N calibration - AdcBulkN = 1 << 4, - /// RF-ADC pulse calibration - AdcPulse = 1 << 3, - /// RF-PLL calibration - Pll = 1 << 2, - /// Sub-GHz radio RC 13 MHz calibration - Rc13M = 1 << 1, - /// Sub-GHz radio RC 64 kHz calibration - Rc64K = 1, -} - -impl Calibrate { - /// Get the bitmask for the block calibration. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::Calibrate; - /// - /// assert_eq!(Calibrate::Image.mask(), 0b0100_0000); - /// assert_eq!(Calibrate::AdcBulkP.mask(), 0b0010_0000); - /// assert_eq!(Calibrate::AdcBulkN.mask(), 0b0001_0000); - /// assert_eq!(Calibrate::AdcPulse.mask(), 0b0000_1000); - /// assert_eq!(Calibrate::Pll.mask(), 0b0000_0100); - /// assert_eq!(Calibrate::Rc13M.mask(), 0b0000_0010); - /// assert_eq!(Calibrate::Rc64K.mask(), 0b0000_0001); - /// ``` - pub const fn mask(self) -> u8 { - self as u8 - } -} diff --git a/embassy-stm32/src/subghz/fallback_mode.rs b/embassy-stm32/src/subghz/fallback_mode.rs deleted file mode 100644 index 50ec592f5..000000000 --- a/embassy-stm32/src/subghz/fallback_mode.rs +++ /dev/null @@ -1,37 +0,0 @@ -/// Fallback mode after successful packet transmission or packet reception. -/// -/// Argument of [`set_tx_rx_fallback_mode`]. -/// -/// [`set_tx_rx_fallback_mode`]: crate::subghz::SubGhz::set_tx_rx_fallback_mode. -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u8)] -pub enum FallbackMode { - /// Standby mode entry. - Standby = 0x20, - /// Standby with HSE32 enabled. - StandbyHse = 0x30, - /// Frequency synthesizer entry. - Fs = 0x40, -} - -impl From for u8 { - fn from(fm: FallbackMode) -> Self { - fm as u8 - } -} - -impl Default for FallbackMode { - /// Default fallback mode after power-on reset. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::FallbackMode; - /// - /// assert_eq!(FallbackMode::default(), FallbackMode::Standby); - /// ``` - fn default() -> Self { - FallbackMode::Standby - } -} diff --git a/embassy-stm32/src/subghz/hse_trim.rs b/embassy-stm32/src/subghz/hse_trim.rs deleted file mode 100644 index edfd52aca..000000000 --- a/embassy-stm32/src/subghz/hse_trim.rs +++ /dev/null @@ -1,107 +0,0 @@ -use super::ValueError; - -/// HSE32 load capacitor trimming. -/// -/// Argument of [`set_hse_in_trim`] and [`set_hse_out_trim`]. -/// -/// [`set_hse_in_trim`]: crate::subghz::SubGhz::set_hse_in_trim -/// [`set_hse_out_trim`]: crate::subghz::SubGhz::set_hse_out_trim -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct HseTrim { - val: u8, -} - -impl HseTrim { - /// Maximum capacitor value, ~33.4 pF - pub const MAX: HseTrim = HseTrim::from_raw(0x2F); - - /// Minimum capacitor value, ~11.3 pF - pub const MIN: HseTrim = HseTrim::from_raw(0x00); - - /// Power-on-reset capacitor value, ~20.3 pF - /// - /// This is the same as `default`. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::HseTrim; - /// - /// assert_eq!(HseTrim::POR, HseTrim::default()); - /// ``` - pub const POR: HseTrim = HseTrim::from_raw(0x12); - - /// Create a new [`HseTrim`] structure from a raw value. - /// - /// Values greater than the maximum of `0x2F` will be set to the maximum. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::HseTrim; - /// - /// assert_eq!(HseTrim::from_raw(0xFF), HseTrim::MAX); - /// assert_eq!(HseTrim::from_raw(0x2F), HseTrim::MAX); - /// assert_eq!(HseTrim::from_raw(0x00), HseTrim::MIN); - /// ``` - pub const fn from_raw(raw: u8) -> HseTrim { - if raw > 0x2F { - HseTrim { val: 0x2F } - } else { - HseTrim { val: raw } - } - } - - /// Create a HSE trim value from farads. - /// - /// Values greater than the maximum of 33.4 pF will be set to the maximum. - /// Values less than the minimum of 11.3 pF will be set to the minimum. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::HseTrim; - /// - /// assert!(HseTrim::from_farads(1.0).is_err()); - /// assert!(HseTrim::from_farads(1e-12).is_err()); - /// assert_eq!(HseTrim::from_farads(20.2e-12), Ok(HseTrim::default())); - /// ``` - pub fn from_farads(farads: f32) -> Result> { - const MAX: f32 = 33.4E-12; - const MIN: f32 = 11.3E-12; - if farads > MAX { - Err(ValueError::too_high(farads, MAX)) - } else if farads < MIN { - Err(ValueError::too_low(farads, MIN)) - } else { - Ok(HseTrim::from_raw(((farads - 11.3e-12) / 0.47e-12) as u8)) - } - } - - /// Get the capacitance as farads. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::HseTrim; - /// - /// assert_eq!((HseTrim::MAX.as_farads() * 10e11) as u8, 33); - /// assert_eq!((HseTrim::MIN.as_farads() * 10e11) as u8, 11); - /// ``` - pub fn as_farads(&self) -> f32 { - (self.val as f32) * 0.47E-12 + 11.3E-12 - } -} - -impl From for u8 { - fn from(ht: HseTrim) -> Self { - ht.val - } -} - -impl Default for HseTrim { - fn default() -> Self { - Self::POR - } -} diff --git a/embassy-stm32/src/subghz/irq.rs b/embassy-stm32/src/subghz/irq.rs deleted file mode 100644 index b56b8ad94..000000000 --- a/embassy-stm32/src/subghz/irq.rs +++ /dev/null @@ -1,292 +0,0 @@ -/// Interrupt lines. -/// -/// Argument of [`CfgIrq::irq_enable`] and [`CfgIrq::irq_disable`]. -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum IrqLine { - /// Global interrupt. - Global, - /// Interrupt line 1. - /// - /// This will output to the [`RfIrq0`](crate::gpio::RfIrq0) pin. - Line1, - /// Interrupt line 2. - /// - /// This will output to the [`RfIrq1`](crate::gpio::RfIrq1) pin. - Line2, - /// Interrupt line 3. - /// - /// This will output to the [`RfIrq2`](crate::gpio::RfIrq2) pin. - Line3, -} - -impl IrqLine { - pub(super) const fn offset(&self) -> usize { - match self { - IrqLine::Global => 1, - IrqLine::Line1 => 3, - IrqLine::Line2 => 5, - IrqLine::Line3 => 7, - } - } -} - -/// IRQ bit mapping -/// -/// See table 37 "IRQ bit mapping and definition" in the reference manual for -/// more information. -#[repr(u16)] -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Irq { - /// Packet transmission finished. - /// - /// * Packet type: LoRa and GFSK - /// * Operation: TX - TxDone = (1 << 0), - /// Packet reception finished. - /// - /// * Packet type: LoRa and GFSK - /// * Operation: RX - RxDone = (1 << 1), - /// Preamble detected. - /// - /// * Packet type: LoRa and GFSK - /// * Operation: RX - PreambleDetected = (1 << 2), - /// Synchronization word valid. - /// - /// * Packet type: GFSK - /// * Operation: RX - SyncDetected = (1 << 3), - /// Header valid. - /// - /// * Packet type: LoRa - /// * Operation: RX - HeaderValid = (1 << 4), - /// Header CRC error. - /// - /// * Packet type: LoRa - /// * Operation: RX - HeaderErr = (1 << 5), - /// Dual meaning error. - /// - /// For GFSK RX this indicates a preamble, syncword, address, CRC, or length - /// error. - /// - /// For LoRa RX this indicates a CRC error. - Err = (1 << 6), - /// Channel activity detection finished. - /// - /// * Packet type: LoRa - /// * Operation: CAD - CadDone = (1 << 7), - /// Channel activity detected. - /// - /// * Packet type: LoRa - /// * Operation: CAD - CadDetected = (1 << 8), - /// RX or TX timeout. - /// - /// * Packet type: LoRa and GFSK - /// * Operation: RX and TX - Timeout = (1 << 9), -} - -impl Irq { - /// Get the bitmask for an IRQ. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::Irq; - /// - /// assert_eq!(Irq::TxDone.mask(), 0x0001); - /// assert_eq!(Irq::Timeout.mask(), 0x0200); - /// ``` - pub const fn mask(self) -> u16 { - self as u16 - } -} - -/// Argument for [`set_irq_cfg`]. -/// -/// [`set_irq_cfg`]: crate::subghz::SubGhz::set_irq_cfg -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct CfgIrq { - buf: [u8; 9], -} - -impl CfgIrq { - /// Create a new `CfgIrq`. - /// - /// This is the same as `default`, but in a `const` function. - /// - /// The default value has all interrupts disabled on all lines. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::CfgIrq; - /// - /// const IRQ_CFG: CfgIrq = CfgIrq::new(); - /// ``` - pub const fn new() -> CfgIrq { - CfgIrq { - buf: [ - super::OpCode::CfgDioIrq as u8, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ], - } - } - - /// Enable an interrupt. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{CfgIrq, Irq, IrqLine}; - /// - /// const IRQ_CFG: CfgIrq = CfgIrq::new() - /// .irq_enable(IrqLine::Global, Irq::TxDone) - /// .irq_enable(IrqLine::Global, Irq::Timeout); - /// # assert_eq!(IRQ_CFG.as_slice()[1], 0x02); - /// # assert_eq!(IRQ_CFG.as_slice()[2], 0x01); - /// # assert_eq!(IRQ_CFG.as_slice()[3], 0x00); - /// ``` - #[must_use = "irq_enable returns a modified CfgIrq"] - pub const fn irq_enable(mut self, line: IrqLine, irq: Irq) -> CfgIrq { - let mask: u16 = irq as u16; - let offset: usize = line.offset(); - self.buf[offset] |= ((mask >> 8) & 0xFF) as u8; - self.buf[offset + 1] |= (mask & 0xFF) as u8; - self - } - - /// Enable an interrupt on all lines. - /// - /// As far as I can tell with empirical testing all IRQ lines need to be - /// enabled for the internal interrupt to be pending in the NVIC. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{CfgIrq, Irq}; - /// - /// const IRQ_CFG: CfgIrq = CfgIrq::new() - /// .irq_enable_all(Irq::TxDone) - /// .irq_enable_all(Irq::Timeout); - /// # assert_eq!(IRQ_CFG.as_slice()[1], 0x02); - /// # assert_eq!(IRQ_CFG.as_slice()[2], 0x01); - /// # assert_eq!(IRQ_CFG.as_slice()[3], 0x02); - /// # assert_eq!(IRQ_CFG.as_slice()[4], 0x01); - /// # assert_eq!(IRQ_CFG.as_slice()[5], 0x02); - /// # assert_eq!(IRQ_CFG.as_slice()[6], 0x01); - /// # assert_eq!(IRQ_CFG.as_slice()[7], 0x02); - /// # assert_eq!(IRQ_CFG.as_slice()[8], 0x01); - /// ``` - #[must_use = "irq_enable_all returns a modified CfgIrq"] - pub const fn irq_enable_all(mut self, irq: Irq) -> CfgIrq { - let mask: [u8; 2] = irq.mask().to_be_bytes(); - - self.buf[1] |= mask[0]; - self.buf[2] |= mask[1]; - self.buf[3] |= mask[0]; - self.buf[4] |= mask[1]; - self.buf[5] |= mask[0]; - self.buf[6] |= mask[1]; - self.buf[7] |= mask[0]; - self.buf[8] |= mask[1]; - - self - } - - /// Disable an interrupt. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{CfgIrq, Irq, IrqLine}; - /// - /// const IRQ_CFG: CfgIrq = CfgIrq::new() - /// .irq_enable(IrqLine::Global, Irq::TxDone) - /// .irq_enable(IrqLine::Global, Irq::Timeout) - /// .irq_disable(IrqLine::Global, Irq::TxDone) - /// .irq_disable(IrqLine::Global, Irq::Timeout); - /// # assert_eq!(IRQ_CFG.as_slice()[1], 0x00); - /// # assert_eq!(IRQ_CFG.as_slice()[2], 0x00); - /// # assert_eq!(IRQ_CFG.as_slice()[3], 0x00); - /// ``` - #[must_use = "irq_disable returns a modified CfgIrq"] - pub const fn irq_disable(mut self, line: IrqLine, irq: Irq) -> CfgIrq { - let mask: u16 = !(irq as u16); - let offset: usize = line.offset(); - self.buf[offset] &= ((mask >> 8) & 0xFF) as u8; - self.buf[offset + 1] &= (mask & 0xFF) as u8; - self - } - - /// Disable an interrupt on all lines. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{CfgIrq, Irq}; - /// - /// const IRQ_CFG: CfgIrq = CfgIrq::new() - /// .irq_enable_all(Irq::TxDone) - /// .irq_enable_all(Irq::Timeout) - /// .irq_disable_all(Irq::TxDone) - /// .irq_disable_all(Irq::Timeout); - /// # assert_eq!(IRQ_CFG, CfgIrq::new()); - /// ``` - #[must_use = "irq_disable_all returns a modified CfgIrq"] - pub const fn irq_disable_all(mut self, irq: Irq) -> CfgIrq { - let mask: [u8; 2] = (!irq.mask()).to_be_bytes(); - - self.buf[1] &= mask[0]; - self.buf[2] &= mask[1]; - self.buf[3] &= mask[0]; - self.buf[4] &= mask[1]; - self.buf[5] &= mask[0]; - self.buf[6] &= mask[1]; - self.buf[7] &= mask[0]; - self.buf[8] &= mask[1]; - - self - } - - /// Extracts a slice containing the packet. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{CfgIrq, Irq}; - /// - /// const IRQ_CFG: CfgIrq = CfgIrq::new() - /// .irq_enable_all(Irq::TxDone) - /// .irq_enable_all(Irq::Timeout); - /// - /// assert_eq!( - /// IRQ_CFG.as_slice(), - /// &[0x08, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01] - /// ); - /// ``` - pub const fn as_slice(&self) -> &[u8] { - &self.buf - } -} - -impl Default for CfgIrq { - fn default() -> Self { - Self::new() - } -} diff --git a/embassy-stm32/src/subghz/lora_sync_word.rs b/embassy-stm32/src/subghz/lora_sync_word.rs deleted file mode 100644 index 2c163104e..000000000 --- a/embassy-stm32/src/subghz/lora_sync_word.rs +++ /dev/null @@ -1,20 +0,0 @@ -/// LoRa synchronization word. -/// -/// Argument of [`set_lora_sync_word`][crate::subghz::SubGhz::set_lora_sync_word]. -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum LoRaSyncWord { - /// LoRa private network. - Private, - /// LoRa public network. - Public, -} - -impl LoRaSyncWord { - pub(crate) const fn bytes(self) -> [u8; 2] { - match self { - LoRaSyncWord::Private => [0x14, 0x24], - LoRaSyncWord::Public => [0x34, 0x44], - } - } -} diff --git a/embassy-stm32/src/subghz/mod.rs b/embassy-stm32/src/subghz/mod.rs deleted file mode 100644 index a74f9a6d5..000000000 --- a/embassy-stm32/src/subghz/mod.rs +++ /dev/null @@ -1,1007 +0,0 @@ -//! Sub-GHz radio operating in the 150 - 960 MHz ISM band -//! -//! The main radio type is [`SubGhz`]. -//! -//! ## LoRa user notice -//! -//! The Sub-GHz radio may have an undocumented erratum, see this ST community -//! post for more information: [link] -//! -//! [link]: https://community.st.com/s/question/0D53W00000hR8kpSAC/stm32wl55-erratum-clairification -//! -//! NOTE: This HAL is based on https://github.com/newAM/stm32wl-hal, but adopted for use with the stm32-metapac -//! and SPI HALs. - -mod bit_sync; -mod cad_params; -mod calibrate; -mod fallback_mode; -mod hse_trim; -mod irq; -mod lora_sync_word; -mod mod_params; -mod ocp; -mod op_error; -mod pa_config; -mod packet_params; -mod packet_status; -mod packet_type; -mod pkt_ctrl; -mod pmode; -mod pwr_ctrl; -mod reg_mode; -mod rf_frequency; -mod rx_timeout_stop; -mod sleep_cfg; -mod smps; -mod standby_clk; -mod stats; -mod status; -mod tcxo_mode; -mod timeout; -mod tx_params; -mod value_error; - -pub use bit_sync::BitSync; -pub use cad_params::{CadParams, ExitMode, NbCadSymbol}; -pub use calibrate::{Calibrate, CalibrateImage}; -use embassy_hal_common::ratio::Ratio; -pub use fallback_mode::FallbackMode; -pub use hse_trim::HseTrim; -pub use irq::{CfgIrq, Irq, IrqLine}; -pub use lora_sync_word::LoRaSyncWord; -pub use mod_params::{ - BpskModParams, CodingRate, FskBandwidth, FskBitrate, FskFdev, FskModParams, FskPulseShape, LoRaBandwidth, - LoRaModParams, SpreadingFactor, -}; -pub use ocp::Ocp; -pub use op_error::OpError; -pub use pa_config::{PaConfig, PaSel}; -pub use packet_params::{ - AddrComp, BpskPacketParams, CrcType, GenericPacketParams, HeaderType, LoRaPacketParams, PreambleDetection, -}; -pub use packet_status::{FskPacketStatus, LoRaPacketStatus}; -pub use packet_type::PacketType; -pub use pkt_ctrl::{InfSeqSel, PktCtrl}; -pub use pmode::PMode; -pub use pwr_ctrl::{CurrentLim, PwrCtrl}; -pub use reg_mode::RegMode; -pub use rf_frequency::RfFreq; -pub use rx_timeout_stop::RxTimeoutStop; -pub use sleep_cfg::{SleepCfg, Startup}; -pub use smps::SmpsDrv; -pub use standby_clk::StandbyClk; -pub use stats::{FskStats, LoRaStats, Stats}; -pub use status::{CmdStatus, Status, StatusMode}; -pub use tcxo_mode::{TcxoMode, TcxoTrim}; -pub use timeout::Timeout; -pub use tx_params::{RampTime, TxParams}; -pub use value_error::ValueError; - -use crate::dma::NoDma; -use crate::peripherals::SUBGHZSPI; -use crate::rcc::sealed::RccPeripheral; -use crate::spi::{BitOrder, Config as SpiConfig, MisoPin, MosiPin, SckPin, Spi, MODE_0}; -use crate::time::Hertz; -use crate::{pac, Peripheral}; - -/// Passthrough for SPI errors (for now) -pub type Error = crate::spi::Error; - -struct Nss { - _priv: (), -} - -impl Nss { - #[inline(always)] - pub fn new() -> Nss { - Self::clear(); - Nss { _priv: () } - } - - /// Clear NSS, enabling SPI transactions - #[inline(always)] - fn clear() { - let pwr = pac::PWR; - unsafe { - pwr.subghzspicr().modify(|w| w.set_nss(pac::pwr::vals::Nss::LOW)); - } - } - - /// Set NSS, disabling SPI transactions - #[inline(always)] - fn set() { - let pwr = pac::PWR; - unsafe { - pwr.subghzspicr().modify(|w| w.set_nss(pac::pwr::vals::Nss::HIGH)); - } - } -} - -impl Drop for Nss { - fn drop(&mut self) { - Self::set() - } -} - -/// Wakeup the radio from sleep mode. -/// -/// # Safety -/// -/// 1. This must not be called when the SubGHz radio is in use. -/// 2. This must not be called when the SubGHz SPI bus is in use. -/// -/// # Example -/// -/// See [`SubGhz::set_sleep`] -#[inline] -unsafe fn wakeup() { - Nss::clear(); - // RM0453 rev 2 page 171 section 5.7.2 "Sleep mode" - // on a firmware request via the sub-GHz radio SPI NSS signal - // (keeping sub-GHz radio SPI NSS low for at least 20 μs) - // - // I have found this to be a more reliable mechanism for ensuring NSS is - // pulled low for long enough to wake the radio. - while rfbusys() {} - Nss::set(); -} - -/// Returns `true` if the radio is busy. -/// -/// This may not be set immediately after NSS going low. -/// -/// See RM0461 Rev 4 section 5.3 page 181 "Radio busy management" for more -/// details. -#[inline] -fn rfbusys() -> bool { - // safety: atmoic read with no side-effects - //unsafe { (*pac::PWR::ptr()).sr2.read().rfbusys().is_busy() } - let pwr = pac::PWR; - unsafe { pwr.sr2().read().rfbusys() == pac::pwr::vals::Rfbusys::BUSY } -} - -/* -/// Returns `true` if the radio is busy or NSS is low. -/// -/// See RM0461 Rev 4 section 5.3 page 181 "Radio busy management" for more -/// details. -#[inline] -fn rfbusyms() -> bool { - let pwr = pac::PWR; - unsafe { pwr.sr2().read().rfbusyms() == pac::pwr::vals::Rfbusyms::BUSY } -} -*/ - -/// Sub-GHz radio peripheral -pub struct SubGhz<'d, Tx, Rx> { - spi: Spi<'d, SUBGHZSPI, Tx, Rx>, -} - -impl<'d, Tx, Rx> SubGhz<'d, Tx, Rx> { - fn pulse_radio_reset() { - let rcc = pac::RCC; - unsafe { - rcc.csr().modify(|w| w.set_rfrst(true)); - rcc.csr().modify(|w| w.set_rfrst(false)); - } - } - - // TODO: This should be replaced with async handling based on IRQ - fn poll_not_busy(&self) { - let mut count: u32 = 1_000_000; - while rfbusys() { - count -= 1; - if count == 0 { - let pwr = pac::PWR; - unsafe { - panic!( - "rfbusys timeout pwr.sr2=0x{:X} pwr.subghzspicr=0x{:X} pwr.cr1=0x{:X}", - pwr.sr2().read().0, - pwr.subghzspicr().read().0, - pwr.cr1().read().0 - ); - } - } - } - } - - /// Create a new sub-GHz radio driver from a peripheral. - /// - /// This will reset the radio and the SPI bus, and enable the peripheral - /// clock. - pub fn new( - peri: impl Peripheral

+ 'd, - sck: impl Peripheral

> + 'd, - mosi: impl Peripheral

> + 'd, - miso: impl Peripheral

> + 'd, - txdma: impl Peripheral

+ 'd, - rxdma: impl Peripheral

+ 'd, - ) -> Self { - Self::pulse_radio_reset(); - - // see RM0453 rev 1 section 7.2.13 page 291 - // The SUBGHZSPI_SCK frequency is obtained by PCLK3 divided by two. - // The SUBGHZSPI_SCK clock maximum speed must not exceed 16 MHz. - let clk = Hertz(core::cmp::min(SUBGHZSPI::frequency().0 / 2, 16_000_000)); - let mut config = SpiConfig::default(); - config.mode = MODE_0; - config.bit_order = BitOrder::MsbFirst; - let spi = Spi::new(peri, sck, mosi, miso, txdma, rxdma, clk, config); - - unsafe { wakeup() }; - - SubGhz { spi } - } - - pub fn is_busy(&mut self) -> bool { - rfbusys() - } - - pub fn reset(&mut self) { - Self::pulse_radio_reset(); - } -} - -impl<'d> SubGhz<'d, NoDma, NoDma> { - fn read(&mut self, opcode: OpCode, data: &mut [u8]) -> Result<(), Error> { - self.poll_not_busy(); - { - let _nss: Nss = Nss::new(); - self.spi.blocking_write(&[opcode as u8])?; - self.spi.blocking_transfer_in_place(data)?; - } - self.poll_not_busy(); - Ok(()) - } - - /// Read one byte from the sub-Ghz radio. - fn read_1(&mut self, opcode: OpCode) -> Result { - let mut buf: [u8; 1] = [0; 1]; - self.read(opcode, &mut buf)?; - Ok(buf[0]) - } - - /// Read a fixed number of bytes from the sub-Ghz radio. - fn read_n(&mut self, opcode: OpCode) -> Result<[u8; N], Error> { - let mut buf: [u8; N] = [0; N]; - self.read(opcode, &mut buf)?; - Ok(buf) - } - - fn write(&mut self, data: &[u8]) -> Result<(), Error> { - self.poll_not_busy(); - { - let _nss: Nss = Nss::new(); - self.spi.blocking_write(data)?; - } - self.poll_not_busy(); - Ok(()) - } - - pub fn write_buffer(&mut self, offset: u8, data: &[u8]) -> Result<(), Error> { - self.poll_not_busy(); - { - let _nss: Nss = Nss::new(); - self.spi.blocking_write(&[OpCode::WriteBuffer as u8, offset])?; - self.spi.blocking_write(data)?; - } - self.poll_not_busy(); - - Ok(()) - } - - /// Read the radio buffer at the given offset. - /// - /// The offset and length of a received packet is provided by - /// [`rx_buffer_status`](Self::rx_buffer_status). - pub fn read_buffer(&mut self, offset: u8, buf: &mut [u8]) -> Result { - let mut status_buf: [u8; 1] = [0]; - - self.poll_not_busy(); - { - let _nss: Nss = Nss::new(); - self.spi.blocking_write(&[OpCode::ReadBuffer as u8, offset])?; - self.spi.blocking_transfer_in_place(&mut status_buf)?; - self.spi.blocking_transfer_in_place(buf)?; - } - self.poll_not_busy(); - - Ok(status_buf[0].into()) - } -} - -// helper to pack register writes into a single buffer to avoid multiple DMA -// transfers -macro_rules! wr_reg { - [$reg:ident, $($data:expr),+] => { - &[ - OpCode::WriteRegister as u8, - Register::$reg.address().to_be_bytes()[0], - Register::$reg.address().to_be_bytes()[1], - $($data),+ - ] - }; -} - -// 5.8.2 -/// Register access -impl<'d> SubGhz<'d, NoDma, NoDma> { - // register write with variable length data - fn write_register(&mut self, register: Register, data: &[u8]) -> Result<(), Error> { - let addr: [u8; 2] = register.address().to_be_bytes(); - - self.poll_not_busy(); - { - let _nss: Nss = Nss::new(); - self.spi - .blocking_write(&[OpCode::WriteRegister as u8, addr[0], addr[1]])?; - self.spi.blocking_write(data)?; - } - self.poll_not_busy(); - - Ok(()) - } - - /// Set the LoRa bit synchronization. - pub fn set_bit_sync(&mut self, bs: BitSync) -> Result<(), Error> { - self.write(wr_reg![GBSYNC, bs.as_bits()]) - } - - /// Set the generic packet control register. - pub fn set_pkt_ctrl(&mut self, pkt_ctrl: PktCtrl) -> Result<(), Error> { - self.write(wr_reg![GPKTCTL1A, pkt_ctrl.as_bits()]) - } - - /// Set the initial value for generic packet whitening. - /// - /// This sets the first 8 bits, the 9th bit is set with - /// [`set_pkt_ctrl`](Self::set_pkt_ctrl). - pub fn set_init_whitening(&mut self, init: u8) -> Result<(), Error> { - self.write(wr_reg![GWHITEINIRL, init]) - } - - /// Set the initial value for generic packet CRC polynomial. - pub fn set_crc_polynomial(&mut self, polynomial: u16) -> Result<(), Error> { - let bytes: [u8; 2] = polynomial.to_be_bytes(); - self.write(wr_reg![GCRCINIRH, bytes[0], bytes[1]]) - } - - /// Set the generic packet CRC polynomial. - pub fn set_initial_crc_polynomial(&mut self, polynomial: u16) -> Result<(), Error> { - let bytes: [u8; 2] = polynomial.to_be_bytes(); - self.write(wr_reg![GCRCPOLRH, bytes[0], bytes[1]]) - } - - /// Set the synchronization word registers. - pub fn set_sync_word(&mut self, sync_word: &[u8; 8]) -> Result<(), Error> { - self.write_register(Register::GSYNC7, sync_word) - } - - /// Set the LoRa synchronization word registers. - pub fn set_lora_sync_word(&mut self, sync_word: LoRaSyncWord) -> Result<(), Error> { - let bytes: [u8; 2] = sync_word.bytes(); - self.write(wr_reg![LSYNCH, bytes[0], bytes[1]]) - } - - /// Set the RX gain control. - pub fn set_rx_gain(&mut self, pmode: PMode) -> Result<(), Error> { - self.write(wr_reg![RXGAINC, pmode as u8]) - } - - /// Set the power amplifier over current protection. - pub fn set_pa_ocp(&mut self, ocp: Ocp) -> Result<(), Error> { - self.write(wr_reg![PAOCP, ocp as u8]) - } - - /// Restart the radio RTC. - /// - /// This is used to workaround an erratum for [`set_rx_duty_cycle`]. - /// - /// [`set_rx_duty_cycle`]: crate::subghz::SubGhz::set_rx_duty_cycle - pub fn restart_rtc(&mut self) -> Result<(), Error> { - self.write(wr_reg![RTCCTLR, 0b1]) - } - - /// Set the radio real-time-clock period. - /// - /// This is used to workaround an erratum for [`set_rx_duty_cycle`]. - /// - /// [`set_rx_duty_cycle`]: crate::subghz::SubGhz::set_rx_duty_cycle - pub fn set_rtc_period(&mut self, period: Timeout) -> Result<(), Error> { - let tobits: u32 = period.into_bits(); - self.write(wr_reg![ - RTCPRDR2, - (tobits >> 16) as u8, - (tobits >> 8) as u8, - tobits as u8 - ]) - } - - /// Set the HSE32 crystal OSC_IN load capacitor trimming. - pub fn set_hse_in_trim(&mut self, trim: HseTrim) -> Result<(), Error> { - self.write(wr_reg![HSEINTRIM, trim.into()]) - } - - /// Set the HSE32 crystal OSC_OUT load capacitor trimming. - pub fn set_hse_out_trim(&mut self, trim: HseTrim) -> Result<(), Error> { - self.write(wr_reg![HSEOUTTRIM, trim.into()]) - } - - /// Set the SMPS clock detection enabled. - /// - /// SMPS clock detection must be enabled fore enabling the SMPS. - pub fn set_smps_clock_det_en(&mut self, en: bool) -> Result<(), Error> { - self.write(wr_reg![SMPSC0, (en as u8) << 6]) - } - - /// Set the power current limiting. - pub fn set_pwr_ctrl(&mut self, pwr_ctrl: PwrCtrl) -> Result<(), Error> { - self.write(wr_reg![PC, pwr_ctrl.as_bits()]) - } - - /// Set the maximum SMPS drive capability. - pub fn set_smps_drv(&mut self, drv: SmpsDrv) -> Result<(), Error> { - self.write(wr_reg![SMPSC2, (drv as u8) << 1]) - } - - /// Set the node address. - /// - /// Used with [`GenericPacketParams::set_addr_comp`] to filter packets based - /// on node address. - pub fn set_node_addr(&mut self, addr: u8) -> Result<(), Error> { - self.write(wr_reg![NODE, addr]) - } - - /// Set the broadcast address. - /// - /// Used with [`GenericPacketParams::set_addr_comp`] to filter packets based - /// on broadcast address. - pub fn set_broadcast_addr(&mut self, addr: u8) -> Result<(), Error> { - self.write(wr_reg![BROADCAST, addr]) - } - - /// Set both the broadcast address and node address. - /// - /// This is a combination of [`set_node_addr`] and [`set_broadcast_addr`] - /// in a single SPI transfer. - /// - /// [`set_node_addr`]: Self::set_node_addr - /// [`set_broadcast_addr`]: Self::set_broadcast_addr - pub fn set_addrs(&mut self, node: u8, broadcast: u8) -> Result<(), Error> { - self.write(wr_reg![NODE, node, broadcast]) - } -} - -// 5.8.3 -/// Operating mode commands -impl<'d> SubGhz<'d, NoDma, NoDma> { - /// Put the radio into sleep mode. - /// - /// This command is only accepted in standby mode. - /// The cfg argument allows some optional functions to be maintained - /// in sleep mode. - /// - /// # Safety - /// - /// 1. After the `set_sleep` command, the sub-GHz radio NSS must not go low - /// for 500 μs. - /// No reason is provided, the reference manual (RM0453 rev 2) simply - /// says "you must". - /// 2. The radio cannot be used while in sleep mode. - /// 3. The radio must be woken up with [`wakeup`] before resuming use. - /// - /// # Example - /// - /// Put the radio into sleep mode. - /// - /// ```no_run - /// # let dp = unsafe { embassy_stm32::pac::Peripherals::steal() }; - /// # let mut sg = embassy_stm32::subghz::SubGhz::new(p.SUBGHZSPI, ...); - /// use embassy_stm32::{ - /// subghz::{wakeup, SleepCfg, StandbyClk}, - /// }; - /// - /// sg.set_standby(StandbyClk::Rc)?; - /// unsafe { sg.set_sleep(SleepCfg::default())? }; - /// embassy_time::Timer::after(embassy_time::Duration::from_micros(500)).await; - /// unsafe { wakeup() }; - /// # Ok::<(), embassy_stm32::subghz::Error>(()) - /// ``` - pub unsafe fn set_sleep(&mut self, cfg: SleepCfg) -> Result<(), Error> { - // poll for busy before, but not after - // radio idles with busy high while in sleep mode - self.poll_not_busy(); - { - let _nss: Nss = Nss::new(); - self.spi.blocking_write(&[OpCode::SetSleep as u8, u8::from(cfg)])?; - } - Ok(()) - } - - /// Put the radio into standby mode. - pub fn set_standby(&mut self, standby_clk: StandbyClk) -> Result<(), Error> { - self.write(&[OpCode::SetStandby as u8, u8::from(standby_clk)]) - } - - /// Put the subghz radio into frequency synthesis mode. - /// - /// The RF-PLL frequency must be set with [`set_rf_frequency`] before using - /// this command. - /// - /// Check the datasheet for more information, this is a test command but - /// I honestly do not see any use for it. Please update this description - /// if you know more than I do. - /// - /// [`set_rf_frequency`]: crate::subghz::SubGhz::set_rf_frequency - pub fn set_fs(&mut self) -> Result<(), Error> { - self.write(&[OpCode::SetFs.into()]) - } - - /// Setup the sub-GHz radio for TX. - pub fn set_tx(&mut self, timeout: Timeout) -> Result<(), Error> { - let tobits: u32 = timeout.into_bits(); - self.write(&[ - OpCode::SetTx.into(), - (tobits >> 16) as u8, - (tobits >> 8) as u8, - tobits as u8, - ]) - } - - /// Setup the sub-GHz radio for RX. - pub fn set_rx(&mut self, timeout: Timeout) -> Result<(), Error> { - let tobits: u32 = timeout.into_bits(); - self.write(&[ - OpCode::SetRx.into(), - (tobits >> 16) as u8, - (tobits >> 8) as u8, - tobits as u8, - ]) - } - - /// Allows selection of the receiver event which stops the RX timeout timer. - pub fn set_rx_timeout_stop(&mut self, rx_timeout_stop: RxTimeoutStop) -> Result<(), Error> { - self.write(&[OpCode::SetStopRxTimerOnPreamble.into(), rx_timeout_stop.into()]) - } - - /// Put the radio in non-continuous RX mode. - /// - /// This command must be sent in Standby mode. - /// This command is only functional with FSK and LoRa packet type. - /// - /// The following steps are performed: - /// 1. Save sub-GHz radio configuration. - /// 2. Enter Receive mode and listen for a preamble for the specified `rx_period`. - /// 3. Upon the detection of a preamble, the `rx_period` timeout is stopped - /// and restarted with the value 2 x `rx_period` + `sleep_period`. - /// During this new period, the sub-GHz radio looks for the detection of - /// a synchronization word when in (G)FSK modulation mode, - /// or a header when in LoRa modulation mode. - /// 4. If no packet is received during the listen period defined by - /// 2 x `rx_period` + `sleep_period`, the sleep mode is entered for a - /// duration of `sleep_period`. At the end of the receive period, - /// the sub-GHz radio takes some time to save the context before starting - /// the sleep period. - /// 5. After the sleep period, a new listening period is automatically - /// started. The sub-GHz radio restores the sub-GHz radio configuration - /// and continuous with step 2. - /// - /// The listening mode is terminated in one of the following cases: - /// * if a packet is received during the listening period: the sub-GHz radio - /// issues a [`RxDone`] interrupt and enters standby mode. - /// * if [`set_standby`] is sent during the listening period or after the - /// sub-GHz has been requested to exit sleep mode by sub-GHz radio SPI NSS - /// - /// # Erratum - /// - /// When a preamble is detected the radio should restart the RX timeout - /// with a value of 2 × `rx_period` + `sleep_period`. - /// Instead the radio erroneously uses `sleep_period`. - /// - /// To workaround this use [`restart_rtc`] and [`set_rtc_period`] to - /// reprogram the radio timeout to 2 × `rx_period` + `sleep_period`. - /// - /// Use code similar to this in the [`PreambleDetected`] interrupt handler. - /// - /// ```no_run - /// # let rx_period: Timeout = Timeout::from_millis_sat(100); - /// # let sleep_period: Timeout = Timeout::from_millis_sat(100); - /// # let mut sg = unsafe { stm32wlxx_hal::subghz::SubGhz::steal() }; - /// use stm32wlxx_hal::subghz::Timeout; - /// - /// let period: Timeout = rx_period - /// .saturating_add(rx_period) - /// .saturating_add(sleep_period); - /// - /// sg.set_rtc_period(period)?; - /// sg.restart_rtc()?; - /// # Ok::<(), stm32wlxx_hal::subghz::Error>(()) - /// ``` - /// - /// Please read the erratum for more details. - /// - /// [`PreambleDetected`]: crate::subghz::Irq::PreambleDetected - /// [`restart_rtc`]: crate::subghz::SubGhz::restart_rtc - /// [`RxDone`]: crate::subghz::Irq::RxDone - /// [`set_rf_frequency`]: crate::subghz::SubGhz::set_rf_frequency - /// [`set_rtc_period`]: crate::subghz::SubGhz::set_rtc_period - /// [`set_standby`]: crate::subghz::SubGhz::set_standby - pub fn set_rx_duty_cycle(&mut self, rx_period: Timeout, sleep_period: Timeout) -> Result<(), Error> { - let rx_period_bits: u32 = rx_period.into_bits(); - let sleep_period_bits: u32 = sleep_period.into_bits(); - self.write(&[ - OpCode::SetRxDutyCycle.into(), - (rx_period_bits >> 16) as u8, - (rx_period_bits >> 8) as u8, - rx_period_bits as u8, - (sleep_period_bits >> 16) as u8, - (sleep_period_bits >> 8) as u8, - sleep_period_bits as u8, - ]) - } - - /// Channel Activity Detection (CAD) with LoRa packets. - /// - /// The channel activity detection (CAD) is a specific LoRa operation mode, - /// where the sub-GHz radio searches for a LoRa radio signal. - /// After the search is completed, the Standby mode is automatically - /// entered, CAD is done and IRQ is generated. - /// When a LoRa radio signal is detected, the CAD detected IRQ is also - /// generated. - /// - /// The length of the search must be configured with [`set_cad_params`] - /// prior to calling `set_cad`. - /// - /// [`set_cad_params`]: crate::subghz::SubGhz::set_cad_params - pub fn set_cad(&mut self) -> Result<(), Error> { - self.write(&[OpCode::SetCad.into()]) - } - - /// Generate a continuous transmit tone at the RF-PLL frequency. - /// - /// The sub-GHz radio remains in continuous transmit tone mode until a mode - /// configuration command is received. - pub fn set_tx_continuous_wave(&mut self) -> Result<(), Error> { - self.write(&[OpCode::SetTxContinuousWave as u8]) - } - - /// Generate an infinite preamble at the RF-PLL frequency. - /// - /// The preamble is an alternating 0s and 1s sequence in generic (G)FSK and - /// (G)MSK modulations. - /// The preamble is symbol 0 in LoRa modulation. - /// The sub-GHz radio remains in infinite preamble mode until a mode - /// configuration command is received. - pub fn set_tx_continuous_preamble(&mut self) -> Result<(), Error> { - self.write(&[OpCode::SetTxContinuousPreamble as u8]) - } -} - -// 5.8.4 -/// Radio configuration commands -impl<'d> SubGhz<'d, NoDma, NoDma> { - /// Set the packet type (modulation scheme). - pub fn set_packet_type(&mut self, packet_type: PacketType) -> Result<(), Error> { - self.write(&[OpCode::SetPacketType as u8, packet_type as u8]) - } - - /// Get the packet type. - pub fn packet_type(&mut self) -> Result, Error> { - let pkt_type: [u8; 2] = self.read_n(OpCode::GetPacketType)?; - Ok(PacketType::from_raw(pkt_type[1])) - } - - /// Set the radio carrier frequency. - pub fn set_rf_frequency(&mut self, freq: &RfFreq) -> Result<(), Error> { - self.write(freq.as_slice()) - } - - /// Set the transmit output power and the PA ramp-up time. - pub fn set_tx_params(&mut self, params: &TxParams) -> Result<(), Error> { - self.write(params.as_slice()) - } - - /// Power amplifier configuration. - /// - /// Used to customize the maximum output power and efficiency. - pub fn set_pa_config(&mut self, pa_config: &PaConfig) -> Result<(), Error> { - self.write(pa_config.as_slice()) - } - - /// Operating mode to enter after a successful packet transmission or - /// packet reception. - pub fn set_tx_rx_fallback_mode(&mut self, fm: FallbackMode) -> Result<(), Error> { - self.write(&[OpCode::SetTxRxFallbackMode as u8, fm as u8]) - } - - /// Set channel activity detection (CAD) parameters. - pub fn set_cad_params(&mut self, params: &CadParams) -> Result<(), Error> { - self.write(params.as_slice()) - } - - /// Set the data buffer base address for the packet handling in TX and RX. - /// - /// There is a single buffer for both TX and RX. - /// The buffer is not memory mapped, it is accessed via the - /// [`read_buffer`](SubGhz::read_buffer) and - /// [`write_buffer`](SubGhz::write_buffer) methods. - pub fn set_buffer_base_address(&mut self, tx: u8, rx: u8) -> Result<(), Error> { - self.write(&[OpCode::SetBufferBaseAddress as u8, tx, rx]) - } - - /// Set the (G)FSK modulation parameters. - pub fn set_fsk_mod_params(&mut self, params: &FskModParams) -> Result<(), Error> { - self.write(params.as_slice()) - } - - /// Set the LoRa modulation parameters. - pub fn set_lora_mod_params(&mut self, params: &LoRaModParams) -> Result<(), Error> { - self.write(params.as_slice()) - } - - /// Set the BPSK modulation parameters. - pub fn set_bpsk_mod_params(&mut self, params: &BpskModParams) -> Result<(), Error> { - self.write(params.as_slice()) - } - - /// Set the generic (FSK) packet parameters. - pub fn set_packet_params(&mut self, params: &GenericPacketParams) -> Result<(), Error> { - self.write(params.as_slice()) - } - - /// Set the BPSK packet parameters. - pub fn set_bpsk_packet_params(&mut self, params: &BpskPacketParams) -> Result<(), Error> { - self.write(params.as_slice()) - } - - /// Set the LoRa packet parameters. - pub fn set_lora_packet_params(&mut self, params: &LoRaPacketParams) -> Result<(), Error> { - self.write(params.as_slice()) - } - - /// Set the number of LoRa symbols to be received before starting the - /// reception of a LoRa packet. - /// - /// Packet reception is started after `n` + 1 symbols are detected. - pub fn set_lora_symb_timeout(&mut self, n: u8) -> Result<(), Error> { - self.write(&[OpCode::SetLoRaSymbTimeout.into(), n]) - } -} - -// 5.8.5 -/// Communication status and information commands -impl<'d> SubGhz<'d, NoDma, NoDma> { - /// Get the radio status. - /// - /// The hardware (or documentation) appears to have many bugs where this - /// will return reserved values. - /// See this thread in the ST community for details: [link] - /// - /// [link]: https://community.st.com/s/question/0D53W00000hR9GQSA0/stm32wl55-getstatus-command-returns-reserved-cmdstatus - pub fn status(&mut self) -> Result { - Ok(self.read_1(OpCode::GetStatus)?.into()) - } - - /// Get the RX buffer status. - /// - /// The return tuple is (status, payload_length, buffer_pointer). - pub fn rx_buffer_status(&mut self) -> Result<(Status, u8, u8), Error> { - let data: [u8; 3] = self.read_n(OpCode::GetRxBufferStatus)?; - Ok((data[0].into(), data[1], data[2])) - } - - /// Returns information on the last received (G)FSK packet. - pub fn fsk_packet_status(&mut self) -> Result { - Ok(FskPacketStatus::from(self.read_n(OpCode::GetPacketStatus)?)) - } - - /// Returns information on the last received LoRa packet. - pub fn lora_packet_status(&mut self) -> Result { - Ok(LoRaPacketStatus::from(self.read_n(OpCode::GetPacketStatus)?)) - } - - /// Get the instantaneous signal strength during packet reception. - /// - /// The units are in dbm. - pub fn rssi_inst(&mut self) -> Result<(Status, Ratio), Error> { - let data: [u8; 2] = self.read_n(OpCode::GetRssiInst)?; - let status: Status = data[0].into(); - let rssi: Ratio = Ratio::new_raw(i16::from(data[1]), -2); - - Ok((status, rssi)) - } - - /// (G)FSK packet stats. - pub fn fsk_stats(&mut self) -> Result, Error> { - let data: [u8; 7] = self.read_n(OpCode::GetStats)?; - Ok(Stats::from_raw_fsk(data)) - } - - /// LoRa packet stats. - pub fn lora_stats(&mut self) -> Result, Error> { - let data: [u8; 7] = self.read_n(OpCode::GetStats)?; - Ok(Stats::from_raw_lora(data)) - } - - /// Reset the stats as reported in [`lora_stats`](SubGhz::lora_stats) and - /// [`fsk_stats`](SubGhz::fsk_stats). - pub fn reset_stats(&mut self) -> Result<(), Error> { - const RESET_STATS: [u8; 7] = [0x00; 7]; - self.write(&RESET_STATS) - } -} - -// 5.8.6 -/// IRQ commands -impl<'d> SubGhz<'d, NoDma, NoDma> { - /// Set the interrupt configuration. - pub fn set_irq_cfg(&mut self, cfg: &CfgIrq) -> Result<(), Error> { - self.write(cfg.as_slice()) - } - - /// Get the IRQ status. - pub fn irq_status(&mut self) -> Result<(Status, u16), Error> { - let data: [u8; 3] = self.read_n(OpCode::GetIrqStatus)?; - let irq_status: u16 = u16::from_be_bytes([data[1], data[2]]); - Ok((data[0].into(), irq_status)) - } - - /// Clear the IRQ status. - pub fn clear_irq_status(&mut self, mask: u16) -> Result<(), Error> { - self.write(&[OpCode::ClrIrqStatus as u8, (mask >> 8) as u8, mask as u8]) - } -} - -// 5.8.7 -/// Miscellaneous commands -impl<'d> SubGhz<'d, NoDma, NoDma> { - /// Calibrate one or several blocks at any time when in standby mode. - pub fn calibrate(&mut self, cal: u8) -> Result<(), Error> { - // bit 7 is reserved and must be kept at reset value. - self.write(&[OpCode::Calibrate as u8, cal & 0x7F]) - } - - /// Calibrate the image at the given frequencies. - /// - /// Requires the radio to be in standby mode. - pub fn calibrate_image(&mut self, cal: CalibrateImage) -> Result<(), Error> { - self.write(&[OpCode::CalibrateImage as u8, cal.0, cal.1]) - } - - /// Set the radio power supply. - pub fn set_regulator_mode(&mut self, reg_mode: RegMode) -> Result<(), Error> { - self.write(&[OpCode::SetRegulatorMode as u8, reg_mode as u8]) - } - - /// Get the radio operational errors. - pub fn op_error(&mut self) -> Result<(Status, u16), Error> { - let data: [u8; 3] = self.read_n(OpCode::GetError)?; - Ok((data[0].into(), u16::from_be_bytes([data[1], data[2]]))) - } - - /// Clear all errors as reported by [`op_error`](SubGhz::op_error). - pub fn clear_error(&mut self) -> Result<(), Error> { - self.write(&[OpCode::ClrError as u8, 0x00]) - } -} - -// 5.8.8 -/// Set TCXO mode command -impl<'d> SubGhz<'d, NoDma, NoDma> { - /// Set the TCXO trim and HSE32 ready timeout. - pub fn set_tcxo_mode(&mut self, tcxo_mode: &TcxoMode) -> Result<(), Error> { - self.write(tcxo_mode.as_slice()) - } -} - -/// sub-GHz radio opcodes. -/// -/// See Table 41 "Sub-GHz radio SPI commands overview" -#[repr(u8)] -#[derive(Debug, Clone, Copy)] -#[allow(dead_code)] -pub(crate) enum OpCode { - Calibrate = 0x89, - CalibrateImage = 0x98, - CfgDioIrq = 0x08, - ClrError = 0x07, - ClrIrqStatus = 0x02, - GetError = 0x17, - GetIrqStatus = 0x12, - GetPacketStatus = 0x14, - GetPacketType = 0x11, - GetRssiInst = 0x15, - GetRxBufferStatus = 0x13, - GetStats = 0x10, - GetStatus = 0xC0, - ReadBuffer = 0x1E, - RegRegister = 0x1D, - ResetStats = 0x00, - SetBufferBaseAddress = 0x8F, - SetCad = 0xC5, - SetCadParams = 0x88, - SetFs = 0xC1, - SetLoRaSymbTimeout = 0xA0, - SetModulationParams = 0x8B, - SetPacketParams = 0x8C, - SetPacketType = 0x8A, - SetPaConfig = 0x95, - SetRegulatorMode = 0x96, - SetRfFrequency = 0x86, - SetRx = 0x82, - SetRxDutyCycle = 0x94, - SetSleep = 0x84, - SetStandby = 0x80, - SetStopRxTimerOnPreamble = 0x9F, - SetTcxoMode = 0x97, - SetTx = 0x83, - SetTxContinuousPreamble = 0xD2, - SetTxContinuousWave = 0xD1, - SetTxParams = 0x8E, - SetTxRxFallbackMode = 0x93, - WriteBuffer = 0x0E, - WriteRegister = 0x0D, -} - -impl From for u8 { - fn from(opcode: OpCode) -> Self { - opcode as u8 - } -} - -#[repr(u16)] -#[allow(clippy::upper_case_acronyms)] -pub(crate) enum Register { - /// Generic bit synchronization. - GBSYNC = 0x06AC, - /// Generic packet control. - GPKTCTL1A = 0x06B8, - /// Generic whitening. - GWHITEINIRL = 0x06B9, - /// Generic CRC initial. - GCRCINIRH = 0x06BC, - /// Generic CRC polynomial. - GCRCPOLRH = 0x06BE, - /// Generic synchronization word 7. - GSYNC7 = 0x06C0, - /// Node address. - NODE = 0x06CD, - /// Broadcast address. - BROADCAST = 0x06CE, - /// LoRa synchronization word MSB. - LSYNCH = 0x0740, - /// LoRa synchronization word LSB. - #[allow(dead_code)] - LSYNCL = 0x0741, - /// Receiver gain control. - RXGAINC = 0x08AC, - /// PA over current protection. - PAOCP = 0x08E7, - /// RTC control. - RTCCTLR = 0x0902, - /// RTC period MSB. - RTCPRDR2 = 0x0906, - /// RTC period mid-byte. - #[allow(dead_code)] - RTCPRDR1 = 0x0907, - /// RTC period LSB. - #[allow(dead_code)] - RTCPRDR0 = 0x0908, - /// HSE32 OSC_IN capacitor trim. - HSEINTRIM = 0x0911, - /// HSE32 OSC_OUT capacitor trim. - HSEOUTTRIM = 0x0912, - /// SMPS control 0. - SMPSC0 = 0x0916, - /// Power control. - PC = 0x091A, - /// SMPS control 2. - SMPSC2 = 0x0923, -} - -impl Register { - pub const fn address(self) -> u16 { - self as u16 - } -} diff --git a/embassy-stm32/src/subghz/mod_params.rs b/embassy-stm32/src/subghz/mod_params.rs deleted file mode 100644 index d997ae112..000000000 --- a/embassy-stm32/src/subghz/mod_params.rs +++ /dev/null @@ -1,1045 +0,0 @@ -/// Bandwidth options for [`FskModParams`]. -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum FskBandwidth { - /// 4.8 kHz double-sideband - Bw4 = 0x1F, - /// 5.8 kHz double-sideband - Bw5 = 0x17, - /// 7.3 kHz double-sideband - Bw7 = 0x0F, - /// 9.7 kHz double-sideband - Bw9 = 0x1E, - /// 11.7 kHz double-sideband - Bw11 = 0x16, - /// 14.6 kHz double-sideband - Bw14 = 0x0E, - /// 19.5 kHz double-sideband - Bw19 = 0x1D, - /// 23.4 kHz double-sideband - Bw23 = 0x15, - /// 29.3 kHz double-sideband - Bw29 = 0x0D, - /// 39.0 kHz double-sideband - Bw39 = 0x1C, - /// 46.9 kHz double-sideband - Bw46 = 0x14, - /// 58.6 kHz double-sideband - Bw58 = 0x0C, - /// 78.2 kHz double-sideband - Bw78 = 0x1B, - /// 93.8 kHz double-sideband - Bw93 = 0x13, - /// 117.3 kHz double-sideband - Bw117 = 0x0B, - /// 156.2 kHz double-sideband - Bw156 = 0x1A, - /// 187.2 kHz double-sideband - Bw187 = 0x12, - /// 234.3 kHz double-sideband - Bw234 = 0x0A, - /// 312.0 kHz double-sideband - Bw312 = 0x19, - /// 373.6 kHz double-sideband - Bw373 = 0x11, - /// 467.0 kHz double-sideband - Bw467 = 0x09, -} - -impl FskBandwidth { - /// Get the bandwidth in hertz. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::FskBandwidth; - /// - /// assert_eq!(FskBandwidth::Bw4.hertz(), 4_800); - /// assert_eq!(FskBandwidth::Bw5.hertz(), 5_800); - /// assert_eq!(FskBandwidth::Bw7.hertz(), 7_300); - /// assert_eq!(FskBandwidth::Bw9.hertz(), 9_700); - /// assert_eq!(FskBandwidth::Bw11.hertz(), 11_700); - /// assert_eq!(FskBandwidth::Bw14.hertz(), 14_600); - /// assert_eq!(FskBandwidth::Bw19.hertz(), 19_500); - /// assert_eq!(FskBandwidth::Bw23.hertz(), 23_400); - /// assert_eq!(FskBandwidth::Bw29.hertz(), 29_300); - /// assert_eq!(FskBandwidth::Bw39.hertz(), 39_000); - /// assert_eq!(FskBandwidth::Bw46.hertz(), 46_900); - /// assert_eq!(FskBandwidth::Bw58.hertz(), 58_600); - /// assert_eq!(FskBandwidth::Bw78.hertz(), 78_200); - /// assert_eq!(FskBandwidth::Bw93.hertz(), 93_800); - /// assert_eq!(FskBandwidth::Bw117.hertz(), 117_300); - /// assert_eq!(FskBandwidth::Bw156.hertz(), 156_200); - /// assert_eq!(FskBandwidth::Bw187.hertz(), 187_200); - /// assert_eq!(FskBandwidth::Bw234.hertz(), 234_300); - /// assert_eq!(FskBandwidth::Bw312.hertz(), 312_000); - /// assert_eq!(FskBandwidth::Bw373.hertz(), 373_600); - /// assert_eq!(FskBandwidth::Bw467.hertz(), 467_000); - /// ``` - pub const fn hertz(&self) -> u32 { - match self { - FskBandwidth::Bw4 => 4_800, - FskBandwidth::Bw5 => 5_800, - FskBandwidth::Bw7 => 7_300, - FskBandwidth::Bw9 => 9_700, - FskBandwidth::Bw11 => 11_700, - FskBandwidth::Bw14 => 14_600, - FskBandwidth::Bw19 => 19_500, - FskBandwidth::Bw23 => 23_400, - FskBandwidth::Bw29 => 29_300, - FskBandwidth::Bw39 => 39_000, - FskBandwidth::Bw46 => 46_900, - FskBandwidth::Bw58 => 58_600, - FskBandwidth::Bw78 => 78_200, - FskBandwidth::Bw93 => 93_800, - FskBandwidth::Bw117 => 117_300, - FskBandwidth::Bw156 => 156_200, - FskBandwidth::Bw187 => 187_200, - FskBandwidth::Bw234 => 234_300, - FskBandwidth::Bw312 => 312_000, - FskBandwidth::Bw373 => 373_600, - FskBandwidth::Bw467 => 467_000, - } - } - - /// Convert from a raw bit value. - /// - /// Invalid values will be returned in the `Err` variant of the result. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::FskBandwidth; - /// - /// assert_eq!(FskBandwidth::from_bits(0x1F), Ok(FskBandwidth::Bw4)); - /// assert_eq!(FskBandwidth::from_bits(0x17), Ok(FskBandwidth::Bw5)); - /// assert_eq!(FskBandwidth::from_bits(0x0F), Ok(FskBandwidth::Bw7)); - /// assert_eq!(FskBandwidth::from_bits(0x1E), Ok(FskBandwidth::Bw9)); - /// assert_eq!(FskBandwidth::from_bits(0x16), Ok(FskBandwidth::Bw11)); - /// assert_eq!(FskBandwidth::from_bits(0x0E), Ok(FskBandwidth::Bw14)); - /// assert_eq!(FskBandwidth::from_bits(0x1D), Ok(FskBandwidth::Bw19)); - /// assert_eq!(FskBandwidth::from_bits(0x15), Ok(FskBandwidth::Bw23)); - /// assert_eq!(FskBandwidth::from_bits(0x0D), Ok(FskBandwidth::Bw29)); - /// assert_eq!(FskBandwidth::from_bits(0x1C), Ok(FskBandwidth::Bw39)); - /// assert_eq!(FskBandwidth::from_bits(0x14), Ok(FskBandwidth::Bw46)); - /// assert_eq!(FskBandwidth::from_bits(0x0C), Ok(FskBandwidth::Bw58)); - /// assert_eq!(FskBandwidth::from_bits(0x1B), Ok(FskBandwidth::Bw78)); - /// assert_eq!(FskBandwidth::from_bits(0x13), Ok(FskBandwidth::Bw93)); - /// assert_eq!(FskBandwidth::from_bits(0x0B), Ok(FskBandwidth::Bw117)); - /// assert_eq!(FskBandwidth::from_bits(0x1A), Ok(FskBandwidth::Bw156)); - /// assert_eq!(FskBandwidth::from_bits(0x12), Ok(FskBandwidth::Bw187)); - /// assert_eq!(FskBandwidth::from_bits(0x0A), Ok(FskBandwidth::Bw234)); - /// assert_eq!(FskBandwidth::from_bits(0x19), Ok(FskBandwidth::Bw312)); - /// assert_eq!(FskBandwidth::from_bits(0x11), Ok(FskBandwidth::Bw373)); - /// assert_eq!(FskBandwidth::from_bits(0x09), Ok(FskBandwidth::Bw467)); - /// assert_eq!(FskBandwidth::from_bits(0x00), Err(0x00)); - /// ``` - pub const fn from_bits(bits: u8) -> Result { - match bits { - 0x1F => Ok(Self::Bw4), - 0x17 => Ok(Self::Bw5), - 0x0F => Ok(Self::Bw7), - 0x1E => Ok(Self::Bw9), - 0x16 => Ok(Self::Bw11), - 0x0E => Ok(Self::Bw14), - 0x1D => Ok(Self::Bw19), - 0x15 => Ok(Self::Bw23), - 0x0D => Ok(Self::Bw29), - 0x1C => Ok(Self::Bw39), - 0x14 => Ok(Self::Bw46), - 0x0C => Ok(Self::Bw58), - 0x1B => Ok(Self::Bw78), - 0x13 => Ok(Self::Bw93), - 0x0B => Ok(Self::Bw117), - 0x1A => Ok(Self::Bw156), - 0x12 => Ok(Self::Bw187), - 0x0A => Ok(Self::Bw234), - 0x19 => Ok(Self::Bw312), - 0x11 => Ok(Self::Bw373), - 0x09 => Ok(Self::Bw467), - x => Err(x), - } - } -} - -impl Ord for FskBandwidth { - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - self.hertz().cmp(&other.hertz()) - } -} - -impl PartialOrd for FskBandwidth { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.hertz().cmp(&other.hertz())) - } -} - -/// Pulse shaping options for [`FskModParams`]. -#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum FskPulseShape { - /// No filtering applied. - None = 0b00, - /// Gaussian BT 0.3 - Bt03 = 0x08, - /// Gaussian BT 0.5 - Bt05 = 0x09, - /// Gaussian BT 0.7 - Bt07 = 0x0A, - /// Gaussian BT 1.0 - Bt10 = 0x0B, -} - -/// Bitrate argument for [`FskModParams::set_bitrate`] and -/// [`BpskModParams::set_bitrate`]. -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub struct FskBitrate { - bits: u32, -} - -impl FskBitrate { - /// Create a new `FskBitrate` from a bitrate in bits per second. - /// - /// This the resulting value will be rounded down, and will saturate if - /// `bps` is outside of the theoretical limits. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::FskBitrate; - /// - /// const BITRATE: FskBitrate = FskBitrate::from_bps(9600); - /// assert_eq!(BITRATE.as_bps(), 9600); - /// ``` - pub const fn from_bps(bps: u32) -> Self { - const MAX: u32 = 0x00FF_FFFF; - if bps == 0 { - Self { bits: MAX } - } else { - let bits: u32 = 32 * 32_000_000 / bps; - if bits > MAX { - Self { bits: MAX } - } else { - Self { bits } - } - } - } - - /// Create a new `FskBitrate` from a raw bit value. - /// - /// bits = 32 × 32 MHz / bitrate - /// - /// **Note:** Only the first 24 bits of the `u32` are used, the `bits` - /// argument will be masked. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::FskBitrate; - /// - /// const BITRATE: FskBitrate = FskBitrate::from_raw(0x7D00); - /// assert_eq!(BITRATE.as_bps(), 32_000); - /// ``` - pub const fn from_raw(bits: u32) -> Self { - Self { - bits: bits & 0x00FF_FFFF, - } - } - - /// Return the bitrate in bits per second, rounded down. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::FskBitrate; - /// - /// const BITS_PER_SEC: u32 = 9600; - /// const BITRATE: FskBitrate = FskBitrate::from_bps(BITS_PER_SEC); - /// assert_eq!(BITRATE.as_bps(), BITS_PER_SEC); - /// ``` - pub const fn as_bps(&self) -> u32 { - if self.bits == 0 { - 0 - } else { - 32 * 32_000_000 / self.bits - } - } - - pub(crate) const fn into_bits(self) -> u32 { - self.bits - } -} - -impl Ord for FskBitrate { - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - self.as_bps().cmp(&other.as_bps()) - } -} - -impl PartialOrd for FskBitrate { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.as_bps().cmp(&other.as_bps())) - } -} - -/// Frequency deviation argument for [`FskModParams::set_fdev`] -#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct FskFdev { - bits: u32, -} - -impl FskFdev { - /// Create a new `FskFdev` from a frequency deviation in hertz, rounded - /// down. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::FskFdev; - /// - /// const FDEV: FskFdev = FskFdev::from_hertz(31_250); - /// assert_eq!(FDEV.as_hertz(), 31_250); - /// ``` - pub const fn from_hertz(hz: u32) -> Self { - Self { - bits: ((hz as u64) * (1 << 25) / 32_000_000) as u32 & 0x00FF_FFFF, - } - } - - /// Create a new `FskFdev` from a raw bit value. - /// - /// bits = fdev × 225 / 32 MHz - /// - /// **Note:** Only the first 24 bits of the `u32` are used, the `bits` - /// argument will be masked. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::FskFdev; - /// - /// const FDEV: FskFdev = FskFdev::from_raw(0x8000); - /// assert_eq!(FDEV.as_hertz(), 31_250); - /// ``` - pub const fn from_raw(bits: u32) -> Self { - Self { - bits: bits & 0x00FF_FFFF, - } - } - - /// Return the frequency deviation in hertz, rounded down. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::FskFdev; - /// - /// const HERTZ: u32 = 31_250; - /// const FDEV: FskFdev = FskFdev::from_hertz(HERTZ); - /// assert_eq!(FDEV.as_hertz(), HERTZ); - /// ``` - pub const fn as_hertz(&self) -> u32 { - ((self.bits as u64) * 32_000_000 / (1 << 25)) as u32 - } - - pub(crate) const fn into_bits(self) -> u32 { - self.bits - } -} - -/// (G)FSK modulation parameters. -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct FskModParams { - buf: [u8; 9], -} - -impl FskModParams { - /// Create a new `FskModParams` struct. - /// - /// This is the same as `default`, but in a `const` function. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::FskModParams; - /// - /// const MOD_PARAMS: FskModParams = FskModParams::new(); - /// ``` - pub const fn new() -> FskModParams { - FskModParams { - buf: [ - super::OpCode::SetModulationParams as u8, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ], - } - .set_bitrate(FskBitrate::from_bps(50_000)) - .set_pulse_shape(FskPulseShape::None) - .set_bandwidth(FskBandwidth::Bw58) - .set_fdev(FskFdev::from_hertz(25_000)) - } - - /// Get the bitrate. - /// - /// # Example - /// - /// Setting the bitrate to 32,000 bits per second. - /// - /// ``` - /// use stm32wlxx_hal::subghz::{FskBitrate, FskModParams}; - /// - /// const BITRATE: FskBitrate = FskBitrate::from_bps(32_000); - /// const MOD_PARAMS: FskModParams = FskModParams::new().set_bitrate(BITRATE); - /// assert_eq!(MOD_PARAMS.bitrate(), BITRATE); - /// ``` - pub const fn bitrate(&self) -> FskBitrate { - let raw: u32 = u32::from_be_bytes([0, self.buf[1], self.buf[2], self.buf[3]]); - FskBitrate::from_raw(raw) - } - - /// Set the bitrate. - /// - /// # Example - /// - /// Setting the bitrate to 32,000 bits per second. - /// - /// ``` - /// use stm32wlxx_hal::subghz::{FskBitrate, FskModParams}; - /// - /// const BITRATE: FskBitrate = FskBitrate::from_bps(32_000); - /// const MOD_PARAMS: FskModParams = FskModParams::new().set_bitrate(BITRATE); - /// # assert_eq!(MOD_PARAMS.as_slice()[1], 0x00); - /// # assert_eq!(MOD_PARAMS.as_slice()[2], 0x7D); - /// # assert_eq!(MOD_PARAMS.as_slice()[3], 0x00); - /// ``` - #[must_use = "set_bitrate returns a modified FskModParams"] - pub const fn set_bitrate(mut self, bitrate: FskBitrate) -> FskModParams { - let bits: u32 = bitrate.into_bits(); - self.buf[1] = ((bits >> 16) & 0xFF) as u8; - self.buf[2] = ((bits >> 8) & 0xFF) as u8; - self.buf[3] = (bits & 0xFF) as u8; - self - } - - /// Set the pulse shaping. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{FskModParams, FskPulseShape}; - /// - /// const MOD_PARAMS: FskModParams = FskModParams::new().set_pulse_shape(FskPulseShape::Bt03); - /// # assert_eq!(MOD_PARAMS.as_slice()[4], 0x08); - /// ``` - #[must_use = "set_pulse_shape returns a modified FskModParams"] - pub const fn set_pulse_shape(mut self, shape: FskPulseShape) -> FskModParams { - self.buf[4] = shape as u8; - self - } - - /// Get the bandwidth. - /// - /// Values that do not correspond to a valid [`FskBandwidth`] will be - /// returned in the `Err` variant of the result. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{FskBandwidth, FskModParams}; - /// - /// const MOD_PARAMS: FskModParams = FskModParams::new().set_bandwidth(FskBandwidth::Bw9); - /// assert_eq!(MOD_PARAMS.bandwidth(), Ok(FskBandwidth::Bw9)); - /// ``` - pub const fn bandwidth(&self) -> Result { - FskBandwidth::from_bits(self.buf[5]) - } - - /// Set the bandwidth. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{FskBandwidth, FskModParams}; - /// - /// const MOD_PARAMS: FskModParams = FskModParams::new().set_bandwidth(FskBandwidth::Bw9); - /// # assert_eq!(MOD_PARAMS.as_slice()[5], 0x1E); - /// ``` - #[must_use = "set_pulse_shape returns a modified FskModParams"] - pub const fn set_bandwidth(mut self, bw: FskBandwidth) -> FskModParams { - self.buf[5] = bw as u8; - self - } - - /// Get the frequency deviation. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{FskFdev, FskModParams}; - /// - /// const FDEV: FskFdev = FskFdev::from_hertz(31_250); - /// const MOD_PARAMS: FskModParams = FskModParams::new().set_fdev(FDEV); - /// assert_eq!(MOD_PARAMS.fdev(), FDEV); - /// ``` - pub const fn fdev(&self) -> FskFdev { - let raw: u32 = u32::from_be_bytes([0, self.buf[6], self.buf[7], self.buf[8]]); - FskFdev::from_raw(raw) - } - - /// Set the frequency deviation. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{FskFdev, FskModParams}; - /// - /// const FDEV: FskFdev = FskFdev::from_hertz(31_250); - /// const MOD_PARAMS: FskModParams = FskModParams::new().set_fdev(FDEV); - /// # assert_eq!(MOD_PARAMS.as_slice()[6], 0x00); - /// # assert_eq!(MOD_PARAMS.as_slice()[7], 0x80); - /// # assert_eq!(MOD_PARAMS.as_slice()[8], 0x00); - /// ``` - #[must_use = "set_fdev returns a modified FskModParams"] - pub const fn set_fdev(mut self, fdev: FskFdev) -> FskModParams { - let bits: u32 = fdev.into_bits(); - self.buf[6] = ((bits >> 16) & 0xFF) as u8; - self.buf[7] = ((bits >> 8) & 0xFF) as u8; - self.buf[8] = (bits & 0xFF) as u8; - self - } - /// Returns `true` if the modulation parameters are valid. - /// - /// The bandwidth must be chosen so that: - /// - /// [`FskBandwidth`] > [`FskBitrate`] + 2 × [`FskFdev`] + frequency error - /// - /// Where frequency error = 2 × HSE32FREQ error. - /// - /// The datasheet (DS13293 Rev 1) gives these requirements for the HSE32 - /// frequency tolerance: - /// - /// * Initial: ±10 ppm - /// * Over temperature (-20 to 70 °C): ±10 ppm - /// * Aging over 10 years: ±10 ppm - /// - /// # Example - /// - /// Checking valid parameters at compile-time - /// - /// ``` - /// extern crate static_assertions as sa; - /// use stm32wlxx_hal::subghz::{FskBandwidth, FskBitrate, FskFdev, FskModParams, FskPulseShape}; - /// - /// const MOD_PARAMS: FskModParams = FskModParams::new() - /// .set_bitrate(FskBitrate::from_bps(20_000)) - /// .set_pulse_shape(FskPulseShape::Bt03) - /// .set_bandwidth(FskBandwidth::Bw58) - /// .set_fdev(FskFdev::from_hertz(10_000)); - /// - /// // 30 PPM is wost case (if the HSE32 crystal meets requirements) - /// sa::const_assert!(MOD_PARAMS.is_valid(30)); - /// ``` - #[must_use = "the return value indicates if the modulation parameters are valid"] - pub const fn is_valid(&self, ppm: u8) -> bool { - let bw: u32 = match self.bandwidth() { - Ok(bw) => bw.hertz(), - Err(_) => return false, - }; - let br: u32 = self.bitrate().as_bps(); - let fdev: u32 = self.fdev().as_hertz(); - let hse_err: u32 = 32 * (ppm as u32); - let freq_err: u32 = 2 * hse_err; - - bw > br + 2 * fdev + freq_err - } - - /// Returns `true` if the modulation parameters are valid for a worst-case - /// crystal tolerance. - /// - /// This is equivalent to [`is_valid`](Self::is_valid) with a `ppm` argument - /// of 30. - #[must_use = "the return value indicates if the modulation parameters are valid"] - pub const fn is_valid_worst_case(&self) -> bool { - self.is_valid(30) - } - - /// Extracts a slice containing the packet. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{FskBandwidth, FskBitrate, FskFdev, FskModParams, FskPulseShape}; - /// - /// const BITRATE: FskBitrate = FskBitrate::from_bps(20_000); - /// const PULSE_SHAPE: FskPulseShape = FskPulseShape::Bt03; - /// const BW: FskBandwidth = FskBandwidth::Bw58; - /// const FDEV: FskFdev = FskFdev::from_hertz(10_000); - /// - /// const MOD_PARAMS: FskModParams = FskModParams::new() - /// .set_bitrate(BITRATE) - /// .set_pulse_shape(PULSE_SHAPE) - /// .set_bandwidth(BW) - /// .set_fdev(FDEV); - /// - /// assert_eq!( - /// MOD_PARAMS.as_slice(), - /// &[0x8B, 0x00, 0xC8, 0x00, 0x08, 0x0C, 0x00, 0x28, 0xF5] - /// ); - /// ``` - pub const fn as_slice(&self) -> &[u8] { - &self.buf - } -} - -impl Default for FskModParams { - fn default() -> Self { - Self::new() - } -} - -/// LoRa spreading factor. -/// -/// Argument of [`LoRaModParams::set_sf`]. -/// -/// Higher spreading factors improve receiver sensitivity, but reduce bit rate -/// and increase power consumption. -#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u8)] -pub enum SpreadingFactor { - /// Spreading factor 5. - Sf5 = 0x05, - /// Spreading factor 6. - Sf6 = 0x06, - /// Spreading factor 7. - Sf7 = 0x07, - /// Spreading factor 8. - Sf8 = 0x08, - /// Spreading factor 9. - Sf9 = 0x09, - /// Spreading factor 10. - Sf10 = 0x0A, - /// Spreading factor 11. - Sf11 = 0x0B, - /// Spreading factor 12. - Sf12 = 0x0C, -} - -impl From for u8 { - fn from(sf: SpreadingFactor) -> Self { - sf as u8 - } -} - -/// LoRa bandwidth. -/// -/// Argument of [`LoRaModParams::set_bw`]. -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u8)] -pub enum LoRaBandwidth { - /// 7.81 kHz - Bw7 = 0x00, - /// 10.42 kHz - Bw10 = 0x08, - /// 15.63 kHz - Bw15 = 0x01, - /// 20.83 kHz - Bw20 = 0x09, - /// 31.25 kHz - Bw31 = 0x02, - /// 41.67 kHz - Bw41 = 0x0A, - /// 62.50 kHz - Bw62 = 0x03, - /// 125 kHz - Bw125 = 0x04, - /// 250 kHz - Bw250 = 0x05, - /// 500 kHz - Bw500 = 0x06, -} - -impl LoRaBandwidth { - /// Get the bandwidth in hertz. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::LoRaBandwidth; - /// - /// assert_eq!(LoRaBandwidth::Bw7.hertz(), 7_810); - /// assert_eq!(LoRaBandwidth::Bw10.hertz(), 10_420); - /// assert_eq!(LoRaBandwidth::Bw15.hertz(), 15_630); - /// assert_eq!(LoRaBandwidth::Bw20.hertz(), 20_830); - /// assert_eq!(LoRaBandwidth::Bw31.hertz(), 31_250); - /// assert_eq!(LoRaBandwidth::Bw41.hertz(), 41_670); - /// assert_eq!(LoRaBandwidth::Bw62.hertz(), 62_500); - /// assert_eq!(LoRaBandwidth::Bw125.hertz(), 125_000); - /// assert_eq!(LoRaBandwidth::Bw250.hertz(), 250_000); - /// assert_eq!(LoRaBandwidth::Bw500.hertz(), 500_000); - /// ``` - pub const fn hertz(&self) -> u32 { - match self { - LoRaBandwidth::Bw7 => 7_810, - LoRaBandwidth::Bw10 => 10_420, - LoRaBandwidth::Bw15 => 15_630, - LoRaBandwidth::Bw20 => 20_830, - LoRaBandwidth::Bw31 => 31_250, - LoRaBandwidth::Bw41 => 41_670, - LoRaBandwidth::Bw62 => 62_500, - LoRaBandwidth::Bw125 => 125_000, - LoRaBandwidth::Bw250 => 250_000, - LoRaBandwidth::Bw500 => 500_000, - } - } -} - -impl Ord for LoRaBandwidth { - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - self.hertz().cmp(&other.hertz()) - } -} - -impl PartialOrd for LoRaBandwidth { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.hertz().cmp(&other.hertz())) - } -} - -/// LoRa forward error correction coding rate. -/// -/// Argument of [`LoRaModParams::set_cr`]. -/// -/// A higher coding rate provides better immunity to interference at the expense -/// of longer transmission time. -/// In normal conditions [`CodingRate::Cr45`] provides the best trade off. -/// In case of strong interference, a higher coding rate may be used. -#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u8)] -pub enum CodingRate { - /// No forward error correction coding rate 4/4 - /// - /// Overhead ratio of 1 - Cr44 = 0x00, - /// Forward error correction coding rate 4/5 - /// - /// Overhead ratio of 1.25 - Cr45 = 0x1, - /// Forward error correction coding rate 4/6 - /// - /// Overhead ratio of 1.5 - Cr46 = 0x2, - /// Forward error correction coding rate 4/7 - /// - /// Overhead ratio of 1.75 - Cr47 = 0x3, - /// Forward error correction coding rate 4/8 - /// - /// Overhead ratio of 2 - Cr48 = 0x4, -} - -/// LoRa modulation parameters. -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] - -pub struct LoRaModParams { - buf: [u8; 5], -} - -impl LoRaModParams { - /// Create a new `LoRaModParams` struct. - /// - /// This is the same as `default`, but in a `const` function. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::LoRaModParams; - /// - /// const MOD_PARAMS: LoRaModParams = LoRaModParams::new(); - /// assert_eq!(MOD_PARAMS, LoRaModParams::default()); - /// ``` - pub const fn new() -> LoRaModParams { - LoRaModParams { - buf: [ - super::OpCode::SetModulationParams as u8, - 0x05, // valid spreading factor - 0x00, - 0x00, - 0x00, - ], - } - } - - /// Set the spreading factor. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{LoRaModParams, SpreadingFactor}; - /// - /// const MOD_PARAMS: LoRaModParams = LoRaModParams::new().set_sf(SpreadingFactor::Sf7); - /// # assert_eq!(MOD_PARAMS.as_slice(), &[0x8B, 0x07, 0x00, 0x00, 0x00]); - /// ``` - #[must_use = "set_sf returns a modified LoRaModParams"] - pub const fn set_sf(mut self, sf: SpreadingFactor) -> Self { - self.buf[1] = sf as u8; - self - } - - /// Set the bandwidth. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{LoRaBandwidth, LoRaModParams}; - /// - /// const MOD_PARAMS: LoRaModParams = LoRaModParams::new().set_bw(LoRaBandwidth::Bw125); - /// # assert_eq!(MOD_PARAMS.as_slice(), &[0x8B, 0x05, 0x04, 0x00, 0x00]); - /// ``` - #[must_use = "set_bw returns a modified LoRaModParams"] - pub const fn set_bw(mut self, bw: LoRaBandwidth) -> Self { - self.buf[2] = bw as u8; - self - } - - /// Set the forward error correction coding rate. - /// - /// See [`CodingRate`] for more information. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{CodingRate, LoRaModParams}; - /// - /// const MOD_PARAMS: LoRaModParams = LoRaModParams::new().set_cr(CodingRate::Cr45); - /// # assert_eq!(MOD_PARAMS.as_slice(), &[0x8B, 0x05, 0x00, 0x01, 0x00]); - /// ``` - #[must_use = "set_cr returns a modified LoRaModParams"] - pub const fn set_cr(mut self, cr: CodingRate) -> Self { - self.buf[3] = cr as u8; - self - } - - /// Set low data rate optimization enable. - /// - /// For low data rates (typically high SF or low BW) and very long payloads - /// (may last several seconds), the low data rate optimization (LDRO) can be - /// enabled. - /// This reduces the number of bits per symbol to the given SF minus 2, - /// to allow the receiver to have a better tracking of the LoRa receive - /// signal. - /// Depending on the payload length, the low data rate optimization is - /// usually recommended when the LoRa symbol time is equal or above - /// 16.38 ms. - /// When using LoRa modulation, the total frequency drift over the packet - /// time must be kept lower than Freq_drift_max: - /// - /// Freq_drift_max = BW / (3 × 2SF) - /// - /// When possible, enabling the low data rate optimization, relaxes the - /// total frequency drift over the packet time by 16: - /// - /// Freq_drift_optimise_max = 16 × Freq_drift_max - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::LoRaModParams; - /// - /// const MOD_PARAMS: LoRaModParams = LoRaModParams::new().set_ldro_en(true); - /// # assert_eq!(MOD_PARAMS.as_slice(), &[0x8B, 0x05, 0x00, 0x00, 0x01]); - /// ``` - #[must_use = "set_ldro_en returns a modified LoRaModParams"] - pub const fn set_ldro_en(mut self, en: bool) -> Self { - self.buf[4] = en as u8; - self - } - - /// Extracts a slice containing the packet. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{CodingRate, LoRaBandwidth, LoRaModParams, SpreadingFactor}; - /// - /// const MOD_PARAMS: LoRaModParams = LoRaModParams::new() - /// .set_sf(SpreadingFactor::Sf7) - /// .set_bw(LoRaBandwidth::Bw125) - /// .set_cr(CodingRate::Cr45) - /// .set_ldro_en(false); - /// - /// assert_eq!(MOD_PARAMS.as_slice(), &[0x8B, 0x07, 0x04, 0x01, 0x00]); - /// ``` - pub const fn as_slice(&self) -> &[u8] { - &self.buf - } -} - -impl Default for LoRaModParams { - fn default() -> Self { - Self::new() - } -} - -/// BPSK modulation parameters. -/// -/// **Note:** There is no method to set the pulse shape because there is only -/// one valid pulse shape (Gaussian BT 0.5). -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct BpskModParams { - buf: [u8; 5], -} - -impl BpskModParams { - /// Create a new `BpskModParams` struct. - /// - /// This is the same as `default`, but in a `const` function. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::BpskModParams; - /// - /// const MOD_PARAMS: BpskModParams = BpskModParams::new(); - /// assert_eq!(MOD_PARAMS, BpskModParams::default()); - /// ``` - pub const fn new() -> BpskModParams { - const OPCODE: u8 = super::OpCode::SetModulationParams as u8; - BpskModParams { - buf: [OPCODE, 0x1A, 0x0A, 0xAA, 0x16], - } - } - - /// Set the bitrate. - /// - /// # Example - /// - /// Setting the bitrate to 600 bits per second. - /// - /// ``` - /// use stm32wlxx_hal::subghz::{BpskModParams, FskBitrate}; - /// - /// const BITRATE: FskBitrate = FskBitrate::from_bps(600); - /// const MOD_PARAMS: BpskModParams = BpskModParams::new().set_bitrate(BITRATE); - /// # assert_eq!(MOD_PARAMS.as_slice()[1], 0x1A); - /// # assert_eq!(MOD_PARAMS.as_slice()[2], 0x0A); - /// # assert_eq!(MOD_PARAMS.as_slice()[3], 0xAA); - /// ``` - #[must_use = "set_bitrate returns a modified BpskModParams"] - pub const fn set_bitrate(mut self, bitrate: FskBitrate) -> BpskModParams { - let bits: u32 = bitrate.into_bits(); - self.buf[1] = ((bits >> 16) & 0xFF) as u8; - self.buf[2] = ((bits >> 8) & 0xFF) as u8; - self.buf[3] = (bits & 0xFF) as u8; - self - } - - /// Extracts a slice containing the packet. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{BpskModParams, FskBitrate}; - /// - /// const BITRATE: FskBitrate = FskBitrate::from_bps(100); - /// const MOD_PARAMS: BpskModParams = BpskModParams::new().set_bitrate(BITRATE); - /// assert_eq!(MOD_PARAMS.as_slice(), [0x8B, 0x9C, 0x40, 0x00, 0x16]); - /// ``` - pub const fn as_slice(&self) -> &[u8] { - &self.buf - } -} - -impl Default for BpskModParams { - fn default() -> Self { - Self::new() - } -} - -#[cfg(test)] -mod test { - use super::{FskBandwidth, FskBitrate, FskFdev, LoRaBandwidth}; - - #[test] - fn fsk_bw_ord() { - assert!((FskBandwidth::Bw4 as u8) > (FskBandwidth::Bw5 as u8)); - assert!(FskBandwidth::Bw4 < FskBandwidth::Bw5); - assert!(FskBandwidth::Bw5 > FskBandwidth::Bw4); - } - - #[test] - fn lora_bw_ord() { - assert!((LoRaBandwidth::Bw10 as u8) > (LoRaBandwidth::Bw15 as u8)); - assert!(LoRaBandwidth::Bw10 < LoRaBandwidth::Bw15); - assert!(LoRaBandwidth::Bw15 > LoRaBandwidth::Bw10); - } - - #[test] - fn fsk_bitrate_ord() { - assert!(FskBitrate::from_bps(9600) > FskBitrate::from_bps(4800)); - assert!(FskBitrate::from_bps(4800) < FskBitrate::from_bps(9600)); - } - - #[test] - fn fsk_bitrate_as_bps_limits() { - const ZERO: FskBitrate = FskBitrate::from_raw(0); - const ONE: FskBitrate = FskBitrate::from_raw(1); - const MAX: FskBitrate = FskBitrate::from_raw(u32::MAX); - - assert_eq!(ZERO.as_bps(), 0); - assert_eq!(ONE.as_bps(), 1_024_000_000); - assert_eq!(MAX.as_bps(), 61); - } - - #[test] - fn fsk_bitrate_from_bps_limits() { - const ZERO: FskBitrate = FskBitrate::from_bps(0); - const ONE: FskBitrate = FskBitrate::from_bps(1); - const MAX: FskBitrate = FskBitrate::from_bps(u32::MAX); - - assert_eq!(ZERO.as_bps(), 61); - assert_eq!(ONE.as_bps(), 61); - assert_eq!(MAX.as_bps(), 0); - } - - #[test] - fn fsk_fdev_ord() { - assert!(FskFdev::from_hertz(30_000) > FskFdev::from_hertz(20_000)); - assert!(FskFdev::from_hertz(20_000) < FskFdev::from_hertz(30_000)); - } - - #[test] - fn fsk_fdev_as_hertz_limits() { - const ZERO: FskFdev = FskFdev::from_raw(0); - const ONE: FskFdev = FskFdev::from_raw(1); - const MAX: FskFdev = FskFdev::from_raw(u32::MAX); - - assert_eq!(ZERO.as_hertz(), 0); - assert_eq!(ONE.as_hertz(), 0); - assert_eq!(MAX.as_hertz(), 15_999_999); - } - - #[test] - fn fsk_fdev_from_hertz_limits() { - const ZERO: FskFdev = FskFdev::from_hertz(0); - const ONE: FskFdev = FskFdev::from_hertz(1); - const MAX: FskFdev = FskFdev::from_hertz(u32::MAX); - - assert_eq!(ZERO.as_hertz(), 0); - assert_eq!(ONE.as_hertz(), 0); - assert_eq!(MAX.as_hertz(), 6_967_294); - } -} diff --git a/embassy-stm32/src/subghz/ocp.rs b/embassy-stm32/src/subghz/ocp.rs deleted file mode 100644 index 81e89c217..000000000 --- a/embassy-stm32/src/subghz/ocp.rs +++ /dev/null @@ -1,14 +0,0 @@ -/// Power amplifier over current protection. -/// -/// Used by [`set_pa_ocp`]. -/// -/// [`set_pa_ocp`]: super::SubGhz::set_pa_ocp -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u8)] -pub enum Ocp { - /// Maximum 60mA current for LP PA mode. - Max60m = 0x18, - /// Maximum 140mA for HP PA mode. - Max140m = 0x38, -} diff --git a/embassy-stm32/src/subghz/op_error.rs b/embassy-stm32/src/subghz/op_error.rs deleted file mode 100644 index b17b99205..000000000 --- a/embassy-stm32/src/subghz/op_error.rs +++ /dev/null @@ -1,48 +0,0 @@ -/// Operation Errors. -/// -/// Returned by [`op_error`]. -/// -/// [`op_error`]: super::SubGhz::op_error -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u8)] -pub enum OpError { - /// PA ramping failed - PaRampError = 8, - /// RF-PLL locking failed - PllLockError = 6, - /// HSE32 clock startup failed - XoscStartError = 5, - /// Image calibration failed - ImageCalibrationError = 4, - /// RF-ADC calibration failed - AdcCalibrationError = 3, - /// RF-PLL calibration failed - PllCalibrationError = 2, - /// Sub-GHz radio RC 13 MHz oscillator - RC13MCalibrationError = 1, - /// Sub-GHz radio RC 64 kHz oscillator - RC64KCalibrationError = 0, -} - -impl OpError { - /// Get the bitmask for the error. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::OpError; - /// - /// assert_eq!(OpError::PaRampError.mask(), 0b1_0000_0000); - /// assert_eq!(OpError::PllLockError.mask(), 0b0_0100_0000); - /// assert_eq!(OpError::XoscStartError.mask(), 0b0_0010_0000); - /// assert_eq!(OpError::ImageCalibrationError.mask(), 0b0_0001_0000); - /// assert_eq!(OpError::AdcCalibrationError.mask(), 0b0_0000_1000); - /// assert_eq!(OpError::PllCalibrationError.mask(), 0b0_0000_0100); - /// assert_eq!(OpError::RC13MCalibrationError.mask(), 0b0_0000_0010); - /// assert_eq!(OpError::RC64KCalibrationError.mask(), 0b0_0000_0001); - /// ``` - pub const fn mask(self) -> u16 { - 1 << (self as u8) - } -} diff --git a/embassy-stm32/src/subghz/pa_config.rs b/embassy-stm32/src/subghz/pa_config.rs deleted file mode 100644 index 875827bd4..000000000 --- a/embassy-stm32/src/subghz/pa_config.rs +++ /dev/null @@ -1,196 +0,0 @@ -/// Power amplifier configuration parameters. -/// -/// Argument of [`set_pa_config`]. -/// -/// [`set_pa_config`]: super::SubGhz::set_pa_config -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct PaConfig { - buf: [u8; 5], -} - -impl PaConfig { - /// Optimal settings for +15dBm output power with the low-power PA. - /// - /// This must be used with [`TxParams::LP_15`](super::TxParams::LP_15). - pub const LP_15: PaConfig = PaConfig::new().set_pa_duty_cycle(0x6).set_hp_max(0x0).set_pa(PaSel::Lp); - - /// Optimal settings for +14dBm output power with the low-power PA. - /// - /// This must be used with [`TxParams::LP_14`](super::TxParams::LP_14). - pub const LP_14: PaConfig = PaConfig::new().set_pa_duty_cycle(0x4).set_hp_max(0x0).set_pa(PaSel::Lp); - - /// Optimal settings for +10dBm output power with the low-power PA. - /// - /// This must be used with [`TxParams::LP_10`](super::TxParams::LP_10). - pub const LP_10: PaConfig = PaConfig::new().set_pa_duty_cycle(0x1).set_hp_max(0x0).set_pa(PaSel::Lp); - - /// Optimal settings for +22dBm output power with the high-power PA. - /// - /// This must be used with [`TxParams::HP`](super::TxParams::HP). - pub const HP_22: PaConfig = PaConfig::new().set_pa_duty_cycle(0x4).set_hp_max(0x7).set_pa(PaSel::Hp); - - /// Optimal settings for +20dBm output power with the high-power PA. - /// - /// This must be used with [`TxParams::HP`](super::TxParams::HP). - pub const HP_20: PaConfig = PaConfig::new().set_pa_duty_cycle(0x3).set_hp_max(0x5).set_pa(PaSel::Hp); - - /// Optimal settings for +17dBm output power with the high-power PA. - /// - /// This must be used with [`TxParams::HP`](super::TxParams::HP). - pub const HP_17: PaConfig = PaConfig::new().set_pa_duty_cycle(0x2).set_hp_max(0x3).set_pa(PaSel::Hp); - - /// Optimal settings for +14dBm output power with the high-power PA. - /// - /// This must be used with [`TxParams::HP`](super::TxParams::HP). - pub const HP_14: PaConfig = PaConfig::new().set_pa_duty_cycle(0x2).set_hp_max(0x2).set_pa(PaSel::Hp); - - /// Create a new `PaConfig` struct. - /// - /// This is the same as `default`, but in a `const` function. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::PaConfig; - /// - /// const PA_CONFIG: PaConfig = PaConfig::new(); - /// ``` - pub const fn new() -> PaConfig { - PaConfig { - buf: [super::OpCode::SetPaConfig as u8, 0x01, 0x00, 0x01, 0x01], - } - } - - /// Set the power amplifier duty cycle (conduit angle) control. - /// - /// **Note:** Only the first 3 bits of the `pa_duty_cycle` argument are used. - /// - /// Duty cycle = 0.2 + 0.04 × bits - /// - /// # Caution - /// - /// The following restrictions must be observed to avoid over-stress on the PA: - /// * LP PA mode with synthesis frequency > 400 MHz, `pa_duty_cycle` must be < 0x7. - /// * LP PA mode with synthesis frequency < 400 MHz, `pa_duty_cycle` must be < 0x4. - /// * HP PA mode, `pa_duty_cycle` must be < 0x4 - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{PaConfig, PaSel}; - /// - /// const PA_CONFIG: PaConfig = PaConfig::new().set_pa(PaSel::Lp).set_pa_duty_cycle(0x4); - /// # assert_eq!(PA_CONFIG.as_slice()[1], 0x04); - /// ``` - #[must_use = "set_pa_duty_cycle returns a modified PaConfig"] - pub const fn set_pa_duty_cycle(mut self, pa_duty_cycle: u8) -> PaConfig { - self.buf[1] = pa_duty_cycle & 0b111; - self - } - - /// Set the high power amplifier output power. - /// - /// **Note:** Only the first 3 bits of the `hp_max` argument are used. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{PaConfig, PaSel}; - /// - /// const PA_CONFIG: PaConfig = PaConfig::new().set_pa(PaSel::Hp).set_hp_max(0x2); - /// # assert_eq!(PA_CONFIG.as_slice()[2], 0x02); - /// ``` - #[must_use = "set_hp_max returns a modified PaConfig"] - pub const fn set_hp_max(mut self, hp_max: u8) -> PaConfig { - self.buf[2] = hp_max & 0b111; - self - } - - /// Set the power amplifier to use, low or high power. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{PaConfig, PaSel}; - /// - /// const PA_CONFIG_HP: PaConfig = PaConfig::new().set_pa(PaSel::Hp); - /// const PA_CONFIG_LP: PaConfig = PaConfig::new().set_pa(PaSel::Lp); - /// # assert_eq!(PA_CONFIG_HP.as_slice()[3], 0x00); - /// # assert_eq!(PA_CONFIG_LP.as_slice()[3], 0x01); - /// ``` - #[must_use = "set_pa returns a modified PaConfig"] - pub const fn set_pa(mut self, pa: PaSel) -> PaConfig { - self.buf[3] = pa as u8; - self - } - - /// Extracts a slice containing the packet. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{PaConfig, PaSel}; - /// - /// const PA_CONFIG: PaConfig = PaConfig::new() - /// .set_pa(PaSel::Hp) - /// .set_pa_duty_cycle(0x2) - /// .set_hp_max(0x3); - /// - /// assert_eq!(PA_CONFIG.as_slice(), &[0x95, 0x2, 0x03, 0x00, 0x01]); - /// ``` - pub const fn as_slice(&self) -> &[u8] { - &self.buf - } -} - -impl Default for PaConfig { - fn default() -> Self { - Self::new() - } -} - -/// Power amplifier selection. -/// -/// Argument of [`PaConfig::set_pa`]. -#[repr(u8)] -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub enum PaSel { - /// High power amplifier. - Hp = 0b0, - /// Low power amplifier. - Lp = 0b1, -} - -impl PartialOrd for PaSel { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for PaSel { - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - match (self, other) { - (PaSel::Hp, PaSel::Hp) | (PaSel::Lp, PaSel::Lp) => core::cmp::Ordering::Equal, - (PaSel::Hp, PaSel::Lp) => core::cmp::Ordering::Greater, - (PaSel::Lp, PaSel::Hp) => core::cmp::Ordering::Less, - } - } -} - -impl Default for PaSel { - fn default() -> Self { - PaSel::Lp - } -} - -#[cfg(test)] -mod test { - use super::PaSel; - - #[test] - fn pa_sel_ord() { - assert!(PaSel::Lp < PaSel::Hp); - assert!(PaSel::Hp > PaSel::Lp); - } -} diff --git a/embassy-stm32/src/subghz/packet_params.rs b/embassy-stm32/src/subghz/packet_params.rs deleted file mode 100644 index db1fb88d9..000000000 --- a/embassy-stm32/src/subghz/packet_params.rs +++ /dev/null @@ -1,534 +0,0 @@ -/// Preamble detection length for [`GenericPacketParams`]. -#[repr(u8)] -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum PreambleDetection { - /// Preamble detection disabled. - Disabled = 0x0, - /// 8-bit preamble detection. - Bit8 = 0x4, - /// 16-bit preamble detection. - Bit16 = 0x5, - /// 24-bit preamble detection. - Bit24 = 0x6, - /// 32-bit preamble detection. - Bit32 = 0x7, -} - -/// Address comparison/filtering for [`GenericPacketParams`]. -#[repr(u8)] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum AddrComp { - /// Address comparison/filtering disabled. - Disabled = 0x0, - /// Address comparison/filtering on node address. - Node = 0x1, - /// Address comparison/filtering on node and broadcast addresses. - Broadcast = 0x2, -} - -/// Packet header type. -/// -/// Argument of [`GenericPacketParams::set_header_type`] and -/// [`LoRaPacketParams::set_header_type`]. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum HeaderType { - /// Fixed; payload length and header field not added to packet. - Fixed, - /// Variable; payload length and header field added to packet. - Variable, -} - -impl HeaderType { - pub(crate) const fn to_bits_generic(self) -> u8 { - match self { - HeaderType::Fixed => 0, - HeaderType::Variable => 1, - } - } - - pub(crate) const fn to_bits_lora(self) -> u8 { - match self { - HeaderType::Fixed => 1, - HeaderType::Variable => 0, - } - } -} - -/// CRC type definition for [`GenericPacketParams`]. -#[repr(u8)] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum CrcType { - /// 1-byte CRC. - Byte1 = 0x0, - /// CRC disabled. - Disabled = 0x1, - /// 2-byte CRC. - Byte2 = 0x2, - /// 1-byte inverted CRC. - Byte1Inverted = 0x4, - /// 2-byte inverted CRC. - Byte2Inverted = 0x6, -} - -/// Packet parameters for [`set_packet_params`]. -/// -/// [`set_packet_params`]: super::SubGhz::set_packet_params -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct GenericPacketParams { - buf: [u8; 10], -} - -impl GenericPacketParams { - /// Create a new `GenericPacketParams`. - /// - /// This is the same as `default`, but in a `const` function. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::GenericPacketParams; - /// - /// const PKT_PARAMS: GenericPacketParams = GenericPacketParams::new(); - /// assert_eq!(PKT_PARAMS, GenericPacketParams::default()); - /// ``` - pub const fn new() -> GenericPacketParams { - const OPCODE: u8 = super::OpCode::SetPacketParams as u8; - // const variable ensure the compile always optimizes the methods - const NEW: GenericPacketParams = GenericPacketParams { - buf: [OPCODE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], - } - .set_preamble_len(1) - .set_preamble_detection(PreambleDetection::Disabled) - .set_sync_word_len(0) - .set_addr_comp(AddrComp::Disabled) - .set_header_type(HeaderType::Fixed) - .set_payload_len(1); - - NEW - } - - /// Preamble length in number of symbols. - /// - /// Values of zero are invalid, and will automatically be set to 1. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::GenericPacketParams; - /// - /// const PKT_PARAMS: GenericPacketParams = GenericPacketParams::new().set_preamble_len(0x1234); - /// # assert_eq!(PKT_PARAMS.as_slice()[1], 0x12); - /// # assert_eq!(PKT_PARAMS.as_slice()[2], 0x34); - /// ``` - #[must_use = "preamble_length returns a modified GenericPacketParams"] - pub const fn set_preamble_len(mut self, mut len: u16) -> GenericPacketParams { - if len == 0 { - len = 1 - } - self.buf[1] = ((len >> 8) & 0xFF) as u8; - self.buf[2] = (len & 0xFF) as u8; - self - } - - /// Preamble detection length in number of bit symbols. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{GenericPacketParams, PreambleDetection}; - /// - /// const PKT_PARAMS: GenericPacketParams = - /// GenericPacketParams::new().set_preamble_detection(PreambleDetection::Bit8); - /// # assert_eq!(PKT_PARAMS.as_slice()[3], 0x4); - /// ``` - #[must_use = "set_preamble_detection returns a modified GenericPacketParams"] - pub const fn set_preamble_detection(mut self, pb_det: PreambleDetection) -> GenericPacketParams { - self.buf[3] = pb_det as u8; - self - } - - /// Sync word length in number of bit symbols. - /// - /// Valid values are `0x00` - `0x40` for 0 to 64-bits respectively. - /// Values that exceed the maximum will saturate at `0x40`. - /// - /// # Example - /// - /// Set the sync word length to 4 bytes (16 bits). - /// - /// ``` - /// use stm32wlxx_hal::subghz::GenericPacketParams; - /// - /// const PKT_PARAMS: GenericPacketParams = GenericPacketParams::new().set_sync_word_len(16); - /// # assert_eq!(PKT_PARAMS.as_slice()[4], 0x10); - /// ``` - #[must_use = "set_sync_word_len returns a modified GenericPacketParams"] - pub const fn set_sync_word_len(mut self, len: u8) -> GenericPacketParams { - const MAX: u8 = 0x40; - if len > MAX { - self.buf[4] = MAX; - } else { - self.buf[4] = len; - } - self - } - - /// Address comparison/filtering. - /// - /// # Example - /// - /// Enable address on the node address. - /// - /// ``` - /// use stm32wlxx_hal::subghz::{AddrComp, GenericPacketParams}; - /// - /// const PKT_PARAMS: GenericPacketParams = - /// GenericPacketParams::new().set_addr_comp(AddrComp::Node); - /// # assert_eq!(PKT_PARAMS.as_slice()[5], 0x01); - /// ``` - #[must_use = "set_addr_comp returns a modified GenericPacketParams"] - pub const fn set_addr_comp(mut self, addr_comp: AddrComp) -> GenericPacketParams { - self.buf[5] = addr_comp as u8; - self - } - - /// Header type definition. - /// - /// **Note:** The reference manual calls this packet type, but that results - /// in a conflicting variable name for the modulation scheme, which the - /// reference manual also calls packet type. - /// - /// # Example - /// - /// Set the header type to a variable length. - /// - /// ``` - /// use stm32wlxx_hal::subghz::{GenericPacketParams, HeaderType}; - /// - /// const PKT_PARAMS: GenericPacketParams = - /// GenericPacketParams::new().set_header_type(HeaderType::Variable); - /// # assert_eq!(PKT_PARAMS.as_slice()[6], 0x01); - /// ``` - #[must_use = "set_header_type returns a modified GenericPacketParams"] - pub const fn set_header_type(mut self, header_type: HeaderType) -> GenericPacketParams { - self.buf[6] = header_type.to_bits_generic(); - self - } - - /// Set the payload length in bytes. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::GenericPacketParams; - /// - /// const PKT_PARAMS: GenericPacketParams = GenericPacketParams::new().set_payload_len(12); - /// # assert_eq!(PKT_PARAMS.as_slice()[7], 12); - /// ``` - #[must_use = "set_payload_len returns a modified GenericPacketParams"] - pub const fn set_payload_len(mut self, len: u8) -> GenericPacketParams { - self.buf[7] = len; - self - } - - /// CRC type definition. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{CrcType, GenericPacketParams}; - /// - /// const PKT_PARAMS: GenericPacketParams = - /// GenericPacketParams::new().set_crc_type(CrcType::Byte2Inverted); - /// # assert_eq!(PKT_PARAMS.as_slice()[8], 0x6); - /// ``` - #[must_use = "set_payload_len returns a modified GenericPacketParams"] - pub const fn set_crc_type(mut self, crc_type: CrcType) -> GenericPacketParams { - self.buf[8] = crc_type as u8; - self - } - - /// Whitening enable. - /// - /// # Example - /// - /// Enable whitening. - /// - /// ``` - /// use stm32wlxx_hal::subghz::GenericPacketParams; - /// - /// const PKT_PARAMS: GenericPacketParams = GenericPacketParams::new().set_whitening_enable(true); - /// # assert_eq!(PKT_PARAMS.as_slice()[9], 1); - /// ``` - #[must_use = "set_whitening_enable returns a modified GenericPacketParams"] - pub const fn set_whitening_enable(mut self, en: bool) -> GenericPacketParams { - self.buf[9] = en as u8; - self - } - - /// Extracts a slice containing the packet. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{ - /// AddrComp, CrcType, GenericPacketParams, HeaderType, PreambleDetection, - /// }; - /// - /// const PKT_PARAMS: GenericPacketParams = GenericPacketParams::new() - /// .set_preamble_len(8) - /// .set_preamble_detection(PreambleDetection::Disabled) - /// .set_sync_word_len(2) - /// .set_addr_comp(AddrComp::Disabled) - /// .set_header_type(HeaderType::Fixed) - /// .set_payload_len(128) - /// .set_crc_type(CrcType::Byte2) - /// .set_whitening_enable(true); - /// - /// assert_eq!( - /// PKT_PARAMS.as_slice(), - /// &[0x8C, 0x00, 0x08, 0x00, 0x02, 0x00, 0x00, 0x80, 0x02, 0x01] - /// ); - /// ``` - pub const fn as_slice(&self) -> &[u8] { - &self.buf - } -} - -impl Default for GenericPacketParams { - fn default() -> Self { - Self::new() - } -} - -/// Packet parameters for [`set_lora_packet_params`]. -/// -/// [`set_lora_packet_params`]: super::SubGhz::set_lora_packet_params -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct LoRaPacketParams { - buf: [u8; 7], -} - -impl LoRaPacketParams { - /// Create a new `LoRaPacketParams`. - /// - /// This is the same as `default`, but in a `const` function. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::LoRaPacketParams; - /// - /// const PKT_PARAMS: LoRaPacketParams = LoRaPacketParams::new(); - /// assert_eq!(PKT_PARAMS, LoRaPacketParams::default()); - /// ``` - pub const fn new() -> LoRaPacketParams { - const OPCODE: u8 = super::OpCode::SetPacketParams as u8; - // const variable ensure the compile always optimizes the methods - const NEW: LoRaPacketParams = LoRaPacketParams { - buf: [OPCODE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], - } - .set_preamble_len(1) - .set_header_type(HeaderType::Fixed) - .set_payload_len(1) - .set_crc_en(true) - .set_invert_iq(false); - - NEW - } - - /// Preamble length in number of symbols. - /// - /// Values of zero are invalid, and will automatically be set to 1. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::LoRaPacketParams; - /// - /// const PKT_PARAMS: LoRaPacketParams = LoRaPacketParams::new().set_preamble_len(0x1234); - /// # assert_eq!(PKT_PARAMS.as_slice()[1], 0x12); - /// # assert_eq!(PKT_PARAMS.as_slice()[2], 0x34); - /// ``` - #[must_use = "preamble_length returns a modified LoRaPacketParams"] - pub const fn set_preamble_len(mut self, mut len: u16) -> LoRaPacketParams { - if len == 0 { - len = 1 - } - self.buf[1] = ((len >> 8) & 0xFF) as u8; - self.buf[2] = (len & 0xFF) as u8; - self - } - - /// Header type (fixed or variable). - /// - /// # Example - /// - /// Set the payload type to a fixed length. - /// - /// ``` - /// use stm32wlxx_hal::subghz::{HeaderType, LoRaPacketParams}; - /// - /// const PKT_PARAMS: LoRaPacketParams = LoRaPacketParams::new().set_header_type(HeaderType::Fixed); - /// # assert_eq!(PKT_PARAMS.as_slice()[3], 0x01); - /// ``` - #[must_use = "set_header_type returns a modified LoRaPacketParams"] - pub const fn set_header_type(mut self, header_type: HeaderType) -> LoRaPacketParams { - self.buf[3] = header_type.to_bits_lora(); - self - } - - /// Set the payload length in bytes. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::LoRaPacketParams; - /// - /// const PKT_PARAMS: LoRaPacketParams = LoRaPacketParams::new().set_payload_len(12); - /// # assert_eq!(PKT_PARAMS.as_slice()[4], 12); - /// ``` - #[must_use = "set_payload_len returns a modified LoRaPacketParams"] - pub const fn set_payload_len(mut self, len: u8) -> LoRaPacketParams { - self.buf[4] = len; - self - } - - /// CRC enable. - /// - /// # Example - /// - /// Enable CRC. - /// - /// ``` - /// use stm32wlxx_hal::subghz::LoRaPacketParams; - /// - /// const PKT_PARAMS: LoRaPacketParams = LoRaPacketParams::new().set_crc_en(true); - /// # assert_eq!(PKT_PARAMS.as_slice()[5], 0x1); - /// ``` - #[must_use = "set_crc_en returns a modified LoRaPacketParams"] - pub const fn set_crc_en(mut self, en: bool) -> LoRaPacketParams { - self.buf[5] = en as u8; - self - } - - /// IQ setup. - /// - /// # Example - /// - /// Use an inverted IQ setup. - /// - /// ``` - /// use stm32wlxx_hal::subghz::LoRaPacketParams; - /// - /// const PKT_PARAMS: LoRaPacketParams = LoRaPacketParams::new().set_invert_iq(true); - /// # assert_eq!(PKT_PARAMS.as_slice()[6], 0x1); - /// ``` - #[must_use = "set_invert_iq returns a modified LoRaPacketParams"] - pub const fn set_invert_iq(mut self, invert: bool) -> LoRaPacketParams { - self.buf[6] = invert as u8; - self - } - - /// Extracts a slice containing the packet. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{HeaderType, LoRaPacketParams}; - /// - /// const PKT_PARAMS: LoRaPacketParams = LoRaPacketParams::new() - /// .set_preamble_len(5 * 8) - /// .set_header_type(HeaderType::Fixed) - /// .set_payload_len(64) - /// .set_crc_en(true) - /// .set_invert_iq(true); - /// - /// assert_eq!( - /// PKT_PARAMS.as_slice(), - /// &[0x8C, 0x00, 0x28, 0x01, 0x40, 0x01, 0x01] - /// ); - /// ``` - pub const fn as_slice(&self) -> &[u8] { - &self.buf - } -} - -impl Default for LoRaPacketParams { - fn default() -> Self { - Self::new() - } -} - -/// Packet parameters for [`set_bpsk_packet_params`]. -/// -/// [`set_bpsk_packet_params`]: super::SubGhz::set_bpsk_packet_params -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct BpskPacketParams { - buf: [u8; 2], -} - -impl BpskPacketParams { - /// Create a new `BpskPacketParams`. - /// - /// This is the same as `default`, but in a `const` function. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::BpskPacketParams; - /// - /// const PKT_PARAMS: BpskPacketParams = BpskPacketParams::new(); - /// assert_eq!(PKT_PARAMS, BpskPacketParams::default()); - /// ``` - pub const fn new() -> BpskPacketParams { - BpskPacketParams { - buf: [super::OpCode::SetPacketParams as u8, 0x00], - } - } - - /// Set the payload length in bytes. - /// - /// The length includes preamble, sync word, device ID, and CRC. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::BpskPacketParams; - /// - /// const PKT_PARAMS: BpskPacketParams = BpskPacketParams::new().set_payload_len(12); - /// # assert_eq!(PKT_PARAMS.as_slice()[1], 12); - /// ``` - #[must_use = "set_payload_len returns a modified BpskPacketParams"] - pub const fn set_payload_len(mut self, len: u8) -> BpskPacketParams { - self.buf[1] = len; - self - } - - /// Extracts a slice containing the packet. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{BpskPacketParams, HeaderType}; - /// - /// const PKT_PARAMS: BpskPacketParams = BpskPacketParams::new().set_payload_len(24); - /// - /// assert_eq!(PKT_PARAMS.as_slice(), &[0x8C, 24]); - /// ``` - pub const fn as_slice(&self) -> &[u8] { - &self.buf - } -} - -impl Default for BpskPacketParams { - fn default() -> Self { - Self::new() - } -} diff --git a/embassy-stm32/src/subghz/packet_status.rs b/embassy-stm32/src/subghz/packet_status.rs deleted file mode 100644 index b3acd73ce..000000000 --- a/embassy-stm32/src/subghz/packet_status.rs +++ /dev/null @@ -1,282 +0,0 @@ -use super::{Ratio, Status}; - -/// (G)FSK packet status. -/// -/// Returned by [`fsk_packet_status`]. -/// -/// [`fsk_packet_status`]: super::SubGhz::fsk_packet_status -#[derive(Clone, Copy, PartialEq, Eq)] -pub struct FskPacketStatus { - buf: [u8; 4], -} - -impl From<[u8; 4]> for FskPacketStatus { - fn from(buf: [u8; 4]) -> Self { - FskPacketStatus { buf } - } -} - -impl FskPacketStatus { - /// Get the status. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{CmdStatus, FskPacketStatus, Status, StatusMode}; - /// - /// let example_data_from_radio: [u8; 4] = [0x54, 0, 0, 0]; - /// let pkt_status: FskPacketStatus = FskPacketStatus::from(example_data_from_radio); - /// let status: Status = pkt_status.status(); - /// assert_eq!(status.mode(), Ok(StatusMode::Rx)); - /// assert_eq!(status.cmd(), Ok(CmdStatus::Avaliable)); - /// ``` - pub const fn status(&self) -> Status { - Status::from_raw(self.buf[0]) - } - - /// Returns `true` if a preamble error occurred. - pub const fn preamble_err(&self) -> bool { - (self.buf[1] & (1 << 7)) != 0 - } - - /// Returns `true` if a synchronization error occurred. - pub const fn sync_err(&self) -> bool { - (self.buf[1] & (1 << 6)) != 0 - } - - /// Returns `true` if an address error occurred. - pub const fn addr_err(&self) -> bool { - (self.buf[1] & (1 << 5)) != 0 - } - - /// Returns `true` if an CRC error occurred. - pub const fn crc_err(&self) -> bool { - (self.buf[1] & (1 << 4)) != 0 - } - - /// Returns `true` if a length error occurred. - pub const fn length_err(&self) -> bool { - (self.buf[1] & (1 << 3)) != 0 - } - - /// Returns `true` if an abort error occurred. - pub const fn abort_err(&self) -> bool { - (self.buf[1] & (1 << 2)) != 0 - } - - /// Returns `true` if a packet is received. - pub const fn pkt_received(&self) -> bool { - (self.buf[1] & (1 << 1)) != 0 - } - - /// Returns `true` when a packet has been sent. - pub const fn pkt_sent(&self) -> bool { - (self.buf[1] & 1) != 0 - } - - /// Returns `true` if any error occurred. - pub const fn any_err(&self) -> bool { - (self.buf[1] & 0xFC) != 0 - } - - /// RSSI level when the synchronization address is detected. - /// - /// Units are in dBm. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::{subghz::FskPacketStatus, Ratio}; - /// - /// let example_data_from_radio: [u8; 4] = [0, 0, 80, 0]; - /// let pkt_status: FskPacketStatus = FskPacketStatus::from(example_data_from_radio); - /// assert_eq!(pkt_status.rssi_sync().to_integer(), -40); - /// ``` - pub fn rssi_sync(&self) -> Ratio { - Ratio::new_raw(i16::from(self.buf[2]), -2) - } - - /// Return the RSSI level over the received packet. - /// - /// Units are in dBm. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::{subghz::FskPacketStatus, Ratio}; - /// - /// let example_data_from_radio: [u8; 4] = [0, 0, 0, 100]; - /// let pkt_status: FskPacketStatus = FskPacketStatus::from(example_data_from_radio); - /// assert_eq!(pkt_status.rssi_avg().to_integer(), -50); - /// ``` - pub fn rssi_avg(&self) -> Ratio { - Ratio::new_raw(i16::from(self.buf[3]), -2) - } -} - -#[cfg(feature = "defmt")] -impl defmt::Format for FskPacketStatus { - fn format(&self, fmt: defmt::Formatter) { - defmt::write!( - fmt, - r#"FskPacketStatus {{ - status: {}, - preamble_err: {}, - sync_err: {}, - addr_err: {}, - crc_err: {}, - length_err: {}, - abort_err: {}, - pkt_received: {}, - pkt_sent: {}, - rssi_sync: {}, - rssi_avg: {}, -}}"#, - self.status(), - self.preamble_err(), - self.sync_err(), - self.addr_err(), - self.crc_err(), - self.length_err(), - self.abort_err(), - self.pkt_received(), - self.pkt_sent(), - self.rssi_sync().to_integer(), - self.rssi_avg().to_integer() - ) - } -} - -impl core::fmt::Debug for FskPacketStatus { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("FskPacketStatus") - .field("status", &self.status()) - .field("preamble_err", &self.preamble_err()) - .field("sync_err", &self.sync_err()) - .field("addr_err", &self.addr_err()) - .field("crc_err", &self.crc_err()) - .field("length_err", &self.length_err()) - .field("abort_err", &self.abort_err()) - .field("pkt_received", &self.pkt_received()) - .field("pkt_sent", &self.pkt_sent()) - .field("rssi_sync", &self.rssi_sync().to_integer()) - .field("rssi_avg", &self.rssi_avg().to_integer()) - .finish() - } -} - -/// (G)FSK packet status. -/// -/// Returned by [`lora_packet_status`]. -/// -/// [`lora_packet_status`]: super::SubGhz::lora_packet_status -#[derive(Clone, Copy, PartialEq, Eq)] -pub struct LoRaPacketStatus { - buf: [u8; 4], -} - -impl From<[u8; 4]> for LoRaPacketStatus { - fn from(buf: [u8; 4]) -> Self { - LoRaPacketStatus { buf } - } -} - -impl LoRaPacketStatus { - /// Get the status. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{CmdStatus, LoRaPacketStatus, Status, StatusMode}; - /// - /// let example_data_from_radio: [u8; 4] = [0x54, 0, 0, 0]; - /// let pkt_status: LoRaPacketStatus = LoRaPacketStatus::from(example_data_from_radio); - /// let status: Status = pkt_status.status(); - /// assert_eq!(status.mode(), Ok(StatusMode::Rx)); - /// assert_eq!(status.cmd(), Ok(CmdStatus::Avaliable)); - /// ``` - pub const fn status(&self) -> Status { - Status::from_raw(self.buf[0]) - } - - /// Average RSSI level over the received packet. - /// - /// Units are in dBm. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::{subghz::LoRaPacketStatus, Ratio}; - /// - /// let example_data_from_radio: [u8; 4] = [0, 80, 0, 0]; - /// let pkt_status: LoRaPacketStatus = LoRaPacketStatus::from(example_data_from_radio); - /// assert_eq!(pkt_status.rssi_pkt().to_integer(), -40); - /// ``` - pub fn rssi_pkt(&self) -> Ratio { - Ratio::new_raw(i16::from(self.buf[1]), -2) - } - - /// Estimation of SNR over the received packet. - /// - /// Units are in dB. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::{subghz::LoRaPacketStatus, Ratio}; - /// - /// let example_data_from_radio: [u8; 4] = [0, 0, 40, 0]; - /// let pkt_status: LoRaPacketStatus = LoRaPacketStatus::from(example_data_from_radio); - /// assert_eq!(pkt_status.snr_pkt().to_integer(), 10); - /// ``` - pub fn snr_pkt(&self) -> Ratio { - Ratio::new_raw(i16::from(self.buf[2]), 4) - } - - /// Estimation of RSSI level of the LoRa signal after despreading. - /// - /// Units are in dBm. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::{subghz::LoRaPacketStatus, Ratio}; - /// - /// let example_data_from_radio: [u8; 4] = [0, 0, 0, 80]; - /// let pkt_status: LoRaPacketStatus = LoRaPacketStatus::from(example_data_from_radio); - /// assert_eq!(pkt_status.signal_rssi_pkt().to_integer(), -40); - /// ``` - pub fn signal_rssi_pkt(&self) -> Ratio { - Ratio::new_raw(i16::from(self.buf[3]), -2) - } -} - -#[cfg(feature = "defmt")] -impl defmt::Format for LoRaPacketStatus { - fn format(&self, fmt: defmt::Formatter) { - defmt::write!( - fmt, - r#"LoRaPacketStatus {{ - status: {}, - rssi_pkt: {}, - snr_pkt: {}, - signal_rssi_pkt: {}, -}}"#, - self.status(), - self.rssi_pkt().to_integer(), - self.snr_pkt().to_integer(), - self.signal_rssi_pkt().to_integer(), - ) - } -} - -impl core::fmt::Debug for LoRaPacketStatus { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("LoRaPacketStatus") - .field("status", &self.status()) - .field("rssi_pkt", &self.rssi_pkt().to_integer()) - .field("snr_pkt", &self.snr_pkt().to_integer()) - .field("signal_rssi_pkt", &self.signal_rssi_pkt().to_integer()) - .finish() - } -} diff --git a/embassy-stm32/src/subghz/packet_type.rs b/embassy-stm32/src/subghz/packet_type.rs deleted file mode 100644 index 88c62bb6a..000000000 --- a/embassy-stm32/src/subghz/packet_type.rs +++ /dev/null @@ -1,44 +0,0 @@ -/// Packet type definition. -/// -/// Argument of [`set_packet_type`] -/// -/// [`set_packet_type`]: super::SubGhz::set_packet_type -#[repr(u8)] -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum PacketType { - /// FSK (frequency shift keying) generic packet type. - Fsk = 0, - /// LoRa (long range) packet type. - LoRa = 1, - /// BPSK (binary phase shift keying) packet type. - Bpsk = 2, - /// MSK (minimum shift keying) generic packet type. - Msk = 3, -} - -impl PacketType { - /// Create a new `PacketType` from bits. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::PacketType; - /// - /// assert_eq!(PacketType::from_raw(0), Ok(PacketType::Fsk)); - /// assert_eq!(PacketType::from_raw(1), Ok(PacketType::LoRa)); - /// assert_eq!(PacketType::from_raw(2), Ok(PacketType::Bpsk)); - /// assert_eq!(PacketType::from_raw(3), Ok(PacketType::Msk)); - /// // Other values are reserved - /// assert_eq!(PacketType::from_raw(4), Err(4)); - /// ``` - pub const fn from_raw(bits: u8) -> Result { - match bits { - 0 => Ok(PacketType::Fsk), - 1 => Ok(PacketType::LoRa), - 2 => Ok(PacketType::Bpsk), - 3 => Ok(PacketType::Msk), - _ => Err(bits), - } - } -} diff --git a/embassy-stm32/src/subghz/pkt_ctrl.rs b/embassy-stm32/src/subghz/pkt_ctrl.rs deleted file mode 100644 index 265833e35..000000000 --- a/embassy-stm32/src/subghz/pkt_ctrl.rs +++ /dev/null @@ -1,247 +0,0 @@ -/// Generic packet infinite sequence selection. -/// -/// Argument of [`PktCtrl::set_inf_seq_sel`]. -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum InfSeqSel { - /// Preamble `0x5555`. - Five = 0b00, - /// Preamble `0x0000`. - Zero = 0b01, - /// Preamble `0xFFFF`. - One = 0b10, - /// PRBS9. - Prbs9 = 0b11, -} - -impl Default for InfSeqSel { - fn default() -> Self { - InfSeqSel::Five - } -} - -/// Generic packet control. -/// -/// Argument of [`set_pkt_ctrl`](super::SubGhz::set_pkt_ctrl). -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct PktCtrl { - val: u8, -} - -impl PktCtrl { - /// Reset value of the packet control register. - pub const RESET: PktCtrl = PktCtrl { val: 0x21 }; - - /// Create a new [`PktCtrl`] structure from a raw value. - /// - /// Reserved bits will be masked. - pub const fn from_raw(raw: u8) -> Self { - Self { val: raw & 0x3F } - } - - /// Get the raw value of the [`PktCtrl`] register. - pub const fn as_bits(&self) -> u8 { - self.val - } - - /// Generic packet synchronization word detection enable. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::PktCtrl; - /// - /// const PKT_CTRL: PktCtrl = PktCtrl::RESET.set_sync_det_en(true); - /// ``` - #[must_use = "set_sync_det_en returns a modified PktCtrl"] - pub const fn set_sync_det_en(mut self, en: bool) -> PktCtrl { - if en { - self.val |= 1 << 5; - } else { - self.val &= !(1 << 5); - } - self - } - - /// Returns `true` if generic packet synchronization word detection is - /// enabled. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::PktCtrl; - /// - /// let pc: PktCtrl = PktCtrl::RESET; - /// assert_eq!(pc.sync_det_en(), true); - /// let pc: PktCtrl = pc.set_sync_det_en(false); - /// assert_eq!(pc.sync_det_en(), false); - /// let pc: PktCtrl = pc.set_sync_det_en(true); - /// assert_eq!(pc.sync_det_en(), true); - /// ``` - pub const fn sync_det_en(&self) -> bool { - self.val & (1 << 5) != 0 - } - - /// Generic packet continuous transmit enable. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::PktCtrl; - /// - /// const PKT_CTRL: PktCtrl = PktCtrl::RESET.set_cont_tx_en(true); - /// ``` - #[must_use = "set_cont_tx_en returns a modified PktCtrl"] - pub const fn set_cont_tx_en(mut self, en: bool) -> PktCtrl { - if en { - self.val |= 1 << 4; - } else { - self.val &= !(1 << 4); - } - self - } - - /// Returns `true` if generic packet continuous transmit is enabled. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::PktCtrl; - /// - /// let pc: PktCtrl = PktCtrl::RESET; - /// assert_eq!(pc.cont_tx_en(), false); - /// let pc: PktCtrl = pc.set_cont_tx_en(true); - /// assert_eq!(pc.cont_tx_en(), true); - /// let pc: PktCtrl = pc.set_cont_tx_en(false); - /// assert_eq!(pc.cont_tx_en(), false); - /// ``` - pub const fn cont_tx_en(&self) -> bool { - self.val & (1 << 4) != 0 - } - - /// Set the continuous sequence type. - #[must_use = "set_inf_seq_sel returns a modified PktCtrl"] - pub const fn set_inf_seq_sel(mut self, sel: InfSeqSel) -> PktCtrl { - self.val &= !(0b11 << 2); - self.val |= (sel as u8) << 2; - self - } - - /// Get the continuous sequence type. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{InfSeqSel, PktCtrl}; - /// - /// let pc: PktCtrl = PktCtrl::RESET; - /// assert_eq!(pc.inf_seq_sel(), InfSeqSel::Five); - /// - /// let pc: PktCtrl = pc.set_inf_seq_sel(InfSeqSel::Zero); - /// assert_eq!(pc.inf_seq_sel(), InfSeqSel::Zero); - /// - /// let pc: PktCtrl = pc.set_inf_seq_sel(InfSeqSel::One); - /// assert_eq!(pc.inf_seq_sel(), InfSeqSel::One); - /// - /// let pc: PktCtrl = pc.set_inf_seq_sel(InfSeqSel::Prbs9); - /// assert_eq!(pc.inf_seq_sel(), InfSeqSel::Prbs9); - /// - /// let pc: PktCtrl = pc.set_inf_seq_sel(InfSeqSel::Five); - /// assert_eq!(pc.inf_seq_sel(), InfSeqSel::Five); - /// ``` - pub const fn inf_seq_sel(&self) -> InfSeqSel { - match (self.val >> 2) & 0b11 { - 0b00 => InfSeqSel::Five, - 0b01 => InfSeqSel::Zero, - 0b10 => InfSeqSel::One, - _ => InfSeqSel::Prbs9, - } - } - - /// Enable infinite sequence generation. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::PktCtrl; - /// - /// const PKT_CTRL: PktCtrl = PktCtrl::RESET.set_inf_seq_en(true); - /// ``` - #[must_use = "set_inf_seq_en returns a modified PktCtrl"] - pub const fn set_inf_seq_en(mut self, en: bool) -> PktCtrl { - if en { - self.val |= 1 << 1; - } else { - self.val &= !(1 << 1); - } - self - } - - /// Returns `true` if infinite sequence generation is enabled. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::PktCtrl; - /// - /// let pc: PktCtrl = PktCtrl::RESET; - /// assert_eq!(pc.inf_seq_en(), false); - /// let pc: PktCtrl = pc.set_inf_seq_en(true); - /// assert_eq!(pc.inf_seq_en(), true); - /// let pc: PktCtrl = pc.set_inf_seq_en(false); - /// assert_eq!(pc.inf_seq_en(), false); - /// ``` - pub const fn inf_seq_en(&self) -> bool { - self.val & (1 << 1) != 0 - } - - /// Set the value of bit-8 (9th bit) for generic packet whitening. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::PktCtrl; - /// - /// const PKT_CTRL: PktCtrl = PktCtrl::RESET.set_whitening_init(true); - /// ``` - #[must_use = "set_whitening_init returns a modified PktCtrl"] - pub const fn set_whitening_init(mut self, val: bool) -> PktCtrl { - if val { - self.val |= 1; - } else { - self.val &= !1; - } - self - } - - /// Returns `true` if bit-8 of the generic packet whitening is set. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::PktCtrl; - /// - /// let pc: PktCtrl = PktCtrl::RESET; - /// assert_eq!(pc.whitening_init(), true); - /// let pc: PktCtrl = pc.set_whitening_init(false); - /// assert_eq!(pc.whitening_init(), false); - /// let pc: PktCtrl = pc.set_whitening_init(true); - /// assert_eq!(pc.whitening_init(), true); - /// ``` - pub const fn whitening_init(&self) -> bool { - self.val & 0b1 != 0 - } -} - -impl From for u8 { - fn from(pc: PktCtrl) -> Self { - pc.val - } -} - -impl Default for PktCtrl { - fn default() -> Self { - Self::RESET - } -} diff --git a/embassy-stm32/src/subghz/pmode.rs b/embassy-stm32/src/subghz/pmode.rs deleted file mode 100644 index 0c07f3195..000000000 --- a/embassy-stm32/src/subghz/pmode.rs +++ /dev/null @@ -1,27 +0,0 @@ -/// RX gain power modes. -/// -/// Argument of [`set_rx_gain`]. -/// -/// [`set_rx_gain`]: super::SubGhz::set_rx_gain -#[repr(u8)] -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum PMode { - /// Power saving mode. - /// - /// Reduces sensitivity. - #[allow(clippy::identity_op)] - PowerSaving = (0x25 << 2) | 0b00, - /// Boost mode level 1. - /// - /// Improves sensitivity at detriment of power consumption. - Boost1 = (0x25 << 2) | 0b01, - /// Boost mode level 2. - /// - /// Improves a set further sensitivity at detriment of power consumption. - Boost2 = (0x25 << 2) | 0b10, - /// Boost mode. - /// - /// Best receiver sensitivity. - Boost = (0x25 << 2) | 0b11, -} diff --git a/embassy-stm32/src/subghz/pwr_ctrl.rs b/embassy-stm32/src/subghz/pwr_ctrl.rs deleted file mode 100644 index 974bddebb..000000000 --- a/embassy-stm32/src/subghz/pwr_ctrl.rs +++ /dev/null @@ -1,160 +0,0 @@ -/// Power-supply current limit. -/// -/// Argument of [`PwrCtrl::set_current_lim`]. -#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u8)] -pub enum CurrentLim { - /// 25 mA - Milli25 = 0x0, - /// 50 mA (default) - Milli50 = 0x1, - /// 100 mA - Milli100 = 0x2, - /// 200 mA - Milli200 = 0x3, -} - -impl CurrentLim { - /// Get the SMPS drive value as milliamps. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::CurrentLim; - /// - /// assert_eq!(CurrentLim::Milli25.as_milliamps(), 25); - /// assert_eq!(CurrentLim::Milli50.as_milliamps(), 50); - /// assert_eq!(CurrentLim::Milli100.as_milliamps(), 100); - /// assert_eq!(CurrentLim::Milli200.as_milliamps(), 200); - /// ``` - pub const fn as_milliamps(&self) -> u8 { - match self { - CurrentLim::Milli25 => 25, - CurrentLim::Milli50 => 50, - CurrentLim::Milli100 => 100, - CurrentLim::Milli200 => 200, - } - } -} - -impl Default for CurrentLim { - fn default() -> Self { - CurrentLim::Milli50 - } -} - -/// Power control. -/// -/// Argument of [`set_bit_sync`](super::SubGhz::set_bit_sync). -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct PwrCtrl { - val: u8, -} - -impl PwrCtrl { - /// Power control register reset value. - pub const RESET: PwrCtrl = PwrCtrl { val: 0x50 }; - - /// Create a new [`PwrCtrl`] structure from a raw value. - /// - /// Reserved bits will be masked. - pub const fn from_raw(raw: u8) -> Self { - Self { val: raw & 0x70 } - } - - /// Get the raw value of the [`PwrCtrl`] register. - pub const fn as_bits(&self) -> u8 { - self.val - } - - /// Set the current limiter enable. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::PwrCtrl; - /// - /// const PWR_CTRL: PwrCtrl = PwrCtrl::RESET.set_current_lim_en(true); - /// # assert_eq!(u8::from(PWR_CTRL), 0x50u8); - /// ``` - #[must_use = "set_current_lim_en returns a modified PwrCtrl"] - pub const fn set_current_lim_en(mut self, en: bool) -> PwrCtrl { - if en { - self.val |= 1 << 6; - } else { - self.val &= !(1 << 6); - } - self - } - - /// Returns `true` if current limiting is enabled - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::PwrCtrl; - /// - /// let pc: PwrCtrl = PwrCtrl::RESET; - /// assert_eq!(pc.current_limit_en(), true); - /// let pc: PwrCtrl = pc.set_current_lim_en(false); - /// assert_eq!(pc.current_limit_en(), false); - /// let pc: PwrCtrl = pc.set_current_lim_en(true); - /// assert_eq!(pc.current_limit_en(), true); - /// ``` - pub const fn current_limit_en(&self) -> bool { - self.val & (1 << 6) != 0 - } - - /// Set the current limit. - #[must_use = "set_current_lim returns a modified PwrCtrl"] - pub const fn set_current_lim(mut self, lim: CurrentLim) -> PwrCtrl { - self.val &= !(0x30); - self.val |= (lim as u8) << 4; - self - } - - /// Get the current limit. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{CurrentLim, PwrCtrl}; - /// - /// let pc: PwrCtrl = PwrCtrl::RESET; - /// assert_eq!(pc.current_lim(), CurrentLim::Milli50); - /// - /// let pc: PwrCtrl = pc.set_current_lim(CurrentLim::Milli25); - /// assert_eq!(pc.current_lim(), CurrentLim::Milli25); - /// - /// let pc: PwrCtrl = pc.set_current_lim(CurrentLim::Milli50); - /// assert_eq!(pc.current_lim(), CurrentLim::Milli50); - /// - /// let pc: PwrCtrl = pc.set_current_lim(CurrentLim::Milli100); - /// assert_eq!(pc.current_lim(), CurrentLim::Milli100); - /// - /// let pc: PwrCtrl = pc.set_current_lim(CurrentLim::Milli200); - /// assert_eq!(pc.current_lim(), CurrentLim::Milli200); - /// ``` - pub const fn current_lim(&self) -> CurrentLim { - match (self.val >> 4) & 0b11 { - 0x0 => CurrentLim::Milli25, - 0x1 => CurrentLim::Milli50, - 0x2 => CurrentLim::Milli100, - _ => CurrentLim::Milli200, - } - } -} - -impl From for u8 { - fn from(bs: PwrCtrl) -> Self { - bs.val - } -} - -impl Default for PwrCtrl { - fn default() -> Self { - Self::RESET - } -} diff --git a/embassy-stm32/src/subghz/reg_mode.rs b/embassy-stm32/src/subghz/reg_mode.rs deleted file mode 100644 index b83226954..000000000 --- a/embassy-stm32/src/subghz/reg_mode.rs +++ /dev/null @@ -1,18 +0,0 @@ -/// Radio power supply selection. -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u8)] -pub enum RegMode { - /// Linear dropout regulator - Ldo = 0b0, - /// Switch mode power supply. - /// - /// Used in standby with HSE32, FS, RX, and TX modes. - Smps = 0b1, -} - -impl Default for RegMode { - fn default() -> Self { - RegMode::Ldo - } -} diff --git a/embassy-stm32/src/subghz/rf_frequency.rs b/embassy-stm32/src/subghz/rf_frequency.rs deleted file mode 100644 index 3de2f50c4..000000000 --- a/embassy-stm32/src/subghz/rf_frequency.rs +++ /dev/null @@ -1,135 +0,0 @@ -/// RF frequency structure. -/// -/// Argument of [`set_rf_frequency`]. -/// -/// [`set_rf_frequency`]: super::SubGhz::set_rf_frequency -#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct RfFreq { - buf: [u8; 5], -} - -impl RfFreq { - /// 915MHz, often used in Australia and North America. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::RfFreq; - /// - /// assert_eq!(RfFreq::F915.freq(), 915_000_000); - /// ``` - pub const F915: RfFreq = RfFreq::from_raw(0x39_30_00_00); - - /// 868MHz, often used in Europe. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::RfFreq; - /// - /// assert_eq!(RfFreq::F868.freq(), 868_000_000); - /// ``` - pub const F868: RfFreq = RfFreq::from_raw(0x36_40_00_00); - - /// 433MHz, often used in Europe. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::RfFreq; - /// - /// assert_eq!(RfFreq::F433.freq(), 433_000_000); - /// ``` - pub const F433: RfFreq = RfFreq::from_raw(0x1B_10_00_00); - - /// Create a new `RfFreq` from a raw bit value. - /// - /// The equation used to get the PLL frequency from the raw bits is: - /// - /// RFPLL = 32e6 × bits / 225 - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::RfFreq; - /// - /// const FREQ: RfFreq = RfFreq::from_raw(0x39300000); - /// assert_eq!(FREQ, RfFreq::F915); - /// ``` - pub const fn from_raw(bits: u32) -> RfFreq { - RfFreq { - buf: [ - super::OpCode::SetRfFrequency as u8, - ((bits >> 24) & 0xFF) as u8, - ((bits >> 16) & 0xFF) as u8, - ((bits >> 8) & 0xFF) as u8, - (bits & 0xFF) as u8, - ], - } - } - - /// Create a new `RfFreq` from a PLL frequency. - /// - /// The equation used to get the raw bits from the PLL frequency is: - /// - /// bits = RFPLL * 225 / 32e6 - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::RfFreq; - /// - /// const FREQ: RfFreq = RfFreq::from_frequency(915_000_000); - /// assert_eq!(FREQ, RfFreq::F915); - /// ``` - pub const fn from_frequency(freq: u32) -> RfFreq { - Self::from_raw((((freq as u64) * (1 << 25)) / 32_000_000) as u32) - } - - // Get the frequency bit value. - const fn as_bits(&self) -> u32 { - ((self.buf[1] as u32) << 24) | ((self.buf[2] as u32) << 16) | ((self.buf[3] as u32) << 8) | (self.buf[4] as u32) - } - - /// Get the actual frequency. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::RfFreq; - /// - /// assert_eq!(RfFreq::from_raw(0x39300000).freq(), 915_000_000); - /// ``` - pub fn freq(&self) -> u32 { - (32_000_000 * (self.as_bits() as u64) / (1 << 25)) as u32 - } - - /// Extracts a slice containing the packet. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::RfFreq; - /// - /// assert_eq!(RfFreq::F915.as_slice(), &[0x86, 0x39, 0x30, 0x00, 0x00]); - /// ``` - pub const fn as_slice(&self) -> &[u8] { - &self.buf - } -} - -#[cfg(test)] -mod test { - use super::RfFreq; - - #[test] - fn max() { - assert_eq!(RfFreq::from_raw(u32::MAX).freq(), 4_095_999_999); - } - - #[test] - fn min() { - assert_eq!(RfFreq::from_raw(u32::MIN).freq(), 0); - } -} diff --git a/embassy-stm32/src/subghz/rx_timeout_stop.rs b/embassy-stm32/src/subghz/rx_timeout_stop.rs deleted file mode 100644 index 1d4aaecee..000000000 --- a/embassy-stm32/src/subghz/rx_timeout_stop.rs +++ /dev/null @@ -1,21 +0,0 @@ -/// Receiver event which stops the RX timeout timer. -/// -/// Used by [`set_rx_timeout_stop`]. -/// -/// [`set_rx_timeout_stop`]: super::SubGhz::set_rx_timeout_stop -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u8)] -pub enum RxTimeoutStop { - /// Receive timeout stopped on synchronization word detection in generic - /// packet mode or header detection in LoRa packet mode. - Sync = 0b0, - /// Receive timeout stopped on preamble detection. - Preamble = 0b1, -} - -impl From for u8 { - fn from(rx_ts: RxTimeoutStop) -> Self { - rx_ts as u8 - } -} diff --git a/embassy-stm32/src/subghz/sleep_cfg.rs b/embassy-stm32/src/subghz/sleep_cfg.rs deleted file mode 100644 index 0a50e9704..000000000 --- a/embassy-stm32/src/subghz/sleep_cfg.rs +++ /dev/null @@ -1,107 +0,0 @@ -/// Startup configurations when exiting sleep mode. -/// -/// Argument of [`SleepCfg::set_startup`]. -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u8)] -pub enum Startup { - /// Cold startup when exiting Sleep mode, configuration registers reset. - Cold = 0, - /// Warm startup when exiting Sleep mode, - /// configuration registers kept in retention. - /// - /// **Note:** Only the configuration of the activated modem, - /// before going to sleep mode, is retained. - /// The configuration of the other modes is lost and must be re-configured - /// when exiting sleep mode. - Warm = 1, -} - -impl Default for Startup { - fn default() -> Self { - Startup::Warm - } -} - -/// Sleep configuration. -/// -/// Argument of [`set_sleep`]. -/// -/// [`set_sleep`]: super::SubGhz::set_sleep -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct SleepCfg(u8); - -impl SleepCfg { - /// Create a new `SleepCfg` structure. - /// - /// This is the same as `default`, but in a `const` function. - /// - /// The defaults are a warm startup, with RTC wakeup enabled. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::SleepCfg; - /// - /// const SLEEP_CFG: SleepCfg = SleepCfg::new(); - /// assert_eq!(SLEEP_CFG, SleepCfg::default()); - /// # assert_eq!(u8::from(SLEEP_CFG), 0b101); - /// ``` - pub const fn new() -> SleepCfg { - SleepCfg(0).set_startup(Startup::Warm).set_rtc_wakeup_en(true) - } - - /// Set the startup mode. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{SleepCfg, Startup}; - /// - /// const SLEEP_CFG: SleepCfg = SleepCfg::new().set_startup(Startup::Cold); - /// # assert_eq!(u8::from(SLEEP_CFG), 0b001); - /// # assert_eq!(u8::from(SLEEP_CFG.set_startup(Startup::Warm)), 0b101); - /// ``` - pub const fn set_startup(mut self, startup: Startup) -> SleepCfg { - if startup as u8 == 1 { - self.0 |= 1 << 2 - } else { - self.0 &= !(1 << 2) - } - self - } - - /// Set the RTC wakeup enable. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::SleepCfg; - /// - /// const SLEEP_CFG: SleepCfg = SleepCfg::new().set_rtc_wakeup_en(false); - /// # assert_eq!(u8::from(SLEEP_CFG), 0b100); - /// # assert_eq!(u8::from(SLEEP_CFG.set_rtc_wakeup_en(true)), 0b101); - /// ``` - #[must_use = "set_rtc_wakeup_en returns a modified SleepCfg"] - pub const fn set_rtc_wakeup_en(mut self, en: bool) -> SleepCfg { - if en { - self.0 |= 0b1 - } else { - self.0 &= !0b1 - } - self - } -} - -impl From for u8 { - fn from(sc: SleepCfg) -> Self { - sc.0 - } -} - -impl Default for SleepCfg { - fn default() -> Self { - Self::new() - } -} diff --git a/embassy-stm32/src/subghz/smps.rs b/embassy-stm32/src/subghz/smps.rs deleted file mode 100644 index 81615ea7b..000000000 --- a/embassy-stm32/src/subghz/smps.rs +++ /dev/null @@ -1,45 +0,0 @@ -/// SMPS maximum drive capability. -/// -/// Argument of [`set_smps_drv`](super::SubGhz::set_smps_drv). -#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u8)] -pub enum SmpsDrv { - /// 20 mA - Milli20 = 0x0, - /// 40 mA - Milli40 = 0x1, - /// 60 mA - Milli60 = 0x2, - /// 100 mA (default) - Milli100 = 0x3, -} - -impl SmpsDrv { - /// Get the SMPS drive value as milliamps. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::SmpsDrv; - /// - /// assert_eq!(SmpsDrv::Milli20.as_milliamps(), 20); - /// assert_eq!(SmpsDrv::Milli40.as_milliamps(), 40); - /// assert_eq!(SmpsDrv::Milli60.as_milliamps(), 60); - /// assert_eq!(SmpsDrv::Milli100.as_milliamps(), 100); - /// ``` - pub const fn as_milliamps(&self) -> u8 { - match self { - SmpsDrv::Milli20 => 20, - SmpsDrv::Milli40 => 40, - SmpsDrv::Milli60 => 60, - SmpsDrv::Milli100 => 100, - } - } -} - -impl Default for SmpsDrv { - fn default() -> Self { - SmpsDrv::Milli100 - } -} diff --git a/embassy-stm32/src/subghz/standby_clk.rs b/embassy-stm32/src/subghz/standby_clk.rs deleted file mode 100644 index c130bbee4..000000000 --- a/embassy-stm32/src/subghz/standby_clk.rs +++ /dev/null @@ -1,20 +0,0 @@ -/// Clock in standby mode. -/// -/// Used by [`set_standby`]. -/// -/// [`set_standby`]: super::SubGhz::set_standby -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u8)] -pub enum StandbyClk { - /// RC 13 MHz used in standby mode. - Rc = 0b0, - /// HSE32 used in standby mode. - Hse = 0b1, -} - -impl From for u8 { - fn from(sc: StandbyClk) -> Self { - sc as u8 - } -} diff --git a/embassy-stm32/src/subghz/stats.rs b/embassy-stm32/src/subghz/stats.rs deleted file mode 100644 index 41b7a200f..000000000 --- a/embassy-stm32/src/subghz/stats.rs +++ /dev/null @@ -1,184 +0,0 @@ -use super::Status; - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct LoRaStats; - -impl LoRaStats { - pub const fn new() -> Self { - Self {} - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct FskStats; - -impl FskStats { - pub const fn new() -> Self { - Self {} - } -} - -/// Packet statistics. -/// -/// Returned by [`fsk_stats`] and [`lora_stats`]. -/// -/// [`fsk_stats`]: super::SubGhz::fsk_stats -/// [`lora_stats`]: super::SubGhz::lora_stats -#[derive(Eq, PartialEq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct Stats { - status: Status, - pkt_rx: u16, - pkt_crc: u16, - pkt_len_or_hdr_err: u16, - ty: ModType, -} - -impl Stats { - const fn from_buf(buf: [u8; 7], ty: ModType) -> Stats { - Stats { - status: Status::from_raw(buf[0]), - pkt_rx: u16::from_be_bytes([buf[1], buf[2]]), - pkt_crc: u16::from_be_bytes([buf[3], buf[4]]), - pkt_len_or_hdr_err: u16::from_be_bytes([buf[5], buf[6]]), - ty, - } - } - - /// Get the radio status returned with the packet statistics. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{CmdStatus, FskStats, Stats, StatusMode}; - /// - /// let example_data_from_radio: [u8; 7] = [0x54, 0, 0, 0, 0, 0, 0]; - /// let stats: Stats = Stats::from_raw_fsk(example_data_from_radio); - /// assert_eq!(stats.status().mode(), Ok(StatusMode::Rx)); - /// assert_eq!(stats.status().cmd(), Ok(CmdStatus::Avaliable)); - /// ``` - pub const fn status(&self) -> Status { - self.status - } - - /// Number of packets received. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{FskStats, Stats}; - /// - /// let example_data_from_radio: [u8; 7] = [0x54, 0, 3, 0, 0, 0, 0]; - /// let stats: Stats = Stats::from_raw_fsk(example_data_from_radio); - /// assert_eq!(stats.pkt_rx(), 3); - /// ``` - pub const fn pkt_rx(&self) -> u16 { - self.pkt_rx - } - - /// Number of packets received with a payload CRC error - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{LoRaStats, Stats}; - /// - /// let example_data_from_radio: [u8; 7] = [0x54, 0, 0, 0, 1, 0, 0]; - /// let stats: Stats = Stats::from_raw_lora(example_data_from_radio); - /// assert_eq!(stats.pkt_crc(), 1); - /// ``` - pub const fn pkt_crc(&self) -> u16 { - self.pkt_crc - } -} - -impl Stats { - /// Create a new FSK packet statistics structure from a raw buffer. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{FskStats, Stats}; - /// - /// let example_data_from_radio: [u8; 7] = [0x54, 0, 0, 0, 0, 0, 0]; - /// let stats: Stats = Stats::from_raw_fsk(example_data_from_radio); - /// ``` - pub const fn from_raw_fsk(buf: [u8; 7]) -> Stats { - Self::from_buf(buf, FskStats::new()) - } - - /// Number of packets received with a payload length error. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{FskStats, Stats}; - /// - /// let example_data_from_radio: [u8; 7] = [0x54, 0, 0, 0, 0, 0, 1]; - /// let stats: Stats = Stats::from_raw_fsk(example_data_from_radio); - /// assert_eq!(stats.pkt_len_err(), 1); - /// ``` - pub const fn pkt_len_err(&self) -> u16 { - self.pkt_len_or_hdr_err - } -} - -impl Stats { - /// Create a new LoRa packet statistics structure from a raw buffer. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{LoRaStats, Stats}; - /// - /// let example_data_from_radio: [u8; 7] = [0x54, 0, 0, 0, 0, 0, 0]; - /// let stats: Stats = Stats::from_raw_lora(example_data_from_radio); - /// ``` - pub const fn from_raw_lora(buf: [u8; 7]) -> Stats { - Self::from_buf(buf, LoRaStats::new()) - } - - /// Number of packets received with a header CRC error. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{LoRaStats, Stats}; - /// - /// let example_data_from_radio: [u8; 7] = [0x54, 0, 0, 0, 0, 0, 1]; - /// let stats: Stats = Stats::from_raw_lora(example_data_from_radio); - /// assert_eq!(stats.pkt_hdr_err(), 1); - /// ``` - pub const fn pkt_hdr_err(&self) -> u16 { - self.pkt_len_or_hdr_err - } -} - -impl core::fmt::Debug for Stats { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("Stats") - .field("status", &self.status()) - .field("pkt_rx", &self.pkt_rx()) - .field("pkt_crc", &self.pkt_crc()) - .field("pkt_len_err", &self.pkt_len_err()) - .finish() - } -} - -#[cfg(test)] -mod test { - use super::super::{CmdStatus, LoRaStats, Stats, StatusMode}; - - #[test] - fn mixed() { - let example_data_from_radio: [u8; 7] = [0x54, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06]; - let stats: Stats = Stats::from_raw_lora(example_data_from_radio); - assert_eq!(stats.status().mode(), Ok(StatusMode::Rx)); - assert_eq!(stats.status().cmd(), Ok(CmdStatus::Avaliable)); - assert_eq!(stats.pkt_rx(), 0x0102); - assert_eq!(stats.pkt_crc(), 0x0304); - assert_eq!(stats.pkt_hdr_err(), 0x0506); - } -} diff --git a/embassy-stm32/src/subghz/status.rs b/embassy-stm32/src/subghz/status.rs deleted file mode 100644 index b84034f68..000000000 --- a/embassy-stm32/src/subghz/status.rs +++ /dev/null @@ -1,197 +0,0 @@ -/// sub-GHz radio operating mode. -/// -/// See `Get_Status` under section 5.8.5 "Communication status information commands" -/// in the reference manual. -/// -/// This is returned by [`Status::mode`]. -#[repr(u8)] -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum StatusMode { - /// Standby mode with RC 13MHz. - StandbyRc = 0x2, - /// Standby mode with HSE32. - StandbyHse = 0x3, - /// Frequency Synthesis mode. - Fs = 0x4, - /// Receive mode. - Rx = 0x5, - /// Transmit mode. - Tx = 0x6, -} - -impl StatusMode { - /// Create a new `StatusMode` from bits. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::StatusMode; - /// - /// assert_eq!(StatusMode::from_raw(0x2), Ok(StatusMode::StandbyRc)); - /// assert_eq!(StatusMode::from_raw(0x3), Ok(StatusMode::StandbyHse)); - /// assert_eq!(StatusMode::from_raw(0x4), Ok(StatusMode::Fs)); - /// assert_eq!(StatusMode::from_raw(0x5), Ok(StatusMode::Rx)); - /// assert_eq!(StatusMode::from_raw(0x6), Ok(StatusMode::Tx)); - /// // Other values are reserved - /// assert_eq!(StatusMode::from_raw(0), Err(0)); - /// ``` - pub const fn from_raw(bits: u8) -> Result { - match bits { - 0x2 => Ok(StatusMode::StandbyRc), - 0x3 => Ok(StatusMode::StandbyHse), - 0x4 => Ok(StatusMode::Fs), - 0x5 => Ok(StatusMode::Rx), - 0x6 => Ok(StatusMode::Tx), - _ => Err(bits), - } - } -} - -/// Command status. -/// -/// See `Get_Status` under section 5.8.5 "Communication status information commands" -/// in the reference manual. -/// -/// This is returned by [`Status::cmd`]. -#[repr(u8)] -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum CmdStatus { - /// Data available to host. - /// - /// Packet received successfully and data can be retrieved. - Avaliable = 0x2, - /// Command time out. - /// - /// Command took too long to complete triggering a sub-GHz radio watchdog - /// timeout. - Timeout = 0x3, - /// Command processing error. - /// - /// Invalid opcode or incorrect number of parameters. - ProcessingError = 0x4, - /// Command execution failure. - /// - /// Command successfully received but cannot be executed at this time, - /// requested operating mode cannot be entered or requested data cannot be - /// sent. - ExecutionFailure = 0x5, - /// Transmit command completed. - /// - /// Current packet transmission completed. - Complete = 0x6, -} - -impl CmdStatus { - /// Create a new `CmdStatus` from bits. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::CmdStatus; - /// - /// assert_eq!(CmdStatus::from_raw(0x2), Ok(CmdStatus::Avaliable)); - /// assert_eq!(CmdStatus::from_raw(0x3), Ok(CmdStatus::Timeout)); - /// assert_eq!(CmdStatus::from_raw(0x4), Ok(CmdStatus::ProcessingError)); - /// assert_eq!(CmdStatus::from_raw(0x5), Ok(CmdStatus::ExecutionFailure)); - /// assert_eq!(CmdStatus::from_raw(0x6), Ok(CmdStatus::Complete)); - /// // Other values are reserved - /// assert_eq!(CmdStatus::from_raw(0), Err(0)); - /// ``` - pub const fn from_raw(bits: u8) -> Result { - match bits { - 0x2 => Ok(CmdStatus::Avaliable), - 0x3 => Ok(CmdStatus::Timeout), - 0x4 => Ok(CmdStatus::ProcessingError), - 0x5 => Ok(CmdStatus::ExecutionFailure), - 0x6 => Ok(CmdStatus::Complete), - _ => Err(bits), - } - } -} - -/// Radio status. -/// -/// This is returned by [`status`]. -/// -/// [`status`]: super::SubGhz::status -#[derive(PartialEq, Eq, Clone, Copy)] -pub struct Status(u8); - -impl From for Status { - fn from(x: u8) -> Self { - Status(x) - } -} -impl From for u8 { - fn from(x: Status) -> Self { - x.0 - } -} - -impl Status { - /// Create a new `Status` from a raw `u8` value. - /// - /// This is the same as `Status::from(u8)`, but in a `const` function. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{CmdStatus, Status, StatusMode}; - /// - /// const STATUS: Status = Status::from_raw(0x54_u8); - /// assert_eq!(STATUS.mode(), Ok(StatusMode::Rx)); - /// assert_eq!(STATUS.cmd(), Ok(CmdStatus::Avaliable)); - /// ``` - pub const fn from_raw(value: u8) -> Status { - Status(value) - } - - /// sub-GHz radio operating mode. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{Status, StatusMode}; - /// - /// let status: Status = 0xACu8.into(); - /// assert_eq!(status.mode(), Ok(StatusMode::StandbyRc)); - /// ``` - pub const fn mode(&self) -> Result { - StatusMode::from_raw((self.0 >> 4) & 0b111) - } - - /// Command status. - /// - /// This method frequently returns reserved values such as `Err(1)`. - /// ST support has confirmed that this is normal and should be ignored. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{CmdStatus, Status}; - /// - /// let status: Status = 0xACu8.into(); - /// assert_eq!(status.cmd(), Ok(CmdStatus::Complete)); - /// ``` - pub const fn cmd(&self) -> Result { - CmdStatus::from_raw((self.0 >> 1) & 0b111) - } -} - -impl core::fmt::Debug for Status { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.debug_struct("Status") - .field("mode", &self.mode()) - .field("cmd", &self.cmd()) - .finish() - } -} - -#[cfg(feature = "defmt")] -impl defmt::Format for Status { - fn format(&self, fmt: defmt::Formatter) { - defmt::write!(fmt, "Status {{ mode: {}, cmd: {} }}", self.mode(), self.cmd()) - } -} diff --git a/embassy-stm32/src/subghz/tcxo_mode.rs b/embassy-stm32/src/subghz/tcxo_mode.rs deleted file mode 100644 index 698dee0a6..000000000 --- a/embassy-stm32/src/subghz/tcxo_mode.rs +++ /dev/null @@ -1,170 +0,0 @@ -use super::Timeout; - -/// TCXO trim. -/// -/// **Note:** To use VDDTCXO, the VDDRF supply must be at -/// least + 200 mV higher than the selected `TcxoTrim` voltage level. -/// -/// Used by [`TcxoMode`]. -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u8)] -pub enum TcxoTrim { - /// 1.6V - Volts1pt6 = 0x0, - /// 1.7V - Volts1pt7 = 0x1, - /// 1.8V - Volts1pt8 = 0x2, - /// 2.2V - Volts2pt2 = 0x3, - /// 2.4V - Volts2pt4 = 0x4, - /// 2.7V - Volts2pt7 = 0x5, - /// 3.0V - Volts3pt0 = 0x6, - /// 3.3V - Volts3pt3 = 0x7, -} - -impl core::fmt::Display for TcxoTrim { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - TcxoTrim::Volts1pt6 => write!(f, "1.6V"), - TcxoTrim::Volts1pt7 => write!(f, "1.7V"), - TcxoTrim::Volts1pt8 => write!(f, "1.8V"), - TcxoTrim::Volts2pt2 => write!(f, "2.2V"), - TcxoTrim::Volts2pt4 => write!(f, "2.4V"), - TcxoTrim::Volts2pt7 => write!(f, "2.7V"), - TcxoTrim::Volts3pt0 => write!(f, "3.0V"), - TcxoTrim::Volts3pt3 => write!(f, "3.3V"), - } - } -} - -impl TcxoTrim { - /// Get the value of the TXCO trim in millivolts. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::TcxoTrim; - /// - /// assert_eq!(TcxoTrim::Volts1pt6.as_millivolts(), 1600); - /// assert_eq!(TcxoTrim::Volts1pt7.as_millivolts(), 1700); - /// assert_eq!(TcxoTrim::Volts1pt8.as_millivolts(), 1800); - /// assert_eq!(TcxoTrim::Volts2pt2.as_millivolts(), 2200); - /// assert_eq!(TcxoTrim::Volts2pt4.as_millivolts(), 2400); - /// assert_eq!(TcxoTrim::Volts2pt7.as_millivolts(), 2700); - /// assert_eq!(TcxoTrim::Volts3pt0.as_millivolts(), 3000); - /// assert_eq!(TcxoTrim::Volts3pt3.as_millivolts(), 3300); - /// ``` - pub const fn as_millivolts(&self) -> u16 { - match self { - TcxoTrim::Volts1pt6 => 1600, - TcxoTrim::Volts1pt7 => 1700, - TcxoTrim::Volts1pt8 => 1800, - TcxoTrim::Volts2pt2 => 2200, - TcxoTrim::Volts2pt4 => 2400, - TcxoTrim::Volts2pt7 => 2700, - TcxoTrim::Volts3pt0 => 3000, - TcxoTrim::Volts3pt3 => 3300, - } - } -} - -/// TCXO trim and HSE32 ready timeout. -/// -/// Argument of [`set_tcxo_mode`]. -/// -/// [`set_tcxo_mode`]: super::SubGhz::set_tcxo_mode -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct TcxoMode { - buf: [u8; 5], -} - -impl TcxoMode { - /// Create a new `TcxoMode` struct. - /// - /// This is the same as `default`, but in a `const` function. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::TcxoMode; - /// - /// const TCXO_MODE: TcxoMode = TcxoMode::new(); - /// ``` - pub const fn new() -> TcxoMode { - TcxoMode { - buf: [super::OpCode::SetTcxoMode as u8, 0x00, 0x00, 0x00, 0x00], - } - } - - /// Set the TCXO trim. - /// - /// **Note:** To use VDDTCXO, the VDDRF supply must be - /// at least + 200 mV higher than the selected `TcxoTrim` voltage level. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{TcxoMode, TcxoTrim}; - /// - /// const TCXO_MODE: TcxoMode = TcxoMode::new().set_txco_trim(TcxoTrim::Volts1pt6); - /// # assert_eq!(TCXO_MODE.as_slice()[1], 0x00); - /// ``` - #[must_use = "set_txco_trim returns a modified TcxoMode"] - pub const fn set_txco_trim(mut self, tcxo_trim: TcxoTrim) -> TcxoMode { - self.buf[1] = tcxo_trim as u8; - self - } - - /// Set the ready timeout duration. - /// - /// # Example - /// - /// ``` - /// use core::time::Duration; - /// use stm32wlxx_hal::subghz::{TcxoMode, Timeout}; - /// - /// // 15.625 ms timeout - /// const TIMEOUT: Timeout = Timeout::from_duration_sat(Duration::from_millis(15_625)); - /// const TCXO_MODE: TcxoMode = TcxoMode::new().set_timeout(TIMEOUT); - /// # assert_eq!(TCXO_MODE.as_slice()[2], 0x0F); - /// # assert_eq!(TCXO_MODE.as_slice()[3], 0x42); - /// # assert_eq!(TCXO_MODE.as_slice()[4], 0x40); - /// ``` - #[must_use = "set_timeout returns a modified TcxoMode"] - pub const fn set_timeout(mut self, timeout: Timeout) -> TcxoMode { - let timeout_bits: u32 = timeout.into_bits(); - self.buf[2] = ((timeout_bits >> 16) & 0xFF) as u8; - self.buf[3] = ((timeout_bits >> 8) & 0xFF) as u8; - self.buf[4] = (timeout_bits & 0xFF) as u8; - self - } - - /// Extracts a slice containing the packet. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{TcxoMode, TcxoTrim, Timeout}; - /// - /// const TCXO_MODE: TcxoMode = TcxoMode::new() - /// .set_txco_trim(TcxoTrim::Volts1pt7) - /// .set_timeout(Timeout::from_raw(0x123456)); - /// assert_eq!(TCXO_MODE.as_slice(), &[0x97, 0x1, 0x12, 0x34, 0x56]); - /// ``` - pub const fn as_slice(&self) -> &[u8] { - &self.buf - } -} - -impl Default for TcxoMode { - fn default() -> Self { - Self::new() - } -} diff --git a/embassy-stm32/src/subghz/timeout.rs b/embassy-stm32/src/subghz/timeout.rs deleted file mode 100644 index 28b3b0c21..000000000 --- a/embassy-stm32/src/subghz/timeout.rs +++ /dev/null @@ -1,491 +0,0 @@ -use core::time::Duration; - -use super::ValueError; - -const fn abs_diff(a: u64, b: u64) -> u64 { - if a > b { - a - b - } else { - b - a - } -} - -/// Timeout argument. -/// -/// This is used by: -/// * [`set_rx`] -/// * [`set_tx`] -/// * [`TcxoMode`] -/// -/// Each timeout has 3 bytes, with a resolution of 15.625µs per bit, giving a -/// range of 0s to 262.143984375s. -/// -/// [`set_rx`]: super::SubGhz::set_rx -/// [`set_tx`]: super::SubGhz::set_tx -/// [`TcxoMode`]: super::TcxoMode -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct Timeout { - bits: u32, -} - -impl Timeout { - const BITS_PER_MILLI: u32 = 64; // 1e-3 / 15.625e-6 - const BITS_PER_SEC: u32 = 64_000; // 1 / 15.625e-6 - - /// Disable the timeout (0s timeout). - /// - /// # Example - /// - /// ``` - /// use core::time::Duration; - /// use stm32wlxx_hal::subghz::Timeout; - /// - /// const TIMEOUT: Timeout = Timeout::DISABLED; - /// assert_eq!(TIMEOUT.as_duration(), Duration::from_secs(0)); - /// ``` - pub const DISABLED: Timeout = Timeout { bits: 0x0 }; - - /// Minimum timeout, 15.625µs. - /// - /// # Example - /// - /// ``` - /// use core::time::Duration; - /// use stm32wlxx_hal::subghz::Timeout; - /// - /// const TIMEOUT: Timeout = Timeout::MIN; - /// assert_eq!(TIMEOUT.into_bits(), 1); - /// ``` - pub const MIN: Timeout = Timeout { bits: 1 }; - - /// Maximum timeout, 262.143984375s. - /// - /// # Example - /// - /// ``` - /// use core::time::Duration; - /// use stm32wlxx_hal::subghz::Timeout; - /// - /// const TIMEOUT: Timeout = Timeout::MAX; - /// assert_eq!(TIMEOUT.as_duration(), Duration::from_nanos(262_143_984_375)); - /// ``` - pub const MAX: Timeout = Timeout { bits: 0x00FF_FFFF }; - - /// Timeout resolution in nanoseconds, 15.625µs. - pub const RESOLUTION_NANOS: u16 = 15_625; - - /// Timeout resolution, 15.625µs. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::Timeout; - /// - /// assert_eq!( - /// Timeout::RESOLUTION.as_nanos(), - /// Timeout::RESOLUTION_NANOS as u128 - /// ); - /// ``` - pub const RESOLUTION: Duration = Duration::from_nanos(Self::RESOLUTION_NANOS as u64); - - /// Create a new timeout from a [`Duration`]. - /// - /// This will return the nearest timeout value possible, or a - /// [`ValueError`] if the value is out of bounds. - /// - /// Use [`from_millis_sat`](Self::from_millis_sat) for runtime timeout - /// construction. - /// This is not _that_ useful right now, it is simply future proofing for a - /// time when `Result::unwrap` is available for `const fn`. - /// - /// # Example - /// - /// Value within bounds: - /// - /// ``` - /// use core::time::Duration; - /// use stm32wlxx_hal::subghz::{Timeout, ValueError}; - /// - /// const MIN: Duration = Timeout::RESOLUTION; - /// assert_eq!(Timeout::from_duration(MIN).unwrap(), Timeout::MIN); - /// ``` - /// - /// Value too low: - /// - /// ``` - /// use core::time::Duration; - /// use stm32wlxx_hal::subghz::{Timeout, ValueError}; - /// - /// const LOWER_LIMIT_NANOS: u128 = 7813; - /// const TOO_LOW_NANOS: u128 = LOWER_LIMIT_NANOS - 1; - /// const TOO_LOW_DURATION: Duration = Duration::from_nanos(TOO_LOW_NANOS as u64); - /// assert_eq!( - /// Timeout::from_duration(TOO_LOW_DURATION), - /// Err(ValueError::too_low(TOO_LOW_NANOS, LOWER_LIMIT_NANOS)) - /// ); - /// ``` - /// - /// Value too high: - /// - /// ``` - /// use core::time::Duration; - /// use stm32wlxx_hal::subghz::{Timeout, ValueError}; - /// - /// const UPPER_LIMIT_NANOS: u128 = Timeout::MAX.as_nanos() as u128 + 7812; - /// const TOO_HIGH_NANOS: u128 = UPPER_LIMIT_NANOS + 1; - /// const TOO_HIGH_DURATION: Duration = Duration::from_nanos(TOO_HIGH_NANOS as u64); - /// assert_eq!( - /// Timeout::from_duration(TOO_HIGH_DURATION), - /// Err(ValueError::too_high(TOO_HIGH_NANOS, UPPER_LIMIT_NANOS)) - /// ); - /// ``` - pub const fn from_duration(duration: Duration) -> Result> { - // at the time of development many methods in - // `core::Duration` were not `const fn`, which leads to the hacks - // you see here. - let nanos: u128 = duration.as_nanos(); - const UPPER_LIMIT: u128 = Timeout::MAX.as_nanos() as u128 + (Timeout::RESOLUTION_NANOS as u128) / 2; - const LOWER_LIMIT: u128 = (((Timeout::RESOLUTION_NANOS as u128) + 1) / 2) as u128; - - if nanos > UPPER_LIMIT { - Err(ValueError::too_high(nanos, UPPER_LIMIT)) - } else if nanos < LOWER_LIMIT { - Err(ValueError::too_low(nanos, LOWER_LIMIT)) - } else { - // safe to truncate here because of previous bounds check. - let duration_nanos: u64 = nanos as u64; - - let div_floor: u64 = duration_nanos / (Self::RESOLUTION_NANOS as u64); - let div_ceil: u64 = 1 + (duration_nanos - 1) / (Self::RESOLUTION_NANOS as u64); - - let timeout_ceil: Timeout = Timeout::from_raw(div_ceil as u32); - let timeout_floor: Timeout = Timeout::from_raw(div_floor as u32); - - let error_ceil: u64 = abs_diff(timeout_ceil.as_nanos(), duration_nanos); - let error_floor: u64 = abs_diff(timeout_floor.as_nanos(), duration_nanos); - - if error_ceil < error_floor { - Ok(timeout_ceil) - } else { - Ok(timeout_floor) - } - } - } - - /// Create a new timeout from a [`Duration`]. - /// - /// This will return the nearest timeout value possible, saturating at the - /// limits. - /// - /// This is an expensive function to call outside of `const` contexts. - /// Use [`from_millis_sat`](Self::from_millis_sat) for runtime timeout - /// construction. - /// - /// # Example - /// - /// ``` - /// use core::time::Duration; - /// use stm32wlxx_hal::subghz::Timeout; - /// - /// const DURATION_MAX_NS: u64 = 262_143_984_376; - /// - /// assert_eq!( - /// Timeout::from_duration_sat(Duration::from_millis(0)), - /// Timeout::MIN - /// ); - /// assert_eq!( - /// Timeout::from_duration_sat(Duration::from_nanos(DURATION_MAX_NS)), - /// Timeout::MAX - /// ); - /// assert_eq!( - /// Timeout::from_duration_sat(Timeout::RESOLUTION).into_bits(), - /// 1 - /// ); - /// ``` - pub const fn from_duration_sat(duration: Duration) -> Timeout { - // at the time of development many methods in - // `core::Duration` were not `const fn`, which leads to the hacks - // you see here. - let nanos: u128 = duration.as_nanos(); - const UPPER_LIMIT: u128 = Timeout::MAX.as_nanos() as u128; - - if nanos > UPPER_LIMIT { - Timeout::MAX - } else if nanos < (Timeout::RESOLUTION_NANOS as u128) { - Timeout::from_raw(1) - } else { - // safe to truncate here because of previous bounds check. - let duration_nanos: u64 = duration.as_nanos() as u64; - - let div_floor: u64 = duration_nanos / (Self::RESOLUTION_NANOS as u64); - let div_ceil: u64 = 1 + (duration_nanos - 1) / (Self::RESOLUTION_NANOS as u64); - - let timeout_ceil: Timeout = Timeout::from_raw(div_ceil as u32); - let timeout_floor: Timeout = Timeout::from_raw(div_floor as u32); - - let error_ceil: u64 = abs_diff(timeout_ceil.as_nanos(), duration_nanos); - let error_floor: u64 = abs_diff(timeout_floor.as_nanos(), duration_nanos); - - if error_ceil < error_floor { - timeout_ceil - } else { - timeout_floor - } - } - } - - /// Create a new timeout from a milliseconds value. - /// - /// This will round towards zero and saturate at the limits. - /// - /// This is the preferred method to call when you need to generate a - /// timeout value at runtime. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::Timeout; - /// - /// assert_eq!(Timeout::from_millis_sat(0), Timeout::MIN); - /// assert_eq!(Timeout::from_millis_sat(262_144), Timeout::MAX); - /// assert_eq!(Timeout::from_millis_sat(1).into_bits(), 64); - /// ``` - pub const fn from_millis_sat(millis: u32) -> Timeout { - if millis == 0 { - Timeout::MIN - } else if millis >= 262_144 { - Timeout::MAX - } else { - Timeout::from_raw(millis * Self::BITS_PER_MILLI) - } - } - - /// Create a timeout from raw bits, where each bit has the resolution of - /// [`Timeout::RESOLUTION`]. - /// - /// **Note:** Only the first 24 bits of the `u32` are used, the `bits` - /// argument will be masked. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::Timeout; - /// - /// assert_eq!(Timeout::from_raw(u32::MAX), Timeout::MAX); - /// assert_eq!(Timeout::from_raw(0x00_FF_FF_FF), Timeout::MAX); - /// assert_eq!(Timeout::from_raw(1).as_duration(), Timeout::RESOLUTION); - /// assert_eq!(Timeout::from_raw(0), Timeout::DISABLED); - /// ``` - pub const fn from_raw(bits: u32) -> Timeout { - Timeout { - bits: bits & 0x00FF_FFFF, - } - } - - /// Get the timeout as nanoseconds. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::Timeout; - /// - /// assert_eq!(Timeout::MAX.as_nanos(), 262_143_984_375); - /// assert_eq!(Timeout::DISABLED.as_nanos(), 0); - /// assert_eq!(Timeout::from_raw(1).as_nanos(), 15_625); - /// assert_eq!(Timeout::from_raw(64_000).as_nanos(), 1_000_000_000); - /// ``` - pub const fn as_nanos(&self) -> u64 { - (self.bits as u64) * (Timeout::RESOLUTION_NANOS as u64) - } - - /// Get the timeout as microseconds, rounding towards zero. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::Timeout; - /// - /// assert_eq!(Timeout::MAX.as_micros(), 262_143_984); - /// assert_eq!(Timeout::DISABLED.as_micros(), 0); - /// assert_eq!(Timeout::from_raw(1).as_micros(), 15); - /// assert_eq!(Timeout::from_raw(64_000).as_micros(), 1_000_000); - /// ``` - pub const fn as_micros(&self) -> u32 { - (self.as_nanos() / 1_000) as u32 - } - - /// Get the timeout as milliseconds, rounding towards zero. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::Timeout; - /// - /// assert_eq!(Timeout::MAX.as_millis(), 262_143); - /// assert_eq!(Timeout::DISABLED.as_millis(), 0); - /// assert_eq!(Timeout::from_raw(1).as_millis(), 0); - /// assert_eq!(Timeout::from_raw(64_000).as_millis(), 1_000); - /// ``` - pub const fn as_millis(&self) -> u32 { - self.into_bits() / Self::BITS_PER_MILLI - } - - /// Get the timeout as seconds, rounding towards zero. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::Timeout; - /// - /// assert_eq!(Timeout::MAX.as_secs(), 262); - /// assert_eq!(Timeout::DISABLED.as_secs(), 0); - /// assert_eq!(Timeout::from_raw(1).as_secs(), 0); - /// assert_eq!(Timeout::from_raw(64_000).as_secs(), 1); - /// ``` - pub const fn as_secs(&self) -> u16 { - (self.into_bits() / Self::BITS_PER_SEC) as u16 - } - - /// Get the timeout as a [`Duration`]. - /// - /// # Example - /// - /// ``` - /// use core::time::Duration; - /// use stm32wlxx_hal::subghz::Timeout; - /// - /// assert_eq!( - /// Timeout::MAX.as_duration(), - /// Duration::from_nanos(262_143_984_375) - /// ); - /// assert_eq!(Timeout::DISABLED.as_duration(), Duration::from_nanos(0)); - /// assert_eq!(Timeout::from_raw(1).as_duration(), Timeout::RESOLUTION); - /// ``` - pub const fn as_duration(&self) -> Duration { - Duration::from_nanos((self.bits as u64) * (Timeout::RESOLUTION_NANOS as u64)) - } - - /// Get the bit value for the timeout. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::Timeout; - /// - /// assert_eq!(Timeout::from_raw(u32::MAX).into_bits(), 0x00FF_FFFF); - /// assert_eq!(Timeout::from_raw(1).into_bits(), 1); - /// ``` - pub const fn into_bits(self) -> u32 { - self.bits - } - - /// Get the byte value for the timeout. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::Timeout; - /// - /// assert_eq!(Timeout::from_raw(u32::MAX).as_bytes(), [0xFF, 0xFF, 0xFF]); - /// assert_eq!(Timeout::from_raw(1).as_bytes(), [0, 0, 1]); - /// ``` - pub const fn as_bytes(self) -> [u8; 3] { - [ - ((self.bits >> 16) & 0xFF) as u8, - ((self.bits >> 8) & 0xFF) as u8, - (self.bits & 0xFF) as u8, - ] - } - - /// Saturating timeout addition. Computes `self + rhs`, saturating at the - /// numeric bounds instead of overflowing. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::Timeout; - /// - /// assert_eq!( - /// Timeout::from_raw(0xFF_FF_F0).saturating_add(Timeout::from_raw(0xFF)), - /// Timeout::from_raw(0xFF_FF_FF) - /// ); - /// assert_eq!( - /// Timeout::from_raw(100).saturating_add(Timeout::from_raw(23)), - /// Timeout::from_raw(123) - /// ); - /// ``` - #[must_use = "saturating_add returns a new Timeout"] - pub const fn saturating_add(self, rhs: Self) -> Self { - // TODO: use core::cmp::min when it is const - let bits: u32 = self.bits.saturating_add(rhs.bits); - if bits > Self::MAX.bits { - Self::MAX - } else { - Self { bits } - } - } -} - -impl From for Duration { - fn from(to: Timeout) -> Self { - to.as_duration() - } -} - -impl From for [u8; 3] { - fn from(to: Timeout) -> Self { - to.as_bytes() - } -} - -impl From for embassy_time::Duration { - fn from(to: Timeout) -> Self { - embassy_time::Duration::from_micros(to.as_micros().into()) - } -} - -#[cfg(test)] -mod tests { - use core::time::Duration; - - use super::{Timeout, ValueError}; - - #[test] - fn saturate() { - assert_eq!(Timeout::from_duration_sat(Duration::from_secs(u64::MAX)), Timeout::MAX); - } - - #[test] - fn rounding() { - const NANO1: Duration = Duration::from_nanos(1); - let res_sub_1_ns: Duration = Timeout::RESOLUTION - NANO1; - let res_add_1_ns: Duration = Timeout::RESOLUTION + NANO1; - assert_eq!(Timeout::from_duration_sat(res_sub_1_ns).into_bits(), 1); - assert_eq!(Timeout::from_duration_sat(res_add_1_ns).into_bits(), 1); - } - - #[test] - fn lower_limit() { - let low: Duration = (Timeout::RESOLUTION + Duration::from_nanos(1)) / 2; - assert_eq!(Timeout::from_duration(low), Ok(Timeout::from_raw(1))); - - let too_low: Duration = low - Duration::from_nanos(1); - assert_eq!( - Timeout::from_duration(too_low), - Err(ValueError::too_low(too_low.as_nanos(), low.as_nanos())) - ); - } - - #[test] - fn upper_limit() { - let high: Duration = Timeout::MAX.as_duration() + Timeout::RESOLUTION / 2; - assert_eq!(Timeout::from_duration(high), Ok(Timeout::from_raw(0xFFFFFF))); - - let too_high: Duration = high + Duration::from_nanos(1); - assert_eq!( - Timeout::from_duration(too_high), - Err(ValueError::too_high(too_high.as_nanos(), high.as_nanos())) - ); - } -} diff --git a/embassy-stm32/src/subghz/tx_params.rs b/embassy-stm32/src/subghz/tx_params.rs deleted file mode 100644 index cede6f2c1..000000000 --- a/embassy-stm32/src/subghz/tx_params.rs +++ /dev/null @@ -1,191 +0,0 @@ -/// Power amplifier ramp time for FSK, MSK, and LoRa modulation. -/// -/// Argument of [`set_ramp_time`][`super::TxParams::set_ramp_time`]. -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[repr(u8)] -pub enum RampTime { - /// 10µs - Micros10 = 0x00, - /// 20µs - Micros20 = 0x01, - /// 40µs - Micros40 = 0x02, - /// 80µs - Micros80 = 0x03, - /// 200µs - Micros200 = 0x04, - /// 800µs - Micros800 = 0x05, - /// 1.7ms - Micros1700 = 0x06, - /// 3.4ms - Micros3400 = 0x07, -} - -impl From for u8 { - fn from(rt: RampTime) -> Self { - rt as u8 - } -} - -impl From for core::time::Duration { - fn from(rt: RampTime) -> Self { - match rt { - RampTime::Micros10 => core::time::Duration::from_micros(10), - RampTime::Micros20 => core::time::Duration::from_micros(20), - RampTime::Micros40 => core::time::Duration::from_micros(40), - RampTime::Micros80 => core::time::Duration::from_micros(80), - RampTime::Micros200 => core::time::Duration::from_micros(200), - RampTime::Micros800 => core::time::Duration::from_micros(800), - RampTime::Micros1700 => core::time::Duration::from_micros(1700), - RampTime::Micros3400 => core::time::Duration::from_micros(3400), - } - } -} - -impl From for embassy_time::Duration { - fn from(rt: RampTime) -> Self { - match rt { - RampTime::Micros10 => embassy_time::Duration::from_micros(10), - RampTime::Micros20 => embassy_time::Duration::from_micros(20), - RampTime::Micros40 => embassy_time::Duration::from_micros(40), - RampTime::Micros80 => embassy_time::Duration::from_micros(80), - RampTime::Micros200 => embassy_time::Duration::from_micros(200), - RampTime::Micros800 => embassy_time::Duration::from_micros(800), - RampTime::Micros1700 => embassy_time::Duration::from_micros(1700), - RampTime::Micros3400 => embassy_time::Duration::from_micros(3400), - } - } -} -/// Transmit parameters, output power and power amplifier ramp up time. -/// -/// Argument of [`set_tx_params`][`super::SubGhz::set_tx_params`]. -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct TxParams { - buf: [u8; 3], -} - -impl TxParams { - /// Optimal power setting for +15dBm output power with the low-power PA. - /// - /// This must be used with [`PaConfig::LP_15`](super::PaConfig::LP_15). - pub const LP_15: TxParams = TxParams::new().set_power(0x0E); - - /// Optimal power setting for +14dBm output power with the low-power PA. - /// - /// This must be used with [`PaConfig::LP_14`](super::PaConfig::LP_14). - pub const LP_14: TxParams = TxParams::new().set_power(0x0E); - - /// Optimal power setting for +10dBm output power with the low-power PA. - /// - /// This must be used with [`PaConfig::LP_10`](super::PaConfig::LP_10). - pub const LP_10: TxParams = TxParams::new().set_power(0x0D); - - /// Optimal power setting for the high-power PA. - /// - /// This must be used with one of: - /// - /// * [`PaConfig::HP_22`](super::PaConfig::HP_22) - /// * [`PaConfig::HP_20`](super::PaConfig::HP_20) - /// * [`PaConfig::HP_17`](super::PaConfig::HP_17) - /// * [`PaConfig::HP_14`](super::PaConfig::HP_14) - pub const HP: TxParams = TxParams::new().set_power(0x16); - - /// Create a new `TxParams` struct. - /// - /// This is the same as `default`, but in a `const` function. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::TxParams; - /// - /// const TX_PARAMS: TxParams = TxParams::new(); - /// assert_eq!(TX_PARAMS, TxParams::default()); - /// ``` - pub const fn new() -> TxParams { - TxParams { - buf: [super::OpCode::SetTxParams as u8, 0x00, 0x00], - } - } - - /// Set the output power. - /// - /// For low power selected in [`set_pa_config`]: - /// - /// * 0x0E: +14 dB - /// * ... - /// * 0x00: 0 dB - /// * ... - /// * 0xEF: -17 dB - /// * Others: reserved - /// - /// For high power selected in [`set_pa_config`]: - /// - /// * 0x16: +22 dB - /// * ... - /// * 0x00: 0 dB - /// * ... - /// * 0xF7: -9 dB - /// * Others: reserved - /// - /// # Example - /// - /// Set the output power to 0 dB. - /// - /// ``` - /// use stm32wlxx_hal::subghz::{RampTime, TxParams}; - /// - /// const TX_PARAMS: TxParams = TxParams::new().set_power(0x00); - /// # assert_eq!(TX_PARAMS.as_slice()[1], 0x00); - /// ``` - /// - /// [`set_pa_config`]: super::SubGhz::set_pa_config - #[must_use = "set_power returns a modified TxParams"] - pub const fn set_power(mut self, power: u8) -> TxParams { - self.buf[1] = power; - self - } - - /// Set the Power amplifier ramp time for FSK, MSK, and LoRa modulation. - /// - /// # Example - /// - /// Set the ramp time to 200 microseconds. - /// - /// ``` - /// use stm32wlxx_hal::subghz::{RampTime, TxParams}; - /// - /// const TX_PARAMS: TxParams = TxParams::new().set_ramp_time(RampTime::Micros200); - /// # assert_eq!(TX_PARAMS.as_slice()[2], 0x04); - /// ``` - #[must_use = "set_ramp_time returns a modified TxParams"] - pub const fn set_ramp_time(mut self, rt: RampTime) -> TxParams { - self.buf[2] = rt as u8; - self - } - - /// Extracts a slice containing the packet. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::{RampTime, TxParams}; - /// - /// const TX_PARAMS: TxParams = TxParams::new() - /// .set_ramp_time(RampTime::Micros80) - /// .set_power(0x0E); - /// assert_eq!(TX_PARAMS.as_slice(), &[0x8E, 0x0E, 0x03]); - /// ``` - pub const fn as_slice(&self) -> &[u8] { - &self.buf - } -} - -impl Default for TxParams { - fn default() -> Self { - Self::new() - } -} diff --git a/embassy-stm32/src/subghz/value_error.rs b/embassy-stm32/src/subghz/value_error.rs deleted file mode 100644 index 6a0b489a8..000000000 --- a/embassy-stm32/src/subghz/value_error.rs +++ /dev/null @@ -1,129 +0,0 @@ -/// Error for a value that is out-of-bounds. -/// -/// Used by [`Timeout::from_duration`]. -/// -/// [`Timeout::from_duration`]: super::Timeout::from_duration -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct ValueError { - value: T, - limit: T, - over: bool, -} - -impl ValueError { - /// Create a new `ValueError` for a value that exceeded an upper bound. - /// - /// Unfortunately panic is not available in `const fn`, so there are no - /// guarantees on the value being greater than the limit. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::ValueError; - /// - /// const ERROR: ValueError = ValueError::too_high(101u8, 100u8); - /// assert!(ERROR.over()); - /// assert!(!ERROR.under()); - /// ``` - pub const fn too_high(value: T, limit: T) -> ValueError { - ValueError { - value, - limit, - over: true, - } - } - - /// Create a new `ValueError` for a value that exceeded a lower bound. - /// - /// Unfortunately panic is not available in `const fn`, so there are no - /// guarantees on the value being less than the limit. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::ValueError; - /// - /// const ERROR: ValueError = ValueError::too_low(200u8, 201u8); - /// assert!(ERROR.under()); - /// assert!(!ERROR.over()); - /// ``` - pub const fn too_low(value: T, limit: T) -> ValueError { - ValueError { - value, - limit, - over: false, - } - } - - /// Get the value that caused the error. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::ValueError; - /// - /// const ERROR: ValueError = ValueError::too_high(101u8, 100u8); - /// assert_eq!(ERROR.value(), &101u8); - /// ``` - pub const fn value(&self) -> &T { - &self.value - } - - /// Get the limit for the value. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::ValueError; - /// - /// const ERROR: ValueError = ValueError::too_high(101u8, 100u8); - /// assert_eq!(ERROR.limit(), &100u8); - /// ``` - pub const fn limit(&self) -> &T { - &self.limit - } - - /// Returns `true` if the value was over the limit. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::ValueError; - /// - /// const ERROR: ValueError = ValueError::too_high(101u8, 100u8); - /// assert!(ERROR.over()); - /// assert!(!ERROR.under()); - /// ``` - pub const fn over(&self) -> bool { - self.over - } - - /// Returns `true` if the value was under the limit. - /// - /// # Example - /// - /// ``` - /// use stm32wlxx_hal::subghz::ValueError; - /// - /// const ERROR: ValueError = ValueError::too_low(200u8, 201u8); - /// assert!(ERROR.under()); - /// assert!(!ERROR.over()); - /// ``` - pub const fn under(&self) -> bool { - !self.over - } -} - -impl core::fmt::Display for ValueError -where - T: core::fmt::Display, -{ - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - if self.over { - write!(f, "Value is too high {} > {}", self.value, self.limit) - } else { - write!(f, "Value is too low {} < {}", self.value, self.limit) - } - } -} diff --git a/embassy-stm32/src/time.rs b/embassy-stm32/src/time.rs index 49140bbe0..604503e61 100644 --- a/embassy-stm32/src/time.rs +++ b/embassy-stm32/src/time.rs @@ -1,34 +1,79 @@ //! Time units +use core::ops::{Div, Mul}; + /// Hertz -#[derive(PartialEq, PartialOrd, Clone, Copy, Debug, Eq)] +#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Hertz(pub u32); impl Hertz { - pub fn hz(hertz: u32) -> Self { + pub const fn hz(hertz: u32) -> Self { Self(hertz) } - pub fn khz(kilohertz: u32) -> Self { + pub const fn khz(kilohertz: u32) -> Self { Self(kilohertz * 1_000) } - pub fn mhz(megahertz: u32) -> Self { + pub const fn mhz(megahertz: u32) -> Self { Self(megahertz * 1_000_000) } } /// This is a convenience shortcut for [`Hertz::hz`] -pub fn hz(hertz: u32) -> Hertz { +pub const fn hz(hertz: u32) -> Hertz { Hertz::hz(hertz) } /// This is a convenience shortcut for [`Hertz::khz`] -pub fn khz(kilohertz: u32) -> Hertz { +pub const fn khz(kilohertz: u32) -> Hertz { Hertz::khz(kilohertz) } /// This is a convenience shortcut for [`Hertz::mhz`] -pub fn mhz(megahertz: u32) -> Hertz { +pub const fn mhz(megahertz: u32) -> Hertz { Hertz::mhz(megahertz) } + +impl Mul for Hertz { + type Output = Hertz; + fn mul(self, rhs: u32) -> Self::Output { + Hertz(self.0 * rhs) + } +} + +impl Div for Hertz { + type Output = Hertz; + fn div(self, rhs: u32) -> Self::Output { + Hertz(self.0 / rhs) + } +} + +impl Mul for Hertz { + type Output = Hertz; + fn mul(self, rhs: u16) -> Self::Output { + self * (rhs as u32) + } +} + +impl Div for Hertz { + type Output = Hertz; + fn div(self, rhs: u16) -> Self::Output { + self / (rhs as u32) + } +} + +impl Mul for Hertz { + type Output = Hertz; + fn mul(self, rhs: u8) -> Self::Output { + self * (rhs as u32) + } +} + +impl Div for Hertz { + type Output = Hertz; + fn div(self, rhs: u8) -> Self::Output { + self / (rhs as u32) + } +} diff --git a/embassy-stm32/src/time_driver.rs b/embassy-stm32/src/time_driver.rs index 6989a43d3..2622442f4 100644 --- a/embassy-stm32/src/time_driver.rs +++ b/embassy-stm32/src/time_driver.rs @@ -4,13 +4,14 @@ use core::sync::atomic::{compiler_fence, Ordering}; use core::{mem, ptr}; use atomic_polyfill::{AtomicU32, AtomicU8}; +use critical_section::CriticalSection; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::blocking_mutex::Mutex; use embassy_time::driver::{AlarmHandle, Driver}; -use embassy_time::TICKS_PER_SECOND; +use embassy_time::TICK_HZ; use stm32_metapac::timer::regs; -use crate::interrupt::{CriticalSection, InterruptExt}; +use crate::interrupt::typelevel::Interrupt; use crate::pac::timer::vals; use crate::rcc::sealed::RccPeripheral; use crate::timer::sealed::{Basic16bitInstance as BasicInstance, GeneralPurpose16bitInstance as Instance}; @@ -39,6 +40,7 @@ type T = peripherals::TIM15; foreach_interrupt! { (TIM2, timer, $block:ident, UP, $irq:ident) => { #[cfg(time_driver_tim2)] + #[cfg(feature = "rt")] #[interrupt] fn $irq() { DRIVER.on_interrupt() @@ -46,6 +48,7 @@ foreach_interrupt! { }; (TIM3, timer, $block:ident, UP, $irq:ident) => { #[cfg(time_driver_tim3)] + #[cfg(feature = "rt")] #[interrupt] fn $irq() { DRIVER.on_interrupt() @@ -53,6 +56,7 @@ foreach_interrupt! { }; (TIM4, timer, $block:ident, UP, $irq:ident) => { #[cfg(time_driver_tim4)] + #[cfg(feature = "rt")] #[interrupt] fn $irq() { DRIVER.on_interrupt() @@ -60,6 +64,7 @@ foreach_interrupt! { }; (TIM5, timer, $block:ident, UP, $irq:ident) => { #[cfg(time_driver_tim5)] + #[cfg(feature = "rt")] #[interrupt] fn $irq() { DRIVER.on_interrupt() @@ -67,6 +72,7 @@ foreach_interrupt! { }; (TIM12, timer, $block:ident, UP, $irq:ident) => { #[cfg(time_driver_tim12)] + #[cfg(feature = "rt")] #[interrupt] fn $irq() { DRIVER.on_interrupt() @@ -74,6 +80,7 @@ foreach_interrupt! { }; (TIM15, timer, $block:ident, UP, $irq:ident) => { #[cfg(time_driver_tim15)] + #[cfg(feature = "rt")] #[interrupt] fn $irq() { DRIVER.on_interrupt() @@ -148,12 +155,11 @@ impl RtcDriver { let timer_freq = T::frequency(); - // NOTE(unsafe) Critical section to use the unsafe methods - critical_section::with(|_| unsafe { + critical_section::with(|_| { r.cr1().modify(|w| w.set_cen(false)); r.cnt().write(|w| w.set_cnt(0)); - let psc = timer_freq.0 / TICKS_PER_SECOND as u32 - 1; + let psc = timer_freq.0 / TICK_HZ as u32 - 1; let psc: u16 = match psc.try_into() { Err(_) => panic!("psc division overflow: {}", psc), Ok(n) => n, @@ -176,9 +182,8 @@ impl RtcDriver { w.set_ccie(0, true); }); - let irq: ::Interrupt = core::mem::transmute(()); - irq.unpend(); - irq.enable(); + ::Interrupt::unpend(); + unsafe { ::Interrupt::enable() }; r.cr1().modify(|w| w.set_cen(true)); }) @@ -187,9 +192,8 @@ impl RtcDriver { fn on_interrupt(&self) { let r = T::regs_gp16(); - // NOTE(unsafe) Use critical section to access the methods // XXX: reduce the size of this critical section ? - critical_section::with(|cs| unsafe { + critical_section::with(|cs| { let sr = r.sr().read(); let dier = r.dier().read(); @@ -222,7 +226,7 @@ impl RtcDriver { let period = self.period.fetch_add(1, Ordering::Relaxed) + 1; let t = (period as u64) << 15; - critical_section::with(move |cs| unsafe { + critical_section::with(move |cs| { r.dier().modify(move |w| { for n in 0..ALARM_COUNT { let alarm = &self.alarms.borrow(cs)[n]; @@ -250,7 +254,7 @@ impl RtcDriver { // Call after clearing alarm, so the callback can set another alarm. // safety: - // - we can ignore the possiblity of `f` being unset (null) because of the safety contract of `allocate_alarm`. + // - we can ignore the possibility of `f` being unset (null) because of the safety contract of `allocate_alarm`. // - other than that we only store valid function pointers into alarm.callback let f: fn(*mut ()) = unsafe { mem::transmute(alarm.callback.get()) }; f(alarm.ctx.get()); @@ -263,8 +267,7 @@ impl Driver for RtcDriver { let period = self.period.load(Ordering::Relaxed); compiler_fence(Ordering::Acquire); - // NOTE(unsafe) Atomic read with no side-effects - let counter = unsafe { r.cnt().read().cnt() }; + let counter = r.cnt().read().cnt(); calc_now(period, counter) } @@ -292,31 +295,36 @@ impl Driver for RtcDriver { }) } - fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) { + fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool { critical_section::with(|cs| { let r = T::regs_gp16(); - let n = alarm.id() as _; + let n = alarm.id() as usize; let alarm = self.get_alarm(cs, alarm); alarm.timestamp.set(timestamp); let t = self.now(); if timestamp <= t { - unsafe { r.dier().modify(|w| w.set_ccie(n + 1, false)) }; - self.trigger_alarm(n, cs); - return; + // If alarm timestamp has passed the alarm will not fire. + // Disarm the alarm and return `false` to indicate that. + r.dier().modify(|w| w.set_ccie(n + 1, false)); + + alarm.timestamp.set(u64::MAX); + + return false; } let safe_timestamp = timestamp.max(t + 3); // Write the CCR value regardless of whether we're going to enable it now or not. // This way, when we enable it later, the right value is already set. - unsafe { r.ccr(n + 1).write(|w| w.set_ccr(safe_timestamp as u16)) }; + r.ccr(n + 1).write(|w| w.set_ccr(safe_timestamp as u16)); // Enable it if it'll happen soon. Otherwise, `next_period` will enable it. let diff = timestamp - t; - // NOTE(unsafe) We're in a critical section - unsafe { r.dier().modify(|w| w.set_ccie(n + 1, diff < 0xc000)) }; + r.dier().modify(|w| w.set_ccie(n + 1, diff < 0xc000)); + + true }) } } diff --git a/embassy-stm32/src/timer/mod.rs b/embassy-stm32/src/timer/mod.rs index 772c67686..09b7a3776 100644 --- a/embassy-stm32/src/timer/mod.rs +++ b/embassy-stm32/src/timer/mod.rs @@ -1,6 +1,6 @@ use stm32_metapac::timer::vals; -use crate::interrupt::Interrupt; +use crate::interrupt; use crate::rcc::sealed::RccPeripheral as __RccPeri; use crate::rcc::RccPeripheral; use crate::time::Hertz; @@ -13,7 +13,7 @@ pub mod low_level { pub(crate) mod sealed { use super::*; pub trait Basic16bitInstance: RccPeripheral { - type Interrupt: Interrupt; + type Interrupt: interrupt::typelevel::Interrupt; fn regs() -> crate::pac::timer::TimBasic; @@ -57,28 +57,22 @@ pub trait Basic16bitInstance: sealed::Basic16bitInstance + 'static {} macro_rules! impl_basic_16bit_timer { ($inst:ident, $irq:ident) => { impl sealed::Basic16bitInstance for crate::peripherals::$inst { - type Interrupt = crate::interrupt::$irq; + type Interrupt = crate::interrupt::typelevel::$irq; fn regs() -> crate::pac::timer::TimBasic { - crate::pac::timer::TimBasic(crate::pac::$inst.0) + unsafe { crate::pac::timer::TimBasic::from_ptr(crate::pac::$inst.as_ptr()) } } fn start(&mut self) { - unsafe { - Self::regs().cr1().modify(|r| r.set_cen(true)); - } + Self::regs().cr1().modify(|r| r.set_cen(true)); } fn stop(&mut self) { - unsafe { - Self::regs().cr1().modify(|r| r.set_cen(false)); - } + Self::regs().cr1().modify(|r| r.set_cen(false)); } fn reset(&mut self) { - unsafe { - Self::regs().cnt().write(|r| r.set_cnt(0)); - } + Self::regs().cnt().write(|r| r.set_cnt(0)); } fn set_frequency(&mut self, frequency: Hertz) { @@ -90,35 +84,29 @@ macro_rules! impl_basic_16bit_timer { let arr: u16 = unwrap!((pclk_ticks_per_timer_period / (u32::from(psc) + 1)).try_into()); let regs = Self::regs(); - unsafe { - regs.psc().write(|r| r.set_psc(psc)); - regs.arr().write(|r| r.set_arr(arr)); + regs.psc().write(|r| r.set_psc(psc)); + regs.arr().write(|r| r.set_arr(arr)); - regs.cr1().modify(|r| r.set_urs(vals::Urs::COUNTERONLY)); - regs.egr().write(|r| r.set_ug(true)); - regs.cr1().modify(|r| r.set_urs(vals::Urs::ANYEVENT)); - } + regs.cr1().modify(|r| r.set_urs(vals::Urs::COUNTERONLY)); + regs.egr().write(|r| r.set_ug(true)); + regs.cr1().modify(|r| r.set_urs(vals::Urs::ANYEVENT)); } fn clear_update_interrupt(&mut self) -> bool { let regs = Self::regs(); - unsafe { - let sr = regs.sr().read(); - if sr.uif() { - regs.sr().modify(|r| { - r.set_uif(false); - }); - true - } else { - false - } + let sr = regs.sr().read(); + if sr.uif() { + regs.sr().modify(|r| { + r.set_uif(false); + }); + true + } else { + false } } fn enable_update_interrupt(&mut self, enable: bool) { - unsafe { - Self::regs().dier().write(|r| r.set_uie(enable)); - } + Self::regs().dier().write(|r| r.set_uie(enable)); } } }; @@ -141,14 +129,12 @@ macro_rules! impl_32bit_timer { let arr: u32 = unwrap!(((pclk_ticks_per_timer_period / (psc as u64 + 1)).try_into())); let regs = Self::regs_gp32(); - unsafe { - regs.psc().write(|r| r.set_psc(psc)); - regs.arr().write(|r| r.set_arr(arr)); + regs.psc().write(|r| r.set_psc(psc)); + regs.arr().write(|r| r.set_arr(arr)); - regs.cr1().modify(|r| r.set_urs(vals::Urs::COUNTERONLY)); - regs.egr().write(|r| r.set_ug(true)); - regs.cr1().modify(|r| r.set_urs(vals::Urs::ANYEVENT)); - } + regs.cr1().modify(|r| r.set_urs(vals::Urs::COUNTERONLY)); + regs.egr().write(|r| r.set_ug(true)); + regs.cr1().modify(|r| r.set_urs(vals::Urs::ANYEVENT)); } } }; @@ -185,7 +171,7 @@ foreach_interrupt! { impl sealed::GeneralPurpose16bitInstance for crate::peripherals::$inst { fn regs_gp16() -> crate::pac::timer::TimGp16 { - crate::pac::timer::TimGp16(crate::pac::$inst.0) + unsafe { crate::pac::timer::TimGp16::from_ptr(crate::pac::$inst.as_ptr()) } } } @@ -206,7 +192,7 @@ foreach_interrupt! { impl sealed::GeneralPurpose16bitInstance for crate::peripherals::$inst { fn regs_gp16() -> crate::pac::timer::TimGp16 { - crate::pac::timer::TimGp16(crate::pac::$inst.0) + unsafe { crate::pac::timer::TimGp16::from_ptr(crate::pac::$inst.as_ptr()) } } } diff --git a/embassy-stm32/src/traits.rs b/embassy-stm32/src/traits.rs index 45cc4e725..ffce7bd42 100644 --- a/embassy-stm32/src/traits.rs +++ b/embassy-stm32/src/traits.rs @@ -34,7 +34,7 @@ macro_rules! dma_trait_impl { (crate::$mod:ident::$trait:ident, $instance:ident, {dmamux: $dmamux:ident}, $request:expr) => { impl crate::$mod::$trait for T where - T: crate::dma::MuxChannel, + T: crate::dma::Channel + crate::dma::MuxChannel, { fn request(&self) -> crate::dma::Request { $request diff --git a/embassy-stm32/src/usart/buffered.rs b/embassy-stm32/src/usart/buffered.rs index a7fa43894..433ad299c 100644 --- a/embassy-stm32/src/usart/buffered.rs +++ b/embassy-stm32/src/usart/buffered.rs @@ -1,148 +1,402 @@ -use core::future::Future; +use core::future::poll_fn; +use core::slice; use core::task::Poll; -use atomic_polyfill::{compiler_fence, Ordering}; -use embassy_cortex_m::peripheral::{PeripheralMutex, PeripheralState, StateStorage}; -use embassy_hal_common::ring_buffer::RingBuffer; -use embassy_sync::waitqueue::WakerRegistration; -use futures::future::poll_fn; +use embassy_hal_common::atomic_ring_buffer::RingBuffer; +use embassy_sync::waitqueue::AtomicWaker; use super::*; +use crate::interrupt::typelevel::Interrupt; -pub struct State<'d, T: BasicInstance>(StateStorage>); -impl<'d, T: BasicInstance> State<'d, T> { - pub fn new() -> Self { - Self(StateStorage::new()) +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let r = T::regs(); + let state = T::buffered_state(); + + // RX + let sr_val = sr(r).read(); + // On v1 & v2, reading DR clears the rxne, error and idle interrupt + // flags. Keep this close to the SR read to reduce the chance of a + // flag being set in-between. + let dr = if sr_val.rxne() || cfg!(any(usart_v1, usart_v2)) && (sr_val.ore() || sr_val.idle()) { + Some(rdr(r).read_volatile()) + } else { + None + }; + clear_interrupt_flags(r, sr_val); + + if sr_val.pe() { + warn!("Parity error"); + } + if sr_val.fe() { + warn!("Framing error"); + } + if sr_val.ne() { + warn!("Noise error"); + } + if sr_val.ore() { + warn!("Overrun error"); + } + if sr_val.rxne() { + let mut rx_writer = state.rx_buf.writer(); + let buf = rx_writer.push_slice(); + if !buf.is_empty() { + buf[0] = dr.unwrap(); + rx_writer.push_done(1); + } else { + // FIXME: Should we disable any further RX interrupts when the buffer becomes full. + } + + if state.rx_buf.is_full() { + state.rx_waker.wake(); + } + } + + if sr_val.idle() { + state.rx_waker.wake(); + } + + // TX + if sr(r).read().txe() { + let mut tx_reader = state.tx_buf.reader(); + let buf = tx_reader.pop_slice(); + if !buf.is_empty() { + r.cr1().modify(|w| { + w.set_txeie(true); + }); + tdr(r).write_volatile(buf[0].into()); + tx_reader.pop_done(1); + state.tx_waker.wake(); + } else { + // Disable interrupt until we have something to transmit again + r.cr1().modify(|w| { + w.set_txeie(false); + }); + } + } } } -struct StateInner<'d, T: BasicInstance> { - phantom: PhantomData<&'d mut T>, +pub struct State { + rx_waker: AtomicWaker, + rx_buf: RingBuffer, - rx_waker: WakerRegistration, - rx: RingBuffer<'d>, - - tx_waker: WakerRegistration, - tx: RingBuffer<'d>, + tx_waker: AtomicWaker, + tx_buf: RingBuffer, } -unsafe impl<'d, T: BasicInstance> Send for StateInner<'d, T> {} -unsafe impl<'d, T: BasicInstance> Sync for StateInner<'d, T> {} +impl State { + pub const fn new() -> Self { + Self { + rx_buf: RingBuffer::new(), + tx_buf: RingBuffer::new(), + rx_waker: AtomicWaker::new(), + tx_waker: AtomicWaker::new(), + } + } +} pub struct BufferedUart<'d, T: BasicInstance> { - inner: PeripheralMutex<'d, StateInner<'d, T>>, + rx: BufferedUartRx<'d, T>, + tx: BufferedUartTx<'d, T>, } -impl<'d, T: BasicInstance> Unpin for BufferedUart<'d, T> {} +pub struct BufferedUartTx<'d, T: BasicInstance> { + phantom: PhantomData<&'d mut T>, +} + +pub struct BufferedUartRx<'d, T: BasicInstance> { + phantom: PhantomData<&'d mut T>, +} impl<'d, T: BasicInstance> BufferedUart<'d, T> { pub fn new( - state: &'d mut State<'d, T>, - _uart: Uart<'d, T, NoDma, NoDma>, - irq: impl Peripheral

+ 'd, + peri: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + rx: impl Peripheral

> + 'd, + tx: impl Peripheral

> + 'd, tx_buffer: &'d mut [u8], rx_buffer: &'d mut [u8], + config: Config, ) -> BufferedUart<'d, T> { - into_ref!(irq); + T::enable(); + T::reset(); + + Self::new_inner(peri, rx, tx, tx_buffer, rx_buffer, config) + } + + pub fn new_with_rtscts( + peri: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + rx: impl Peripheral

> + 'd, + tx: impl Peripheral

> + 'd, + rts: impl Peripheral

> + 'd, + cts: impl Peripheral

> + 'd, + tx_buffer: &'d mut [u8], + rx_buffer: &'d mut [u8], + config: Config, + ) -> BufferedUart<'d, T> { + into_ref!(cts, rts); + + T::enable(); + T::reset(); + + rts.set_as_af(rts.af_num(), AFType::OutputPushPull); + cts.set_as_af(cts.af_num(), AFType::Input); + T::regs().cr3().write(|w| { + w.set_rtse(true); + w.set_ctse(true); + }); + + Self::new_inner(peri, rx, tx, tx_buffer, rx_buffer, config) + } + + #[cfg(not(any(usart_v1, usart_v2)))] + pub fn new_with_de( + peri: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + rx: impl Peripheral

> + 'd, + tx: impl Peripheral

> + 'd, + de: impl Peripheral

> + 'd, + tx_buffer: &'d mut [u8], + rx_buffer: &'d mut [u8], + config: Config, + ) -> BufferedUart<'d, T> { + into_ref!(de); + + T::enable(); + T::reset(); + + de.set_as_af(de.af_num(), AFType::OutputPushPull); + T::regs().cr3().write(|w| { + w.set_dem(true); + }); + + Self::new_inner(peri, rx, tx, tx_buffer, rx_buffer, config) + } + + fn new_inner( + _peri: impl Peripheral

+ 'd, + rx: impl Peripheral

> + 'd, + tx: impl Peripheral

> + 'd, + tx_buffer: &'d mut [u8], + rx_buffer: &'d mut [u8], + config: Config, + ) -> BufferedUart<'d, T> { + into_ref!(_peri, rx, tx); + + let state = T::buffered_state(); + let len = tx_buffer.len(); + unsafe { state.tx_buf.init(tx_buffer.as_mut_ptr(), len) }; + let len = rx_buffer.len(); + unsafe { state.rx_buf.init(rx_buffer.as_mut_ptr(), len) }; let r = T::regs(); - unsafe { - r.cr1().modify(|w| { - w.set_rxneie(true); - w.set_idleie(true); - }); - } + rx.set_as_af(rx.af_num(), AFType::Input); + tx.set_as_af(tx.af_num(), AFType::OutputPushPull); + + configure(r, &config, T::frequency(), T::KIND, true, true); + + r.cr1().modify(|w| { + #[cfg(lpuart_v2)] + w.set_fifoen(true); + + w.set_rxneie(true); + w.set_idleie(true); + }); + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; Self { - inner: PeripheralMutex::new(irq, &mut state.0, move || StateInner { - phantom: PhantomData, - tx: RingBuffer::new(tx_buffer), - tx_waker: WakerRegistration::new(), + rx: BufferedUartRx { phantom: PhantomData }, + tx: BufferedUartTx { phantom: PhantomData }, + } + } - rx: RingBuffer::new(rx_buffer), - rx_waker: WakerRegistration::new(), - }), + pub fn split(self) -> (BufferedUartTx<'d, T>, BufferedUartRx<'d, T>) { + (self.tx, self.rx) + } +} + +impl<'d, T: BasicInstance> BufferedUartRx<'d, T> { + async fn read(&self, buf: &mut [u8]) -> Result { + poll_fn(move |cx| { + let state = T::buffered_state(); + let mut rx_reader = unsafe { state.rx_buf.reader() }; + let data = rx_reader.pop_slice(); + + if !data.is_empty() { + let len = data.len().min(buf.len()); + buf[..len].copy_from_slice(&data[..len]); + + let do_pend = state.rx_buf.is_full(); + rx_reader.pop_done(len); + + if do_pend { + T::Interrupt::pend(); + } + + return Poll::Ready(Ok(len)); + } + + state.rx_waker.register(cx.waker()); + Poll::Pending + }) + .await + } + + fn blocking_read(&self, buf: &mut [u8]) -> Result { + loop { + let state = T::buffered_state(); + let mut rx_reader = unsafe { state.rx_buf.reader() }; + let data = rx_reader.pop_slice(); + + if !data.is_empty() { + let len = data.len().min(buf.len()); + buf[..len].copy_from_slice(&data[..len]); + + let do_pend = state.rx_buf.is_full(); + rx_reader.pop_done(len); + + if do_pend { + T::Interrupt::pend(); + } + + return Ok(len); + } + } + } + + async fn fill_buf(&self) -> Result<&[u8], Error> { + poll_fn(move |cx| { + let state = T::buffered_state(); + let mut rx_reader = unsafe { state.rx_buf.reader() }; + let (p, n) = rx_reader.pop_buf(); + if n == 0 { + state.rx_waker.register(cx.waker()); + return Poll::Pending; + } + + let buf = unsafe { slice::from_raw_parts(p, n) }; + Poll::Ready(Ok(buf)) + }) + .await + } + + fn consume(&self, amt: usize) { + let state = T::buffered_state(); + let mut rx_reader = unsafe { state.rx_buf.reader() }; + let full = state.rx_buf.is_full(); + rx_reader.pop_done(amt); + if full { + T::Interrupt::pend(); } } } -impl<'d, T: BasicInstance> StateInner<'d, T> -where - Self: 'd, -{ - fn on_rx(&mut self) { - let r = T::regs(); - unsafe { - let sr = sr(r).read(); - clear_interrupt_flags(r, sr); +impl<'d, T: BasicInstance> BufferedUartTx<'d, T> { + async fn write(&self, buf: &[u8]) -> Result { + poll_fn(move |cx| { + let state = T::buffered_state(); + let empty = state.tx_buf.is_empty(); - // This read also clears the error and idle interrupt flags on v1. - let b = rdr(r).read_volatile(); - - if sr.rxne() { - if sr.pe() { - warn!("Parity error"); - } - if sr.fe() { - warn!("Framing error"); - } - if sr.ne() { - warn!("Noise error"); - } - if sr.ore() { - warn!("Overrun error"); - } - - let buf = self.rx.push_buf(); - if !buf.is_empty() { - buf[0] = b; - self.rx.push(1); - } else { - warn!("RX buffer full, discard received byte"); - } - - if self.rx.is_full() { - self.rx_waker.wake(); - } + let mut tx_writer = unsafe { state.tx_buf.writer() }; + let data = tx_writer.push_slice(); + if data.is_empty() { + state.tx_waker.register(cx.waker()); + return Poll::Pending; } - if sr.idle() { - self.rx_waker.wake(); - }; + let n = data.len().min(buf.len()); + data[..n].copy_from_slice(&buf[..n]); + tx_writer.push_done(n); + + if empty { + T::Interrupt::pend(); + } + + Poll::Ready(Ok(n)) + }) + .await + } + + async fn flush(&self) -> Result<(), Error> { + poll_fn(move |cx| { + let state = T::buffered_state(); + if !state.tx_buf.is_empty() { + state.tx_waker.register(cx.waker()); + return Poll::Pending; + } + + Poll::Ready(Ok(())) + }) + .await + } + + fn blocking_write(&self, buf: &[u8]) -> Result { + loop { + let state = T::buffered_state(); + let empty = state.tx_buf.is_empty(); + + let mut tx_writer = unsafe { state.tx_buf.writer() }; + let data = tx_writer.push_slice(); + if !data.is_empty() { + let n = data.len().min(buf.len()); + data[..n].copy_from_slice(&buf[..n]); + tx_writer.push_done(n); + + if empty { + T::Interrupt::pend(); + } + + return Ok(n); + } } } - fn on_tx(&mut self) { - let r = T::regs(); - unsafe { - if sr(r).read().txe() { - let buf = self.tx.pop_buf(); - if !buf.is_empty() { - r.cr1().modify(|w| { - w.set_txeie(true); - }); - tdr(r).write_volatile(buf[0].into()); - self.tx.pop(1); - self.tx_waker.wake(); - } else { - // Disable interrupt until we have something to transmit again - r.cr1().modify(|w| { - w.set_txeie(false); - }); - } + fn blocking_flush(&self) -> Result<(), Error> { + loop { + let state = T::buffered_state(); + if state.tx_buf.is_empty() { + return Ok(()); } } } } -impl<'d, T: BasicInstance> PeripheralState for StateInner<'d, T> -where - Self: 'd, -{ - type Interrupt = T::Interrupt; - fn on_interrupt(&mut self) { - self.on_rx(); - self.on_tx(); +impl<'d, T: BasicInstance> Drop for BufferedUartRx<'d, T> { + fn drop(&mut self) { + let state = T::buffered_state(); + unsafe { + state.rx_buf.deinit(); + + // TX is inactive if the the buffer is not available. + // We can now unregister the interrupt handler + if state.tx_buf.len() == 0 { + T::Interrupt::disable(); + } + } + } +} + +impl<'d, T: BasicInstance> Drop for BufferedUartTx<'d, T> { + fn drop(&mut self) { + let state = T::buffered_state(); + unsafe { + state.tx_buf.deinit(); + + // RX is inactive if the the buffer is not available. + // We can now unregister the interrupt handler + if state.rx_buf.len() == 0 { + T::Interrupt::disable(); + } + } } } @@ -156,123 +410,284 @@ impl<'d, T: BasicInstance> embedded_io::Io for BufferedUart<'d, T> { type Error = Error; } +impl<'d, T: BasicInstance> embedded_io::Io for BufferedUartRx<'d, T> { + type Error = Error; +} + +impl<'d, T: BasicInstance> embedded_io::Io for BufferedUartTx<'d, T> { + type Error = Error; +} + impl<'d, T: BasicInstance> embedded_io::asynch::Read for BufferedUart<'d, T> { - type ReadFuture<'a> = impl Future> - where - Self: 'a; + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.rx.read(buf).await + } +} - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - poll_fn(move |cx| { - let mut do_pend = false; - let res = self.inner.with(|state| { - compiler_fence(Ordering::SeqCst); - - // We have data ready in buffer? Return it. - let data = state.rx.pop_buf(); - if !data.is_empty() { - let len = data.len().min(buf.len()); - buf[..len].copy_from_slice(&data[..len]); - - if state.rx.is_full() { - do_pend = true; - } - state.rx.pop(len); - - return Poll::Ready(Ok(len)); - } - - state.rx_waker.register(cx.waker()); - Poll::Pending - }); - - if do_pend { - self.inner.pend(); - } - - res - }) +impl<'d, T: BasicInstance> embedded_io::asynch::Read for BufferedUartRx<'d, T> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + Self::read(self, buf).await } } impl<'d, T: BasicInstance> embedded_io::asynch::BufRead for BufferedUart<'d, T> { - type FillBufFuture<'a> = impl Future> - where - Self: 'a; - - fn fill_buf<'a>(&'a mut self) -> Self::FillBufFuture<'a> { - poll_fn(move |cx| { - self.inner.with(|state| { - compiler_fence(Ordering::SeqCst); - - // We have data ready in buffer? Return it. - let buf = state.rx.pop_buf(); - if !buf.is_empty() { - let buf: &[u8] = buf; - // Safety: buffer lives as long as uart - let buf: &[u8] = unsafe { core::mem::transmute(buf) }; - return Poll::Ready(Ok(buf)); - } - - state.rx_waker.register(cx.waker()); - Poll::>::Pending - }) - }) + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + self.rx.fill_buf().await } fn consume(&mut self, amt: usize) { - let signal = self.inner.with(|state| { - let full = state.rx.is_full(); - state.rx.pop(amt); - full - }); - if signal { - self.inner.pend(); - } + self.rx.consume(amt) + } +} + +impl<'d, T: BasicInstance> embedded_io::asynch::BufRead for BufferedUartRx<'d, T> { + async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { + Self::fill_buf(self).await + } + + fn consume(&mut self, amt: usize) { + Self::consume(self, amt) } } impl<'d, T: BasicInstance> embedded_io::asynch::Write for BufferedUart<'d, T> { - type WriteFuture<'a> = impl Future> - where - Self: 'a; - - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { - poll_fn(move |cx| { - let (poll, empty) = self.inner.with(|state| { - let empty = state.tx.is_empty(); - let tx_buf = state.tx.push_buf(); - if tx_buf.is_empty() { - state.tx_waker.register(cx.waker()); - return (Poll::Pending, empty); - } - - let n = core::cmp::min(tx_buf.len(), buf.len()); - tx_buf[..n].copy_from_slice(&buf[..n]); - state.tx.push(n); - - (Poll::Ready(Ok(n)), empty) - }); - if empty { - self.inner.pend(); - } - poll - }) + async fn write(&mut self, buf: &[u8]) -> Result { + self.tx.write(buf).await } - type FlushFuture<'a> = impl Future> - where - Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - poll_fn(move |cx| { - self.inner.with(|state| { - if !state.tx.is_empty() { - state.tx_waker.register(cx.waker()); - return Poll::Pending; - } - - Poll::Ready(Ok(())) - }) - }) + async fn flush(&mut self) -> Result<(), Self::Error> { + self.tx.flush().await + } +} + +impl<'d, T: BasicInstance> embedded_io::asynch::Write for BufferedUartTx<'d, T> { + async fn write(&mut self, buf: &[u8]) -> Result { + Self::write(self, buf).await + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + Self::flush(self).await + } +} + +impl<'d, T: BasicInstance> embedded_io::blocking::Read for BufferedUart<'d, T> { + fn read(&mut self, buf: &mut [u8]) -> Result { + self.rx.blocking_read(buf) + } +} + +impl<'d, T: BasicInstance> embedded_io::blocking::Read for BufferedUartRx<'d, T> { + fn read(&mut self, buf: &mut [u8]) -> Result { + self.blocking_read(buf) + } +} + +impl<'d, T: BasicInstance> embedded_io::blocking::Write for BufferedUart<'d, T> { + fn write(&mut self, buf: &[u8]) -> Result { + self.tx.blocking_write(buf) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.tx.blocking_flush() + } +} + +impl<'d, T: BasicInstance> embedded_io::blocking::Write for BufferedUartTx<'d, T> { + fn write(&mut self, buf: &[u8]) -> Result { + Self::blocking_write(self, buf) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + Self::blocking_flush(self) + } +} + +mod eh02 { + use super::*; + + impl<'d, T: BasicInstance> embedded_hal_02::serial::Read for BufferedUartRx<'d, T> { + type Error = Error; + + fn read(&mut self) -> Result> { + let r = T::regs(); + unsafe { + let sr = sr(r).read(); + if sr.pe() { + rdr(r).read_volatile(); + Err(nb::Error::Other(Error::Parity)) + } else if sr.fe() { + rdr(r).read_volatile(); + Err(nb::Error::Other(Error::Framing)) + } else if sr.ne() { + rdr(r).read_volatile(); + Err(nb::Error::Other(Error::Noise)) + } else if sr.ore() { + rdr(r).read_volatile(); + Err(nb::Error::Other(Error::Overrun)) + } else if sr.rxne() { + Ok(rdr(r).read_volatile()) + } else { + Err(nb::Error::WouldBlock) + } + } + } + } + + impl<'d, T: BasicInstance> embedded_hal_02::blocking::serial::Write for BufferedUartTx<'d, T> { + type Error = Error; + + fn bwrite_all(&mut self, mut buffer: &[u8]) -> Result<(), Self::Error> { + while !buffer.is_empty() { + match self.blocking_write(buffer) { + Ok(0) => panic!("zero-length write."), + Ok(n) => buffer = &buffer[n..], + Err(e) => return Err(e), + } + } + Ok(()) + } + + fn bflush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } + } + + impl<'d, T: BasicInstance> embedded_hal_02::serial::Read for BufferedUart<'d, T> { + type Error = Error; + + fn read(&mut self) -> Result> { + embedded_hal_02::serial::Read::read(&mut self.rx) + } + } + + impl<'d, T: BasicInstance> embedded_hal_02::blocking::serial::Write for BufferedUart<'d, T> { + type Error = Error; + + fn bwrite_all(&mut self, mut buffer: &[u8]) -> Result<(), Self::Error> { + while !buffer.is_empty() { + match self.tx.blocking_write(buffer) { + Ok(0) => panic!("zero-length write."), + Ok(n) => buffer = &buffer[n..], + Err(e) => return Err(e), + } + } + Ok(()) + } + + fn bflush(&mut self) -> Result<(), Self::Error> { + self.tx.blocking_flush() + } + } +} + +#[cfg(feature = "unstable-traits")] +mod eh1 { + use super::*; + + impl<'d, T: BasicInstance> embedded_hal_1::serial::ErrorType for BufferedUart<'d, T> { + type Error = Error; + } + + impl<'d, T: BasicInstance> embedded_hal_1::serial::ErrorType for BufferedUartTx<'d, T> { + type Error = Error; + } + + impl<'d, T: BasicInstance> embedded_hal_1::serial::ErrorType for BufferedUartRx<'d, T> { + type Error = Error; + } + + impl<'d, T: BasicInstance> embedded_hal_nb::serial::Read for BufferedUartRx<'d, T> { + fn read(&mut self) -> nb::Result { + embedded_hal_02::serial::Read::read(self) + } + } + + impl<'d, T: BasicInstance> embedded_hal_1::serial::Write for BufferedUartTx<'d, T> { + fn write(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(buffer).map(drop) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } + } + + impl<'d, T: BasicInstance> embedded_hal_nb::serial::Write for BufferedUartTx<'d, T> { + fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { + self.blocking_write(&[char]).map(drop).map_err(nb::Error::Other) + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + self.blocking_flush().map_err(nb::Error::Other) + } + } + + impl<'d, T: BasicInstance> embedded_hal_nb::serial::Read for BufferedUart<'d, T> { + fn read(&mut self) -> Result> { + embedded_hal_02::serial::Read::read(&mut self.rx) + } + } + + impl<'d, T: BasicInstance> embedded_hal_1::serial::Write for BufferedUart<'d, T> { + fn write(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { + self.tx.blocking_write(buffer).map(drop) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.tx.blocking_flush() + } + } + + impl<'d, T: BasicInstance> embedded_hal_nb::serial::Write for BufferedUart<'d, T> { + fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { + self.tx.blocking_write(&[char]).map(drop).map_err(nb::Error::Other) + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + self.tx.blocking_flush().map_err(nb::Error::Other) + } + } +} + +#[cfg(all( + feature = "unstable-traits", + feature = "nightly", + feature = "_todo_embedded_hal_serial" +))] +mod eha { + use core::future::Future; + + use super::*; + + impl<'d, T: BasicInstance> embedded_hal_async::serial::Write for BufferedUartTx<'d, T> { + async fn write(&mut self, buf: &[u8]) -> Result<(), Self::Error> { + Self::write(buf) + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + Self::flush() + } + } + + impl<'d, T: BasicInstance> embedded_hal_async::serial::Read for BufferedUartRx<'d, T> { + async fn read(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> { + Self::read(buf) + } + } + + impl<'d, T: BasicInstance> embedded_hal_async::serial::Write for BufferedUart<'d, T> { + async fn write(&mut self, buf: &[u8]) -> Result<(), Self::Error> { + self.tx.write(buf) + } + + async fn flush(&mut self) -> Result<(), Self::Error> { + self.tx.flush() + } + } + + impl<'d, T: BasicInstance> embedded_hal_async::serial::Read for BufferedUart<'d, T> { + async fn read(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> { + self.rx.read(buf) + } } } diff --git a/embassy-stm32/src/usart/mod.rs b/embassy-stm32/src/usart/mod.rs index 2ad85c675..c97efbf0a 100644 --- a/embassy-stm32/src/usart/mod.rs +++ b/embassy-stm32/src/usart/mod.rs @@ -1,16 +1,78 @@ #![macro_use] +use core::future::poll_fn; use core::marker::PhantomData; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; +use embassy_hal_common::drop::OnDrop; use embassy_hal_common::{into_ref, PeripheralRef}; +use futures::future::{select, Either}; -use crate::dma::NoDma; +use crate::dma::{NoDma, Transfer}; use crate::gpio::sealed::AFType; -#[cfg(any(lpuart_v1, lpuart_v2))] -use crate::pac::lpuart::{regs, vals, Lpuart as Regs}; -#[cfg(not(any(lpuart_v1, lpuart_v2)))] -use crate::pac::usart::{regs, vals, Usart as Regs}; -use crate::{peripherals, Peripheral}; +use crate::interrupt::typelevel::Interrupt; +#[cfg(not(any(usart_v1, usart_v2)))] +#[allow(unused_imports)] +use crate::pac::usart::regs::Isr as Sr; +#[cfg(any(usart_v1, usart_v2))] +#[allow(unused_imports)] +use crate::pac::usart::regs::Sr; +#[cfg(not(any(usart_v1, usart_v2)))] +use crate::pac::usart::Lpuart as Regs; +#[cfg(any(usart_v1, usart_v2))] +use crate::pac::usart::Usart as Regs; +use crate::pac::usart::{regs, vals}; +use crate::time::Hertz; +use crate::{interrupt, peripherals, Peripheral}; + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let r = T::regs(); + let s = T::state(); + + let (sr, cr1, cr3) = (sr(r).read(), r.cr1().read(), r.cr3().read()); + + let has_errors = (sr.pe() && cr1.peie()) || ((sr.fe() || sr.ne() || sr.ore()) && cr3.eie()); + if has_errors { + // clear all interrupts and DMA Rx Request + r.cr1().modify(|w| { + // disable RXNE interrupt + w.set_rxneie(false); + // disable parity interrupt + w.set_peie(false); + // disable idle line interrupt + w.set_idleie(false); + }); + r.cr3().modify(|w| { + // disable Error Interrupt: (Frame error, Noise error, Overrun error) + w.set_eie(false); + // disable DMA Rx Request + w.set_dmar(false); + }); + } else if cr1.idleie() && sr.idle() { + // IDLE detected: no more data will come + r.cr1().modify(|w| { + // disable idle line detection + w.set_idleie(false); + }); + } else if cr1.rxneie() { + // We cannot check the RXNE flag as it is auto-cleared by the DMA controller + + // It is up to the listener to determine if this in fact was a RX event and disable the RXNE detection + } else { + return; + } + + compiler_fence(Ordering::SeqCst); + s.rx_waker.wake(); + } +} #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum DataBits { @@ -44,6 +106,16 @@ pub struct Config { pub data_bits: DataBits, pub stop_bits: StopBits, pub parity: Parity, + /// if true, on read-like method, if there is a latent error pending, + /// read will abort, the error reported and cleared + /// if false, the error is ignored and cleared + pub detect_previous_overrun: bool, + + /// Set this to true if the line is considered noise free. + /// This will increase the receivers tolerance to clock deviations, + /// but will effectively disable noise detection. + #[cfg(not(usart_v1))] + pub assume_noise_free: bool, } impl Default for Config { @@ -53,6 +125,10 @@ impl Default for Config { data_bits: DataBits::DataBits8, stop_bits: StopBits::STOP1, parity: Parity::ParityNone, + // historical behavior + detect_previous_overrun: false, + #[cfg(not(usart_v1))] + assume_noise_free: false, } } } @@ -70,10 +146,18 @@ pub enum Error { Overrun, /// Parity check error Parity, + /// Buffer too large for DMA + BufferTooLong, +} + +enum ReadCompletionEvent { + // DMA Read transfer completed first + DmaCompleted, + // Idle line detected first + Idle(usize), } pub struct Uart<'d, T: BasicInstance, TxDma = NoDma, RxDma = NoDma> { - phantom: PhantomData<&'d mut T>, tx: UartTx<'d, T, TxDma>, rx: UartRx<'d, T, RxDma>, } @@ -84,12 +168,63 @@ pub struct UartTx<'d, T: BasicInstance, TxDma = NoDma> { } pub struct UartRx<'d, T: BasicInstance, RxDma = NoDma> { - phantom: PhantomData<&'d mut T>, + _peri: PeripheralRef<'d, T>, rx_dma: PeripheralRef<'d, RxDma>, + detect_previous_overrun: bool, + #[cfg(any(usart_v1, usart_v2))] + buffered_sr: stm32_metapac::usart::regs::Sr, } impl<'d, T: BasicInstance, TxDma> UartTx<'d, T, TxDma> { - fn new(tx_dma: PeripheralRef<'d, TxDma>) -> Self { + /// Useful if you only want Uart Tx. It saves 1 pin and consumes a little less power. + pub fn new( + peri: impl Peripheral

+ 'd, + tx: impl Peripheral

> + 'd, + tx_dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + T::enable(); + T::reset(); + + Self::new_inner(peri, tx, tx_dma, config) + } + + pub fn new_with_cts( + peri: impl Peripheral

+ 'd, + tx: impl Peripheral

> + 'd, + cts: impl Peripheral

> + 'd, + tx_dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(cts); + + T::enable(); + T::reset(); + + cts.set_as_af(cts.af_num(), AFType::Input); + T::regs().cr3().write(|w| { + w.set_ctse(true); + }); + Self::new_inner(peri, tx, tx_dma, config) + } + + fn new_inner( + _peri: impl Peripheral

+ 'd, + tx: impl Peripheral

> + 'd, + tx_dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(_peri, tx, tx_dma); + + let r = T::regs(); + + tx.set_as_af(tx.af_num(), AFType::OutputPushPull); + + configure(r, &config, T::frequency(), T::KIND, false, true); + + // create state once! + let _s = T::state(); + Self { tx_dma, phantom: PhantomData, @@ -102,142 +237,480 @@ impl<'d, T: BasicInstance, TxDma> UartTx<'d, T, TxDma> { { let ch = &mut self.tx_dma; let request = ch.request(); - unsafe { - T::regs().cr3().modify(|reg| { - reg.set_dmat(true); - }); - } + T::regs().cr3().modify(|reg| { + reg.set_dmat(true); + }); // If we don't assign future to a variable, the data register pointer // is held across an await and makes the future non-Send. - let transfer = crate::dma::write(ch, request, buffer, tdr(T::regs())); + let transfer = unsafe { Transfer::new_write(ch, request, buffer, tdr(T::regs()), Default::default()) }; transfer.await; Ok(()) } pub fn blocking_write(&mut self, buffer: &[u8]) -> Result<(), Error> { - unsafe { - let r = T::regs(); - for &b in buffer { - while !sr(r).read().txe() {} - tdr(r).write_volatile(b); - } + let r = T::regs(); + for &b in buffer { + while !sr(r).read().txe() {} + unsafe { tdr(r).write_volatile(b) }; } Ok(()) } pub fn blocking_flush(&mut self) -> Result<(), Error> { - unsafe { - let r = T::regs(); - while !sr(r).read().tc() {} - } + let r = T::regs(); + while !sr(r).read().tc() {} Ok(()) } } impl<'d, T: BasicInstance, RxDma> UartRx<'d, T, RxDma> { - fn new(rx_dma: PeripheralRef<'d, RxDma>) -> Self { + /// Useful if you only want Uart Rx. It saves 1 pin and consumes a little less power. + pub fn new( + peri: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + rx: impl Peripheral

> + 'd, + rx_dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + T::enable(); + T::reset(); + + Self::new_inner(peri, rx, rx_dma, config) + } + + pub fn new_with_rts( + peri: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + rx: impl Peripheral

> + 'd, + rts: impl Peripheral

> + 'd, + rx_dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(rts); + + T::enable(); + T::reset(); + + rts.set_as_af(rts.af_num(), AFType::OutputPushPull); + T::regs().cr3().write(|w| { + w.set_rtse(true); + }); + + Self::new_inner(peri, rx, rx_dma, config) + } + + fn new_inner( + peri: impl Peripheral

+ 'd, + rx: impl Peripheral

> + 'd, + rx_dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(peri, rx, rx_dma); + + let r = T::regs(); + + rx.set_as_af(rx.af_num(), AFType::Input); + + configure(r, &config, T::frequency(), T::KIND, true, false); + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + // create state once! + let _s = T::state(); + Self { + _peri: peri, rx_dma, - phantom: PhantomData, + detect_previous_overrun: config.detect_previous_overrun, + #[cfg(any(usart_v1, usart_v2))] + buffered_sr: stm32_metapac::usart::regs::Sr(0), } } + #[cfg(any(usart_v1, usart_v2))] + fn check_rx_flags(&mut self) -> Result { + let r = T::regs(); + loop { + // Handle all buffered error flags. + if self.buffered_sr.pe() { + self.buffered_sr.set_pe(false); + return Err(Error::Parity); + } else if self.buffered_sr.fe() { + self.buffered_sr.set_fe(false); + return Err(Error::Framing); + } else if self.buffered_sr.ne() { + self.buffered_sr.set_ne(false); + return Err(Error::Noise); + } else if self.buffered_sr.ore() { + self.buffered_sr.set_ore(false); + return Err(Error::Overrun); + } else if self.buffered_sr.rxne() { + self.buffered_sr.set_rxne(false); + return Ok(true); + } else { + // No error flags from previous iterations were set: Check the actual status register + let sr = r.sr().read(); + if !sr.rxne() { + return Ok(false); + } + + // Buffer the status register and let the loop handle the error flags. + self.buffered_sr = sr; + } + } + } + + #[cfg(any(usart_v3, usart_v4))] + fn check_rx_flags(&mut self) -> Result { + let r = T::regs(); + let sr = r.isr().read(); + if sr.pe() { + r.icr().write(|w| w.set_pe(true)); + return Err(Error::Parity); + } else if sr.fe() { + r.icr().write(|w| w.set_fe(true)); + return Err(Error::Framing); + } else if sr.ne() { + r.icr().write(|w| w.set_ne(true)); + return Err(Error::Noise); + } else if sr.ore() { + r.icr().write(|w| w.set_ore(true)); + return Err(Error::Overrun); + } + Ok(sr.rxne()) + } + pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> where RxDma: crate::usart::RxDma, { - let ch = &mut self.rx_dma; - let request = ch.request(); - unsafe { - T::regs().cr3().modify(|reg| { - reg.set_dmar(true); - }); - } - // If we don't assign future to a variable, the data register pointer - // is held across an await and makes the future non-Send. - let transfer = crate::dma::read(ch, request, rdr(T::regs()), buffer); - transfer.await; + self.inner_read(buffer, false).await?; + Ok(()) } + pub fn nb_read(&mut self) -> Result> { + let r = T::regs(); + if self.check_rx_flags()? { + Ok(unsafe { rdr(r).read_volatile() }) + } else { + Err(nb::Error::WouldBlock) + } + } + pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { - unsafe { - let r = T::regs(); - for b in buffer { - loop { - let sr = sr(r).read(); - if sr.pe() { - rdr(r).read_volatile(); - return Err(Error::Parity); - } else if sr.fe() { - rdr(r).read_volatile(); - return Err(Error::Framing); - } else if sr.ne() { - rdr(r).read_volatile(); - return Err(Error::Noise); - } else if sr.ore() { - rdr(r).read_volatile(); - return Err(Error::Overrun); - } else if sr.rxne() { - break; - } - } - *b = rdr(r).read_volatile(); - } + let r = T::regs(); + for b in buffer { + while !self.check_rx_flags()? {} + unsafe { *b = rdr(r).read_volatile() } } Ok(()) } + + pub async fn read_until_idle(&mut self, buffer: &mut [u8]) -> Result + where + RxDma: crate::usart::RxDma, + { + self.inner_read(buffer, true).await + } + + async fn inner_read_run( + &mut self, + buffer: &mut [u8], + enable_idle_line_detection: bool, + ) -> Result + where + RxDma: crate::usart::RxDma, + { + let r = T::regs(); + + // make sure USART state is restored to neutral state when this future is dropped + let on_drop = OnDrop::new(move || { + // defmt::trace!("Clear all USART interrupts and DMA Read Request"); + // clear all interrupts and DMA Rx Request + r.cr1().modify(|w| { + // disable RXNE interrupt + w.set_rxneie(false); + // disable parity interrupt + w.set_peie(false); + // disable idle line interrupt + w.set_idleie(false); + }); + r.cr3().modify(|w| { + // disable Error Interrupt: (Frame error, Noise error, Overrun error) + w.set_eie(false); + // disable DMA Rx Request + w.set_dmar(false); + }); + }); + + let ch = &mut self.rx_dma; + let request = ch.request(); + + let buffer_len = buffer.len(); + + // Start USART DMA + // will not do anything yet because DMAR is not yet set + // future which will complete when DMA Read request completes + let transfer = unsafe { Transfer::new_read(ch, request, rdr(T::regs()), buffer, Default::default()) }; + + // clear ORE flag just before enabling DMA Rx Request: can be mandatory for the second transfer + if !self.detect_previous_overrun { + let sr = sr(r).read(); + // This read also clears the error and idle interrupt flags on v1. + unsafe { rdr(r).read_volatile() }; + clear_interrupt_flags(r, sr); + } + + r.cr1().modify(|w| { + // disable RXNE interrupt + w.set_rxneie(false); + // enable parity interrupt if not ParityNone + w.set_peie(w.pce()); + }); + + r.cr3().modify(|w| { + // enable Error Interrupt: (Frame error, Noise error, Overrun error) + w.set_eie(true); + // enable DMA Rx Request + w.set_dmar(true); + }); + + compiler_fence(Ordering::SeqCst); + + // In case of errors already pending when reception started, interrupts may have already been raised + // and lead to reception abortion (Overrun error for instance). In such a case, all interrupts + // have been disabled in interrupt handler and DMA Rx Request has been disabled. + + let cr3 = r.cr3().read(); + + if !cr3.dmar() { + // something went wrong + // because the only way to get this flag cleared is to have an interrupt + + // DMA will be stopped when transfer is dropped + + let sr = sr(r).read(); + // This read also clears the error and idle interrupt flags on v1. + unsafe { rdr(r).read_volatile() }; + clear_interrupt_flags(r, sr); + + if sr.pe() { + return Err(Error::Parity); + } + if sr.fe() { + return Err(Error::Framing); + } + if sr.ne() { + return Err(Error::Noise); + } + if sr.ore() { + return Err(Error::Overrun); + } + + unreachable!(); + } + + if enable_idle_line_detection { + // clear idle flag + let sr = sr(r).read(); + // This read also clears the error and idle interrupt flags on v1. + unsafe { rdr(r).read_volatile() }; + clear_interrupt_flags(r, sr); + + // enable idle interrupt + r.cr1().modify(|w| { + w.set_idleie(true); + }); + } + + compiler_fence(Ordering::SeqCst); + + // future which completes when idle line or error is detected + let abort = poll_fn(move |cx| { + let s = T::state(); + + s.rx_waker.register(cx.waker()); + + let sr = sr(r).read(); + + // This read also clears the error and idle interrupt flags on v1. + unsafe { rdr(r).read_volatile() }; + clear_interrupt_flags(r, sr); + + compiler_fence(Ordering::SeqCst); + + let has_errors = sr.pe() || sr.fe() || sr.ne() || sr.ore(); + + if has_errors { + // all Rx interrupts and Rx DMA Request have already been cleared in interrupt handler + + if sr.pe() { + return Poll::Ready(Err(Error::Parity)); + } + if sr.fe() { + return Poll::Ready(Err(Error::Framing)); + } + if sr.ne() { + return Poll::Ready(Err(Error::Noise)); + } + if sr.ore() { + return Poll::Ready(Err(Error::Overrun)); + } + } + + if enable_idle_line_detection && sr.idle() { + // Idle line detected + return Poll::Ready(Ok(())); + } + + Poll::Pending + }); + + // wait for the first of DMA request or idle line detected to completes + // select consumes its arguments + // when transfer is dropped, it will stop the DMA request + let r = match select(transfer, abort).await { + // DMA transfer completed first + Either::Left(((), _)) => Ok(ReadCompletionEvent::DmaCompleted), + + // Idle line detected first + Either::Right((Ok(()), transfer)) => Ok(ReadCompletionEvent::Idle( + buffer_len - transfer.get_remaining_transfers() as usize, + )), + + // error occurred + Either::Right((Err(e), _)) => Err(e), + }; + + drop(on_drop); + + r + } + + async fn inner_read(&mut self, buffer: &mut [u8], enable_idle_line_detection: bool) -> Result + where + RxDma: crate::usart::RxDma, + { + if buffer.is_empty() { + return Ok(0); + } else if buffer.len() > 0xFFFF { + return Err(Error::BufferTooLong); + } + + let buffer_len = buffer.len(); + + // wait for DMA to complete or IDLE line detection if requested + let res = self.inner_read_run(buffer, enable_idle_line_detection).await; + + match res { + Ok(ReadCompletionEvent::DmaCompleted) => Ok(buffer_len), + Ok(ReadCompletionEvent::Idle(n)) => Ok(n), + Err(e) => Err(e), + } + } } impl<'d, T: BasicInstance, TxDma, RxDma> Uart<'d, T, TxDma, RxDma> { pub fn new( - _inner: impl Peripheral

+ 'd, + peri: impl Peripheral

+ 'd, + rx: impl Peripheral

> + 'd, + tx: impl Peripheral

> + 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + tx_dma: impl Peripheral

+ 'd, + rx_dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + T::enable(); + T::reset(); + + Self::new_inner(peri, rx, tx, tx_dma, rx_dma, config) + } + + pub fn new_with_rtscts( + peri: impl Peripheral

+ 'd, + rx: impl Peripheral

> + 'd, + tx: impl Peripheral

> + 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + rts: impl Peripheral

> + 'd, + cts: impl Peripheral

> + 'd, + tx_dma: impl Peripheral

+ 'd, + rx_dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(cts, rts); + + T::enable(); + T::reset(); + + rts.set_as_af(rts.af_num(), AFType::OutputPushPull); + cts.set_as_af(cts.af_num(), AFType::Input); + T::regs().cr3().write(|w| { + w.set_rtse(true); + w.set_ctse(true); + }); + Self::new_inner(peri, rx, tx, tx_dma, rx_dma, config) + } + + #[cfg(not(any(usart_v1, usart_v2)))] + pub fn new_with_de( + peri: impl Peripheral

+ 'd, + rx: impl Peripheral

> + 'd, + tx: impl Peripheral

> + 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + de: impl Peripheral

> + 'd, + tx_dma: impl Peripheral

+ 'd, + rx_dma: impl Peripheral

+ 'd, + config: Config, + ) -> Self { + into_ref!(de); + + T::enable(); + T::reset(); + + de.set_as_af(de.af_num(), AFType::OutputPushPull); + T::regs().cr3().write(|w| { + w.set_dem(true); + }); + Self::new_inner(peri, rx, tx, tx_dma, rx_dma, config) + } + + fn new_inner( + peri: impl Peripheral

+ 'd, rx: impl Peripheral

> + 'd, tx: impl Peripheral

> + 'd, tx_dma: impl Peripheral

+ 'd, rx_dma: impl Peripheral

+ 'd, config: Config, ) -> Self { - into_ref!(_inner, rx, tx, tx_dma, rx_dma); - - T::enable(); - T::reset(); - let pclk_freq = T::frequency(); - - // TODO: better calculation, including error checking and OVER8 if possible. - let div = (pclk_freq.0 + (config.baudrate / 2)) / config.baudrate * T::MULTIPLIER; + into_ref!(peri, rx, tx, tx_dma, rx_dma); let r = T::regs(); - unsafe { - rx.set_as_af(rx.af_num(), AFType::Input); - tx.set_as_af(tx.af_num(), AFType::OutputPushPull); + rx.set_as_af(rx.af_num(), AFType::Input); + tx.set_as_af(tx.af_num(), AFType::OutputPushPull); - r.cr2().write(|_w| {}); - r.cr3().write(|_w| {}); - r.brr().write_value(regs::Brr(div)); - r.cr1().write(|w| { - w.set_ue(true); - w.set_te(true); - w.set_re(true); - w.set_m0(if config.parity != Parity::ParityNone { - vals::M0::BIT9 - } else { - vals::M0::BIT8 - }); - w.set_pce(config.parity != Parity::ParityNone); - w.set_ps(match config.parity { - Parity::ParityOdd => vals::Ps::ODD, - Parity::ParityEven => vals::Ps::EVEN, - _ => vals::Ps::EVEN, - }); - }); - } + configure(r, &config, T::frequency(), T::KIND, true, true); + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + // create state once! + let _s = T::state(); Self { - tx: UartTx::new(tx_dma), - rx: UartRx::new(rx_dma), - phantom: PhantomData {}, + tx: UartTx { + tx_dma, + phantom: PhantomData, + }, + rx: UartRx { + _peri: peri, + rx_dma, + detect_previous_overrun: config.detect_previous_overrun, + #[cfg(any(usart_v1, usart_v2))] + buffered_sr: stm32_metapac::usart::regs::Sr(0), + }, } } @@ -263,45 +736,155 @@ impl<'d, T: BasicInstance, TxDma, RxDma> Uart<'d, T, TxDma, RxDma> { self.rx.read(buffer).await } + pub fn nb_read(&mut self) -> Result> { + self.rx.nb_read() + } + pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { self.rx.blocking_read(buffer) } + pub async fn read_until_idle(&mut self, buffer: &mut [u8]) -> Result + where + RxDma: crate::usart::RxDma, + { + self.rx.read_until_idle(buffer).await + } + /// Split the Uart into a transmitter and receiver, which is - /// particuarly useful when having two tasks correlating to + /// particularly useful when having two tasks correlating to /// transmitting and receiving. pub fn split(self) -> (UartTx<'d, T, TxDma>, UartRx<'d, T, RxDma>) { (self.tx, self.rx) } } +fn configure(r: Regs, config: &Config, pclk_freq: Hertz, kind: Kind, enable_rx: bool, enable_tx: bool) { + if !enable_rx && !enable_tx { + panic!("USART: At least one of RX or TX should be enabled"); + } + + #[cfg(not(usart_v4))] + static DIVS: [(u16, ()); 1] = [(1, ())]; + + #[cfg(usart_v4)] + static DIVS: [(u16, vals::Presc); 12] = [ + (1, vals::Presc::DIV1), + (2, vals::Presc::DIV2), + (4, vals::Presc::DIV4), + (6, vals::Presc::DIV6), + (8, vals::Presc::DIV8), + (10, vals::Presc::DIV10), + (12, vals::Presc::DIV12), + (16, vals::Presc::DIV16), + (32, vals::Presc::DIV32), + (64, vals::Presc::DIV64), + (128, vals::Presc::DIV128), + (256, vals::Presc::DIV256), + ]; + + let (mul, brr_min, brr_max) = match kind { + #[cfg(any(usart_v3, usart_v4))] + Kind::Lpuart => (256, 0x300, 0x10_0000), + Kind::Uart => (1, 0x10, 0x1_0000), + }; + + #[cfg(not(usart_v1))] + let mut over8 = false; + let mut found = None; + for &(presc, _presc_val) in &DIVS { + let denom = (config.baudrate * presc as u32) as u64; + let div = (pclk_freq.0 as u64 * mul + (denom / 2)) / denom; + trace!( + "USART: presc={}, div=0x{:08x} (mantissa = {}, fraction = {})", + presc, + div, + div >> 4, + div & 0x0F + ); + + if div < brr_min { + #[cfg(not(usart_v1))] + if div * 2 >= brr_min && kind == Kind::Uart && !cfg!(usart_v1) { + over8 = true; + let div = div as u32; + r.brr().write_value(regs::Brr(((div << 1) & !0xF) | (div & 0x07))); + #[cfg(usart_v4)] + r.presc().write(|w| w.set_prescaler(_presc_val)); + found = Some(div); + break; + } + panic!("USART: baudrate too high"); + } + + if div < brr_max { + let div = div as u32; + r.brr().write_value(regs::Brr(div)); + #[cfg(usart_v4)] + r.presc().write(|w| w.set_prescaler(_presc_val)); + found = Some(div); + break; + } + } + + let div = found.expect("USART: baudrate too low"); + + #[cfg(not(usart_v1))] + let oversampling = if over8 { "8 bit" } else { "16 bit" }; + #[cfg(usart_v1)] + let oversampling = "default"; + trace!( + "Using {} oversampling, desired baudrate: {}, actual baudrate: {}", + oversampling, + config.baudrate, + pclk_freq.0 / div + ); + + r.cr2().write(|w| { + w.set_stop(match config.stop_bits { + StopBits::STOP0P5 => vals::Stop::STOP0P5, + StopBits::STOP1 => vals::Stop::STOP1, + StopBits::STOP1P5 => vals::Stop::STOP1P5, + StopBits::STOP2 => vals::Stop::STOP2, + }); + }); + r.cr1().write(|w| { + // enable uart + w.set_ue(true); + // enable transceiver + w.set_te(enable_tx); + // enable receiver + w.set_re(enable_rx); + // configure word size + w.set_m0(if config.parity != Parity::ParityNone { + vals::M0::BIT9 + } else { + vals::M0::BIT8 + }); + // configure parity + w.set_pce(config.parity != Parity::ParityNone); + w.set_ps(match config.parity { + Parity::ParityOdd => vals::Ps::ODD, + Parity::ParityEven => vals::Ps::EVEN, + _ => vals::Ps::EVEN, + }); + #[cfg(not(usart_v1))] + w.set_over8(vals::Over8::from_bits(over8 as _)); + }); + + #[cfg(not(usart_v1))] + r.cr3().modify(|w| { + w.set_onebit(config.assume_noise_free); + }); +} + mod eh02 { use super::*; impl<'d, T: BasicInstance, RxDma> embedded_hal_02::serial::Read for UartRx<'d, T, RxDma> { type Error = Error; fn read(&mut self) -> Result> { - let r = T::regs(); - unsafe { - let sr = sr(r).read(); - if sr.pe() { - rdr(r).read_volatile(); - Err(nb::Error::Other(Error::Parity)) - } else if sr.fe() { - rdr(r).read_volatile(); - Err(nb::Error::Other(Error::Framing)) - } else if sr.ne() { - rdr(r).read_volatile(); - Err(nb::Error::Other(Error::Noise)) - } else if sr.ore() { - rdr(r).read_volatile(); - Err(nb::Error::Other(Error::Overrun)) - } else if sr.rxne() { - Ok(rdr(r).read_volatile()) - } else { - Err(nb::Error::WouldBlock) - } - } + self.nb_read() } } @@ -318,7 +901,7 @@ mod eh02 { impl<'d, T: BasicInstance, TxDma, RxDma> embedded_hal_02::serial::Read for Uart<'d, T, TxDma, RxDma> { type Error = Error; fn read(&mut self) -> Result> { - embedded_hal_02::serial::Read::read(&mut self.rx) + self.nb_read() } } @@ -344,6 +927,7 @@ mod eh1 { Self::Noise => embedded_hal_1::serial::ErrorKind::Noise, Self::Overrun => embedded_hal_1::serial::ErrorKind::Overrun, Self::Parity => embedded_hal_1::serial::ErrorKind::Parity, + Self::BufferTooLong => embedded_hal_1::serial::ErrorKind::Other, } } } @@ -359,125 +943,204 @@ mod eh1 { impl<'d, T: BasicInstance, RxDma> embedded_hal_1::serial::ErrorType for UartRx<'d, T, RxDma> { type Error = Error; } + + impl<'d, T: BasicInstance, RxDma> embedded_hal_nb::serial::Read for UartRx<'d, T, RxDma> { + fn read(&mut self) -> nb::Result { + self.nb_read() + } + } + + impl<'d, T: BasicInstance, TxDma> embedded_hal_1::serial::Write for UartTx<'d, T, TxDma> { + fn write(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(buffer) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } + } + + impl<'d, T: BasicInstance, TxDma> embedded_hal_nb::serial::Write for UartTx<'d, T, TxDma> { + fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { + self.blocking_write(&[char]).map_err(nb::Error::Other) + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + self.blocking_flush().map_err(nb::Error::Other) + } + } + + impl<'d, T: BasicInstance, TxDma, RxDma> embedded_hal_nb::serial::Read for Uart<'d, T, TxDma, RxDma> { + fn read(&mut self) -> Result> { + self.nb_read() + } + } + + impl<'d, T: BasicInstance, TxDma, RxDma> embedded_hal_1::serial::Write for Uart<'d, T, TxDma, RxDma> { + fn write(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { + self.blocking_write(buffer) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } + } + + impl<'d, T: BasicInstance, TxDma, RxDma> embedded_hal_nb::serial::Write for Uart<'d, T, TxDma, RxDma> { + fn write(&mut self, char: u8) -> nb::Result<(), Self::Error> { + self.blocking_write(&[char]).map_err(nb::Error::Other) + } + + fn flush(&mut self) -> nb::Result<(), Self::Error> { + self.blocking_flush().map_err(nb::Error::Other) + } + } } -cfg_if::cfg_if! { - if #[cfg(all(feature = "unstable-traits", feature = "nightly", feature = "_todo_embedded_hal_serial"))] { - use core::future::Future; +#[cfg(all(feature = "unstable-traits", feature = "nightly"))] +mod eio { + use embedded_io::asynch::Write; + use embedded_io::Io; - impl<'d, T: BasicInstance, TxDma> embedded_hal_async::serial::Write for UartTx<'d, T, TxDma> - where - TxDma: crate::usart::TxDma, - { - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; + use super::*; - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { - self.write(buf) - } + impl Io for Uart<'_, T, TxDma, RxDma> + where + T: BasicInstance, + { + type Error = Error; + } - type FlushFuture<'a> = impl Future> + 'a where Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - async move { Ok(()) } - } + impl Write for Uart<'_, T, TxDma, RxDma> + where + T: BasicInstance, + TxDma: super::TxDma, + { + async fn write(&mut self, buf: &[u8]) -> Result { + self.write(buf).await?; + Ok(buf.len()) } - impl<'d, T: BasicInstance, RxDma> embedded_hal_async::serial::Read for UartRx<'d, T, RxDma> - where - RxDma: crate::usart::RxDma, - { - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; + async fn flush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() + } + } - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - self.read(buf) - } + impl Io for UartTx<'_, T, TxDma> + where + T: BasicInstance, + { + type Error = Error; + } + + impl Write for UartTx<'_, T, TxDma> + where + T: BasicInstance, + TxDma: super::TxDma, + { + async fn write(&mut self, buf: &[u8]) -> Result { + self.write(buf).await?; + Ok(buf.len()) } - impl<'d, T: BasicInstance, TxDma, RxDma> embedded_hal_async::serial::Write for Uart<'d, T, TxDma, RxDma> - where - TxDma: crate::usart::TxDma, - { - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { - self.write(buf) - } - - type FlushFuture<'a> = impl Future> + 'a where Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - async move { Ok(()) } - } - } - - impl<'d, T: BasicInstance, TxDma, RxDma> embedded_hal_async::serial::Read for Uart<'d, T, TxDma, RxDma> - where - RxDma: crate::usart::RxDma, - { - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - self.read(buf) - } + async fn flush(&mut self) -> Result<(), Self::Error> { + self.blocking_flush() } } } #[cfg(feature = "nightly")] pub use buffered::*; + +#[cfg(feature = "nightly")] +pub use crate::usart::buffered::InterruptHandler as BufferedInterruptHandler; #[cfg(feature = "nightly")] mod buffered; -#[cfg(usart_v1)] +#[cfg(not(gpdma))] +mod ringbuffered; +#[cfg(not(gpdma))] +pub use ringbuffered::RingBufferedUartRx; + +use self::sealed::Kind; + +#[cfg(any(usart_v1, usart_v2))] fn tdr(r: crate::pac::usart::Usart) -> *mut u8 { - r.dr().ptr() as _ + r.dr().as_ptr() as _ } -#[cfg(usart_v1)] +#[cfg(any(usart_v1, usart_v2))] fn rdr(r: crate::pac::usart::Usart) -> *mut u8 { - r.dr().ptr() as _ + r.dr().as_ptr() as _ } -#[cfg(usart_v1)] +#[cfg(any(usart_v1, usart_v2))] fn sr(r: crate::pac::usart::Usart) -> crate::pac::common::Reg { r.sr() } -#[cfg(usart_v1)] +#[cfg(any(usart_v1, usart_v2))] #[allow(unused)] -unsafe fn clear_interrupt_flags(_r: Regs, _sr: regs::Sr) { +fn clear_interrupt_flags(_r: Regs, _sr: regs::Sr) { // On v1 the flags are cleared implicitly by reads and writes to DR. } -#[cfg(usart_v2)] +#[cfg(any(usart_v3, usart_v4))] fn tdr(r: Regs) -> *mut u8 { - r.tdr().ptr() as _ + r.tdr().as_ptr() as _ } -#[cfg(usart_v2)] +#[cfg(any(usart_v3, usart_v4))] fn rdr(r: Regs) -> *mut u8 { - r.rdr().ptr() as _ + r.rdr().as_ptr() as _ } -#[cfg(usart_v2)] +#[cfg(any(usart_v3, usart_v4))] fn sr(r: Regs) -> crate::pac::common::Reg { r.isr() } -#[cfg(usart_v2)] +#[cfg(any(usart_v3, usart_v4))] #[allow(unused)] -unsafe fn clear_interrupt_flags(r: Regs, sr: regs::Isr) { +fn clear_interrupt_flags(r: Regs, sr: regs::Isr) { r.icr().write(|w| *w = regs::Icr(sr.0)); } pub(crate) mod sealed { + use embassy_sync::waitqueue::AtomicWaker; + use super::*; + #[derive(Clone, Copy, PartialEq, Eq)] + pub enum Kind { + Uart, + #[cfg(any(usart_v3, usart_v4))] + Lpuart, + } + + pub struct State { + pub rx_waker: AtomicWaker, + pub tx_waker: AtomicWaker, + } + + impl State { + pub const fn new() -> Self { + Self { + rx_waker: AtomicWaker::new(), + tx_waker: AtomicWaker::new(), + } + } + } + pub trait BasicInstance: crate::rcc::RccPeripheral { - const MULTIPLIER: u32; - type Interrupt: crate::interrupt::Interrupt; + const KIND: Kind; + type Interrupt: interrupt::typelevel::Interrupt; fn regs() -> Regs; + fn state() -> &'static State; + + #[cfg(feature = "nightly")] + fn buffered_state() -> &'static buffered::State; } pub trait FullInstance: BasicInstance { @@ -485,7 +1148,7 @@ pub(crate) mod sealed { } } -pub trait BasicInstance: sealed::BasicInstance {} +pub trait BasicInstance: Peripheral

+ sealed::BasicInstance + 'static + Send {} pub trait FullInstance: sealed::FullInstance {} @@ -494,18 +1157,30 @@ pin_trait!(TxPin, BasicInstance); pin_trait!(CtsPin, BasicInstance); pin_trait!(RtsPin, BasicInstance); pin_trait!(CkPin, BasicInstance); +pin_trait!(DePin, BasicInstance); dma_trait!(TxDma, BasicInstance); dma_trait!(RxDma, BasicInstance); -macro_rules! impl_lpuart { - ($inst:ident, $irq:ident, $mul:expr) => { +macro_rules! impl_usart { + ($inst:ident, $irq:ident, $kind:expr) => { impl sealed::BasicInstance for crate::peripherals::$inst { - const MULTIPLIER: u32 = $mul; - type Interrupt = crate::interrupt::$irq; + const KIND: Kind = $kind; + type Interrupt = crate::interrupt::typelevel::$irq; fn regs() -> Regs { - Regs(crate::pac::$inst.0) + unsafe { Regs::from_ptr(crate::pac::$inst.as_ptr()) } + } + + fn state() -> &'static crate::usart::sealed::State { + static STATE: crate::usart::sealed::State = crate::usart::sealed::State::new(); + &STATE + } + + #[cfg(feature = "nightly")] + fn buffered_state() -> &'static buffered::State { + static STATE: buffered::State = buffered::State::new(); + &STATE } } @@ -514,21 +1189,19 @@ macro_rules! impl_lpuart { } foreach_interrupt!( - ($inst:ident, lpuart, $block:ident, $signal_name:ident, $irq:ident) => { - impl_lpuart!($inst, $irq, 255); + ($inst:ident, usart, LPUART, $signal_name:ident, $irq:ident) => { + impl_usart!($inst, $irq, Kind::Lpuart); }; ($inst:ident, usart, $block:ident, $signal_name:ident, $irq:ident) => { - impl_lpuart!($inst, $irq, 1); + impl_usart!($inst, $irq, Kind::Uart); impl sealed::FullInstance for peripherals::$inst { - fn regs_uart() -> crate::pac::usart::Usart { crate::pac::$inst } } - impl FullInstance for peripherals::$inst { - } + impl FullInstance for peripherals::$inst {} }; ); diff --git a/embassy-stm32/src/usart/ringbuffered.rs b/embassy-stm32/src/usart/ringbuffered.rs new file mode 100644 index 000000000..c74d7e092 --- /dev/null +++ b/embassy-stm32/src/usart/ringbuffered.rs @@ -0,0 +1,247 @@ +use core::future::poll_fn; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; + +use embassy_hal_common::PeripheralRef; +use futures::future::{select, Either}; + +use super::{clear_interrupt_flags, rdr, sr, BasicInstance, Error, UartRx}; +use crate::dma::RingBuffer; +use crate::usart::{Regs, Sr}; + +pub struct RingBufferedUartRx<'d, T: BasicInstance, RxDma: super::RxDma> { + _peri: PeripheralRef<'d, T>, + ring_buf: RingBuffer<'d, RxDma, u8>, +} + +impl<'d, T: BasicInstance, RxDma: super::RxDma> UartRx<'d, T, RxDma> { + /// Turn the `UartRx` into a buffered uart which can continously receive in the background + /// without the possibility of loosing bytes. The `dma_buf` is a buffer registered to the + /// DMA controller, and must be sufficiently large, such that it will not overflow. + pub fn into_ring_buffered(self, dma_buf: &'d mut [u8]) -> RingBufferedUartRx<'d, T, RxDma> { + assert!(dma_buf.len() > 0 && dma_buf.len() <= 0xFFFF); + + let request = self.rx_dma.request(); + let opts = Default::default(); + + let ring_buf = unsafe { RingBuffer::new_read(self.rx_dma, request, rdr(T::regs()), dma_buf, opts) }; + + RingBufferedUartRx { + _peri: self._peri, + ring_buf, + } + } +} + +impl<'d, T: BasicInstance, RxDma: super::RxDma> RingBufferedUartRx<'d, T, RxDma> { + pub fn start(&mut self) -> Result<(), Error> { + // Clear the ring buffer so that it is ready to receive data + self.ring_buf.clear(); + + self.setup_uart(); + + Ok(()) + } + + fn stop(&mut self, err: Error) -> Result { + self.teardown_uart(); + + Err(err) + } + + /// Start uart background receive + fn setup_uart(&mut self) { + // fence before starting DMA. + compiler_fence(Ordering::SeqCst); + + // start the dma controller + self.ring_buf.start(); + + let r = T::regs(); + // clear all interrupts and DMA Rx Request + r.cr1().modify(|w| { + // disable RXNE interrupt + w.set_rxneie(false); + // enable parity interrupt if not ParityNone + w.set_peie(w.pce()); + // enable idle line interrupt + w.set_idleie(true); + }); + r.cr3().modify(|w| { + // enable Error Interrupt: (Frame error, Noise error, Overrun error) + w.set_eie(true); + // enable DMA Rx Request + w.set_dmar(true); + }); + } + + /// Stop uart background receive + fn teardown_uart(&mut self) { + self.ring_buf.request_stop(); + + let r = T::regs(); + // clear all interrupts and DMA Rx Request + r.cr1().modify(|w| { + // disable RXNE interrupt + w.set_rxneie(false); + // disable parity interrupt + w.set_peie(false); + // disable idle line interrupt + w.set_idleie(false); + }); + r.cr3().modify(|w| { + // disable Error Interrupt: (Frame error, Noise error, Overrun error) + w.set_eie(false); + // disable DMA Rx Request + w.set_dmar(false); + }); + + compiler_fence(Ordering::SeqCst); + } + + /// Read bytes that are readily available in the ring buffer. + /// If no bytes are currently available in the buffer the call waits until the some + /// bytes are available (at least one byte and at most half the buffer size) + /// + /// Background receive is started if `start()` has not been previously called. + /// + /// Receive in the background is terminated if an error is returned. + /// It must then manually be started again by calling `start()` or by re-calling `read()`. + pub async fn read(&mut self, buf: &mut [u8]) -> Result { + let r = T::regs(); + + // Start background receive if it was not already started + match r.cr3().read().dmar() { + false => self.start()?, + _ => {} + }; + + check_for_errors(clear_idle_flag(T::regs()))?; + + loop { + match self.ring_buf.read(buf) { + Ok((0, _)) => {} + Ok((len, _)) => { + return Ok(len); + } + Err(_) => { + return self.stop(Error::Overrun); + } + } + + match self.wait_for_data_or_idle().await { + Ok(_) => {} + Err(err) => { + return self.stop(err); + } + } + } + } + + /// Wait for uart idle or dma half-full or full + async fn wait_for_data_or_idle(&mut self) -> Result<(), Error> { + compiler_fence(Ordering::SeqCst); + + let mut dma_init = false; + // Future which completes when there is dma is half full or full + let dma = poll_fn(|cx| { + self.ring_buf.set_waker(cx.waker()); + + let status = match dma_init { + false => Poll::Pending, + true => Poll::Ready(()), + }; + + dma_init = true; + status + }); + + // Future which completes when idle line is detected + let uart = poll_fn(|cx| { + let s = T::state(); + s.rx_waker.register(cx.waker()); + + compiler_fence(Ordering::SeqCst); + + // Critical section is needed so that IDLE isn't set after + // our read but before we clear it. + let sr = critical_section::with(|_| clear_idle_flag(T::regs())); + + check_for_errors(sr)?; + + if sr.idle() { + // Idle line is detected + Poll::Ready(Ok(())) + } else { + Poll::Pending + } + }); + + match select(dma, uart).await { + Either::Left(((), _)) => Ok(()), + Either::Right((result, _)) => result, + } + } +} + +impl> Drop for RingBufferedUartRx<'_, T, RxDma> { + fn drop(&mut self) { + self.teardown_uart(); + } +} +/// Return an error result if the Sr register has errors +fn check_for_errors(s: Sr) -> Result<(), Error> { + if s.pe() { + Err(Error::Parity) + } else if s.fe() { + Err(Error::Framing) + } else if s.ne() { + Err(Error::Noise) + } else if s.ore() { + Err(Error::Overrun) + } else { + Ok(()) + } +} + +/// Clear IDLE and return the Sr register +fn clear_idle_flag(r: Regs) -> Sr { + // SAFETY: read only and we only use Rx related flags + + let sr = sr(r).read(); + + // This read also clears the error and idle interrupt flags on v1. + unsafe { rdr(r).read_volatile() }; + clear_interrupt_flags(r, sr); + + r.cr1().modify(|w| w.set_idleie(true)); + + sr +} + +#[cfg(all(feature = "unstable-traits", feature = "nightly"))] +mod eio { + use embedded_io::asynch::Read; + use embedded_io::Io; + + use super::RingBufferedUartRx; + use crate::usart::{BasicInstance, Error, RxDma}; + + impl Io for RingBufferedUartRx<'_, T, Rx> + where + T: BasicInstance, + Rx: RxDma, + { + type Error = Error; + } + + impl Read for RingBufferedUartRx<'_, T, Rx> + where + T: BasicInstance, + Rx: RxDma, + { + async fn read(&mut self, buf: &mut [u8]) -> Result { + self.read(buf).await + } + } +} diff --git a/embassy-stm32/src/usb/mod.rs b/embassy-stm32/src/usb/mod.rs index fbd1fa823..bee287fe6 100644 --- a/embassy-stm32/src/usb/mod.rs +++ b/embassy-stm32/src/usb/mod.rs @@ -1,4 +1,4 @@ -use crate::interrupt::Interrupt; +use crate::interrupt; use crate::rcc::RccPeripheral; #[cfg(feature = "nightly")] @@ -13,7 +13,7 @@ pub(crate) mod sealed { } pub trait Instance: sealed::Instance + RccPeripheral + 'static { - type Interrupt: Interrupt; + type Interrupt: interrupt::typelevel::Interrupt; } // Internal PHY pins @@ -29,7 +29,7 @@ foreach_interrupt!( } impl Instance for crate::peripherals::$inst { - type Interrupt = crate::interrupt::$irq; + type Interrupt = crate::interrupt::typelevel::$irq; } }; ); diff --git a/embassy-stm32/src/usb/usb.rs b/embassy-stm32/src/usb/usb.rs index db965824a..ecdd1d0b8 100644 --- a/embassy-stm32/src/usb/usb.rs +++ b/embassy-stm32/src/usb/usb.rs @@ -1,43 +1,133 @@ #![macro_use] +use core::future::poll_fn; use core::marker::PhantomData; -use core::sync::atomic::Ordering; +use core::sync::atomic::{AtomicBool, Ordering}; use core::task::Poll; -use atomic_polyfill::{AtomicBool, AtomicU8}; use embassy_hal_common::into_ref; use embassy_sync::waitqueue::AtomicWaker; -use embassy_time::{block_for, Duration}; -use embassy_usb::driver::{self, EndpointAllocError, EndpointError, Event, Unsupported}; -use embassy_usb::types::{EndpointAddress, EndpointInfo, EndpointType, UsbDirection}; -use futures::future::poll_fn; -use futures::Future; -use pac::common::{Reg, RW}; -use pac::usb::vals::{EpType, Stat}; +use embassy_usb_driver as driver; +use embassy_usb_driver::{ + Direction, EndpointAddress, EndpointAllocError, EndpointError, EndpointInfo, EndpointType, Event, Unsupported, +}; use super::{DmPin, DpPin, Instance}; use crate::gpio::sealed::AFType; -use crate::interrupt::InterruptExt; +use crate::interrupt::typelevel::Interrupt; use crate::pac::usb::regs; +use crate::pac::usb::vals::{EpType, Stat}; +use crate::pac::USBRAM; use crate::rcc::sealed::RccPeripheral; -use crate::{pac, Peripheral}; +use crate::{interrupt, Peripheral}; + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let regs = T::regs(); + //let x = regs.istr().read().0; + //trace!("USB IRQ: {:08x}", x); + + let istr = regs.istr().read(); + + if istr.susp() { + //trace!("USB IRQ: susp"); + IRQ_SUSPEND.store(true, Ordering::Relaxed); + regs.cntr().modify(|w| { + w.set_fsusp(true); + w.set_lpmode(true); + }); + + // Write 0 to clear. + let mut clear = regs::Istr(!0); + clear.set_susp(false); + regs.istr().write_value(clear); + + // Wake main thread. + BUS_WAKER.wake(); + } + + if istr.wkup() { + //trace!("USB IRQ: wkup"); + IRQ_RESUME.store(true, Ordering::Relaxed); + regs.cntr().modify(|w| { + w.set_fsusp(false); + w.set_lpmode(false); + }); + + // Write 0 to clear. + let mut clear = regs::Istr(!0); + clear.set_wkup(false); + regs.istr().write_value(clear); + + // Wake main thread. + BUS_WAKER.wake(); + } + + if istr.reset() { + //trace!("USB IRQ: reset"); + IRQ_RESET.store(true, Ordering::Relaxed); + + // Write 0 to clear. + let mut clear = regs::Istr(!0); + clear.set_reset(false); + regs.istr().write_value(clear); + + // Wake main thread. + BUS_WAKER.wake(); + } + + if istr.ctr() { + let index = istr.ep_id() as usize; + let mut epr = regs.epr(index).read(); + if epr.ctr_rx() { + if index == 0 && epr.setup() { + EP0_SETUP.store(true, Ordering::Relaxed); + } + //trace!("EP {} RX, setup={}", index, epr.setup()); + EP_OUT_WAKERS[index].wake(); + } + if epr.ctr_tx() { + //trace!("EP {} TX", index); + EP_IN_WAKERS[index].wake(); + } + epr.set_dtog_rx(false); + epr.set_dtog_tx(false); + epr.set_stat_rx(Stat::from_bits(0)); + epr.set_stat_tx(Stat::from_bits(0)); + epr.set_ctr_rx(!epr.ctr_rx()); + epr.set_ctr_tx(!epr.ctr_tx()); + regs.epr(index).write_value(epr); + } + } +} const EP_COUNT: usize = 8; -#[cfg(any(usb_v1_x1, usb_v1_x2))] -const EP_MEMORY_SIZE: usize = 512; -#[cfg(not(any(usb_v1_x1, usb_v1_x2)))] -const EP_MEMORY_SIZE: usize = 1024; +#[cfg(any(usbram_16x1_512, usbram_16x2_512))] +const USBRAM_SIZE: usize = 512; +#[cfg(usbram_16x2_1024)] +const USBRAM_SIZE: usize = 1024; +#[cfg(usbram_32_2048)] +const USBRAM_SIZE: usize = 2048; + +#[cfg(not(usbram_32_2048))] +const USBRAM_ALIGN: usize = 2; +#[cfg(usbram_32_2048)] +const USBRAM_ALIGN: usize = 4; const NEW_AW: AtomicWaker = AtomicWaker::new(); static BUS_WAKER: AtomicWaker = NEW_AW; static EP0_SETUP: AtomicBool = AtomicBool::new(false); static EP_IN_WAKERS: [AtomicWaker; EP_COUNT] = [NEW_AW; EP_COUNT]; static EP_OUT_WAKERS: [AtomicWaker; EP_COUNT] = [NEW_AW; EP_COUNT]; -static IRQ_FLAGS: AtomicU8 = AtomicU8::new(0); -const IRQ_FLAG_RESET: u8 = 0x01; -const IRQ_FLAG_SUSPEND: u8 = 0x02; -const IRQ_FLAG_RESUME: u8 = 0x04; +static IRQ_RESET: AtomicBool = AtomicBool::new(false); +static IRQ_SUSPEND: AtomicBool = AtomicBool::new(false); +static IRQ_RESUME: AtomicBool = AtomicBool::new(false); fn convert_type(t: EndpointType) -> EpType { match t { @@ -53,30 +143,65 @@ fn invariant(mut r: regs::Epr) -> regs::Epr { r.set_ctr_tx(true); // don't clear r.set_dtog_rx(false); // don't toggle r.set_dtog_tx(false); // don't toggle - r.set_stat_rx(Stat(0)); - r.set_stat_tx(Stat(0)); + r.set_stat_rx(Stat::from_bits(0)); + r.set_stat_tx(Stat::from_bits(0)); r } +fn align_len_up(len: u16) -> u16 { + ((len as usize + USBRAM_ALIGN - 1) / USBRAM_ALIGN * USBRAM_ALIGN) as u16 +} + // Returns (actual_len, len_bits) fn calc_out_len(len: u16) -> (u16, u16) { match len { - 2..=62 => ((len + 1) / 2 * 2, ((len + 1) / 2) << 10), - 63..=480 => ((len + 31) / 32 * 32, (((len + 31) / 32 - 1) << 10) | 0x8000), + // NOTE: this could be 2..=62 with 16bit USBRAM, but not with 32bit. Limit it to 60 for simplicity. + 2..=60 => (align_len_up(len), align_len_up(len) / 2 << 10), + 61..=1024 => ((len + 31) / 32 * 32, (((len + 31) / 32 - 1) << 10) | 0x8000), _ => panic!("invalid OUT length {}", len), } } -fn ep_in_addr(index: usize) -> Reg { - T::regs().ep_mem(index * 4 + 0) + +#[cfg(not(usbram_32_2048))] +mod btable { + use super::*; + + pub(super) fn write_in(index: usize, addr: u16) { + USBRAM.mem(index * 4 + 0).write_value(addr); + } + + pub(super) fn write_in_len(index: usize, _addr: u16, len: u16) { + USBRAM.mem(index * 4 + 1).write_value(len); + } + + pub(super) fn write_out(index: usize, addr: u16, max_len_bits: u16) { + USBRAM.mem(index * 4 + 2).write_value(addr); + USBRAM.mem(index * 4 + 3).write_value(max_len_bits); + } + + pub(super) fn read_out_len(index: usize) -> u16 { + USBRAM.mem(index * 4 + 3).read() + } } -fn ep_in_len(index: usize) -> Reg { - T::regs().ep_mem(index * 4 + 1) -} -fn ep_out_addr(index: usize) -> Reg { - T::regs().ep_mem(index * 4 + 2) -} -fn ep_out_len(index: usize) -> Reg { - T::regs().ep_mem(index * 4 + 3) +#[cfg(usbram_32_2048)] +mod btable { + use super::*; + + pub(super) fn write_in(_index: usize, _addr: u16) {} + + pub(super) fn write_in_len(index: usize, addr: u16, len: u16) { + USBRAM.mem(index * 2).write_value((addr as u32) | ((len as u32) << 16)); + } + + pub(super) fn write_out(index: usize, addr: u16, max_len_bits: u16) { + USBRAM + .mem(index * 2 + 1) + .write_value((addr as u32) | ((max_len_bits as u32) << 16)); + } + + pub(super) fn read_out_len(index: usize) -> u16 { + (USBRAM.mem(index * 2 + 1).read() >> 16) as u16 + } } struct EndpointBuffer { @@ -88,23 +213,25 @@ struct EndpointBuffer { impl EndpointBuffer { fn read(&mut self, buf: &mut [u8]) { assert!(buf.len() <= self.len as usize); - for i in 0..((buf.len() + 1) / 2) { - let val = unsafe { T::regs().ep_mem(self.addr as usize / 2 + i).read() }; - buf[i * 2] = val as u8; - if i * 2 + 1 < buf.len() { - buf[i * 2 + 1] = (val >> 8) as u8; - } + for i in 0..(buf.len() + USBRAM_ALIGN - 1) / USBRAM_ALIGN { + let val = USBRAM.mem(self.addr as usize / USBRAM_ALIGN + i).read(); + let n = USBRAM_ALIGN.min(buf.len() - i * USBRAM_ALIGN); + buf[i * USBRAM_ALIGN..][..n].copy_from_slice(&val.to_le_bytes()[..n]); } } fn write(&mut self, buf: &[u8]) { assert!(buf.len() <= self.len as usize); - for i in 0..((buf.len() + 1) / 2) { - let mut val = buf[i * 2] as u16; - if i * 2 + 1 < buf.len() { - val |= (buf[i * 2 + 1] as u16) << 8; - } - unsafe { T::regs().ep_mem(self.addr as usize / 2 + i).write_value(val) }; + for i in 0..(buf.len() + USBRAM_ALIGN - 1) / USBRAM_ALIGN { + let mut val = [0u8; USBRAM_ALIGN]; + let n = USBRAM_ALIGN.min(buf.len() - i * USBRAM_ALIGN); + val[..n].copy_from_slice(&buf[i * USBRAM_ALIGN..][..n]); + + #[cfg(not(usbram_32_2048))] + let val = u16::from_le_bytes(val); + #[cfg(usbram_32_2048)] + let val = u32::from_le_bytes(val); + USBRAM.mem(self.addr as usize / USBRAM_ALIGN + i).write_value(val); } } } @@ -126,40 +253,43 @@ pub struct Driver<'d, T: Instance> { impl<'d, T: Instance> Driver<'d, T> { pub fn new( _usb: impl Peripheral

+ 'd, - irq: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, dp: impl Peripheral

> + 'd, dm: impl Peripheral

> + 'd, ) -> Self { - into_ref!(irq, dp, dm); - irq.set_handler(Self::on_interrupt); - irq.unpend(); - irq.enable(); + into_ref!(dp, dm); + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; let regs = T::regs(); #[cfg(stm32l5)] - unsafe { + { crate::peripherals::PWR::enable(); - - pac::PWR.cr2().modify(|w| w.set_usv(true)); + crate::pac::PWR.cr2().modify(|w| w.set_usv(true)); } - unsafe { - ::enable(); - ::reset(); + #[cfg(pwr_h5)] + crate::pac::PWR.usbscr().modify(|w| w.set_usb33sv(true)); - regs.cntr().write(|w| { - w.set_pdwn(false); - w.set_fres(true); - }); + ::enable(); + ::reset(); - block_for(Duration::from_millis(100)); + regs.cntr().write(|w| { + w.set_pdwn(false); + w.set_fres(true); + }); - regs.btable().write(|w| w.set_btable(0)); + #[cfg(time)] + embassy_time::block_for(embassy_time::Duration::from_millis(100)); + #[cfg(not(time))] + cortex_m::asm::delay(unsafe { crate::rcc::get_freqs() }.sys.0 / 10); - dp.set_as_af(dp.af_num(), AFType::OutputPushPull); - dm.set_as_af(dm.af_num(), AFType::OutputPushPull); - } + #[cfg(not(usb_v4))] + regs.btable().write(|w| w.set_btable(0)); + + dp.set_as_af(dp.af_num(), AFType::OutputPushPull); + dm.set_as_af(dm.af_num(), AFType::OutputPushPull); // Initialize the bus so that it signals that power is available BUS_WAKER.wake(); @@ -175,85 +305,10 @@ impl<'d, T: Instance> Driver<'d, T> { } } - fn on_interrupt(_: *mut ()) { - unsafe { - let regs = T::regs(); - //let x = regs.istr().read().0; - //trace!("USB IRQ: {:08x}", x); - - let istr = regs.istr().read(); - - let mut flags: u8 = 0; - - if istr.susp() { - //trace!("USB IRQ: susp"); - flags |= IRQ_FLAG_SUSPEND; - regs.cntr().modify(|w| { - w.set_fsusp(true); - w.set_lpmode(true); - }) - } - - if istr.wkup() { - //trace!("USB IRQ: wkup"); - flags |= IRQ_FLAG_RESUME; - regs.cntr().modify(|w| { - w.set_fsusp(false); - w.set_lpmode(false); - }) - } - - if istr.reset() { - //trace!("USB IRQ: reset"); - flags |= IRQ_FLAG_RESET; - - // Write 0 to clear. - let mut clear = regs::Istr(!0); - clear.set_reset(false); - regs.istr().write_value(clear); - } - - if flags != 0 { - // Send irqs to main thread. - IRQ_FLAGS.fetch_or(flags, Ordering::AcqRel); - BUS_WAKER.wake(); - - // Clear them - let mut mask = regs::Istr(0); - mask.set_wkup(true); - mask.set_susp(true); - mask.set_reset(true); - regs.istr().write_value(regs::Istr(!(istr.0 & mask.0))); - } - - if istr.ctr() { - let index = istr.ep_id() as usize; - let mut epr = regs.epr(index).read(); - if epr.ctr_rx() { - if index == 0 && epr.setup() { - EP0_SETUP.store(true, Ordering::Relaxed); - } - //trace!("EP {} RX, setup={}", index, epr.setup()); - EP_OUT_WAKERS[index].wake(); - } - if epr.ctr_tx() { - //trace!("EP {} TX", index); - EP_IN_WAKERS[index].wake(); - } - epr.set_dtog_rx(false); - epr.set_dtog_tx(false); - epr.set_stat_rx(Stat(0)); - epr.set_stat_tx(Stat(0)); - epr.set_ctr_rx(!epr.ctr_rx()); - epr.set_ctr_tx(!epr.ctr_tx()); - regs.epr(index).write_value(epr); - } - } - } - fn alloc_ep_mem(&mut self, len: u16) -> u16 { + assert!(len as usize % USBRAM_ALIGN == 0); let addr = self.ep_mem_free; - if addr + len > EP_MEMORY_SIZE as _ { + if addr + len > USBRAM_SIZE as _ { panic!("Endpoint memory full"); } self.ep_mem_free += len; @@ -264,13 +319,13 @@ impl<'d, T: Instance> Driver<'d, T> { &mut self, ep_type: EndpointType, max_packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result, driver::EndpointAllocError> { trace!( - "allocating type={:?} mps={:?} interval={}, dir={:?}", + "allocating type={:?} mps={:?} interval_ms={}, dir={:?}", ep_type, max_packet_size, - interval, + interval_ms, D::dir() ); @@ -280,8 +335,8 @@ impl<'d, T: Instance> Driver<'d, T> { } let used = ep.used_out || ep.used_in; let used_dir = match D::dir() { - UsbDirection::Out => ep.used_out, - UsbDirection::In => ep.used_in, + Direction::Out => ep.used_out, + Direction::In => ep.used_in, }; !used || (ep.ep_type == ep_type && !used_dir) }); @@ -294,7 +349,7 @@ impl<'d, T: Instance> Driver<'d, T> { ep.ep_type = ep_type; let buf = match D::dir() { - UsbDirection::Out => { + Direction::Out => { assert!(!ep.used_out); ep.used_out = true; @@ -302,10 +357,7 @@ impl<'d, T: Instance> Driver<'d, T> { let addr = self.alloc_ep_mem(len); trace!(" len_bits = {:04x}", len_bits); - unsafe { - ep_out_addr::(index).write_value(addr); - ep_out_len::(index).write_value(len_bits); - } + btable::write_out::(index, addr, len_bits); EndpointBuffer { addr, @@ -313,17 +365,15 @@ impl<'d, T: Instance> Driver<'d, T> { _phantom: PhantomData, } } - UsbDirection::In => { + Direction::In => { assert!(!ep.used_in); ep.used_in = true; - let len = (max_packet_size + 1) / 2 * 2; + let len = align_len_up(max_packet_size); let addr = self.alloc_ep_mem(len); - unsafe { - ep_in_addr::(index).write_value(addr); - // ep_in_len is written when actually TXing packets. - } + // ep_in_len is written when actually TXing packets. + btable::write_in::(index, addr); EndpointBuffer { addr, @@ -341,7 +391,7 @@ impl<'d, T: Instance> Driver<'d, T> { addr: EndpointAddress::from_parts(index, D::dir()), ep_type, max_packet_size, - interval, + interval_ms, }, buf, }) @@ -358,18 +408,18 @@ impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> { &mut self, ep_type: EndpointType, max_packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result { - self.alloc_endpoint(ep_type, max_packet_size, interval) + self.alloc_endpoint(ep_type, max_packet_size, interval_ms) } fn alloc_endpoint_out( &mut self, ep_type: EndpointType, max_packet_size: u16, - interval: u8, + interval_ms: u8, ) -> Result { - self.alloc_endpoint(ep_type, max_packet_size, interval) + self.alloc_endpoint(ep_type, max_packet_size, interval_ms) } fn start(mut self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) { @@ -384,19 +434,17 @@ impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> { let regs = T::regs(); - unsafe { - regs.cntr().write(|w| { - w.set_pdwn(false); - w.set_fres(false); - w.set_resetm(true); - w.set_suspm(true); - w.set_wkupm(true); - w.set_ctrm(true); - }); + regs.cntr().write(|w| { + w.set_pdwn(false); + w.set_fres(false); + w.set_resetm(true); + w.set_suspm(true); + w.set_wkupm(true); + w.set_ctrm(true); + }); - #[cfg(usb_v3)] - regs.bcdr().write(|w| w.set_dppu(true)) - } + #[cfg(any(usb_v3, usb_v4))] + regs.bcdr().write(|w| w.set_dppu(true)); trace!("enabled"); @@ -428,86 +476,72 @@ pub struct Bus<'d, T: Instance> { } impl<'d, T: Instance> driver::Bus for Bus<'d, T> { - type PollFuture<'a> = impl Future + 'a where Self: 'a; - - fn poll<'a>(&'a mut self) -> Self::PollFuture<'a> { - poll_fn(move |cx| unsafe { + async fn poll(&mut self) -> Event { + poll_fn(move |cx| { BUS_WAKER.register(cx.waker()); - if self.inited { - let regs = T::regs(); - - let flags = IRQ_FLAGS.load(Ordering::Acquire); - - if flags & IRQ_FLAG_RESUME != 0 { - IRQ_FLAGS.fetch_and(!IRQ_FLAG_RESUME, Ordering::AcqRel); - return Poll::Ready(Event::Resume); - } - - if flags & IRQ_FLAG_RESET != 0 { - IRQ_FLAGS.fetch_and(!IRQ_FLAG_RESET, Ordering::AcqRel); - - trace!("RESET REGS WRITINGINGING"); - regs.daddr().write(|w| { - w.set_ef(true); - w.set_add(0); - }); - - regs.epr(0).write(|w| { - w.set_ep_type(EpType::CONTROL); - w.set_stat_rx(Stat::NAK); - w.set_stat_tx(Stat::NAK); - }); - - for i in 1..EP_COUNT { - regs.epr(i).write(|w| { - w.set_ea(i as _); - w.set_ep_type(self.ep_types[i - 1]); - }) - } - - for w in &EP_IN_WAKERS { - w.wake() - } - for w in &EP_OUT_WAKERS { - w.wake() - } - - return Poll::Ready(Event::Reset); - } - - if flags & IRQ_FLAG_SUSPEND != 0 { - IRQ_FLAGS.fetch_and(!IRQ_FLAG_SUSPEND, Ordering::AcqRel); - return Poll::Ready(Event::Suspend); - } - - Poll::Pending - } else { + // TODO: implement VBUS detection. + if !self.inited { self.inited = true; return Poll::Ready(Event::PowerDetected); } - }) - } - #[inline] - fn set_address(&mut self, addr: u8) { - let regs = T::regs(); - trace!("setting addr: {}", addr); - unsafe { - regs.daddr().write(|w| { - w.set_ef(true); - w.set_add(addr); - }) - } + let regs = T::regs(); + + if IRQ_RESUME.load(Ordering::Acquire) { + IRQ_RESUME.store(false, Ordering::Relaxed); + return Poll::Ready(Event::Resume); + } + + if IRQ_RESET.load(Ordering::Acquire) { + IRQ_RESET.store(false, Ordering::Relaxed); + + trace!("RESET"); + regs.daddr().write(|w| { + w.set_ef(true); + w.set_add(0); + }); + + regs.epr(0).write(|w| { + w.set_ep_type(EpType::CONTROL); + w.set_stat_rx(Stat::NAK); + w.set_stat_tx(Stat::NAK); + }); + + for i in 1..EP_COUNT { + regs.epr(i).write(|w| { + w.set_ea(i as _); + w.set_ep_type(self.ep_types[i - 1]); + }) + } + + for w in &EP_IN_WAKERS { + w.wake() + } + for w in &EP_OUT_WAKERS { + w.wake() + } + + return Poll::Ready(Event::Reset); + } + + if IRQ_SUSPEND.load(Ordering::Acquire) { + IRQ_SUSPEND.store(false, Ordering::Relaxed); + return Poll::Ready(Event::Suspend); + } + + Poll::Pending + }) + .await } fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool) { // This can race, so do a retry loop. let reg = T::regs().epr(ep_addr.index() as _); match ep_addr.direction() { - UsbDirection::In => { + Direction::In => { loop { - let r = unsafe { reg.read() }; + let r = reg.read(); match r.stat_tx() { Stat::DISABLED => break, // if disabled, stall does nothing. Stat::STALL => break, // done! @@ -517,16 +551,16 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> { true => Stat::STALL, }; let mut w = invariant(r); - w.set_stat_tx(Stat(r.stat_tx().0 ^ want_stat.0)); - unsafe { reg.write_value(w) }; + w.set_stat_tx(Stat::from_bits(r.stat_tx().to_bits() ^ want_stat.to_bits())); + reg.write_value(w); } } } EP_IN_WAKERS[ep_addr.index()].wake(); } - UsbDirection::Out => { + Direction::Out => { loop { - let r = unsafe { reg.read() }; + let r = reg.read(); match r.stat_rx() { Stat::DISABLED => break, // if disabled, stall does nothing. Stat::STALL => break, // done! @@ -536,8 +570,8 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> { true => Stat::STALL, }; let mut w = invariant(r); - w.set_stat_rx(Stat(r.stat_rx().0 ^ want_stat.0)); - unsafe { reg.write_value(w) }; + w.set_stat_rx(Stat::from_bits(r.stat_rx().to_bits() ^ want_stat.to_bits())); + reg.write_value(w); } } } @@ -548,10 +582,10 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> { fn endpoint_is_stalled(&mut self, ep_addr: EndpointAddress) -> bool { let regs = T::regs(); - let epr = unsafe { regs.epr(ep_addr.index() as _).read() }; + let epr = regs.epr(ep_addr.index() as _).read(); match ep_addr.direction() { - UsbDirection::In => epr.stat_tx() == Stat::STALL, - UsbDirection::Out => epr.stat_rx() == Stat::STALL, + Direction::In => epr.stat_tx() == Stat::STALL, + Direction::Out => epr.stat_rx() == Stat::STALL, } } @@ -559,72 +593,61 @@ impl<'d, T: Instance> driver::Bus for Bus<'d, T> { trace!("set_enabled {:x} {}", ep_addr, enabled); // This can race, so do a retry loop. let reg = T::regs().epr(ep_addr.index() as _); - trace!("EPR before: {:04x}", unsafe { reg.read() }.0); + trace!("EPR before: {:04x}", reg.read().0); match ep_addr.direction() { - UsbDirection::In => { + Direction::In => { loop { let want_stat = match enabled { false => Stat::DISABLED, true => Stat::NAK, }; - let r = unsafe { reg.read() }; + let r = reg.read(); if r.stat_tx() == want_stat { break; } let mut w = invariant(r); - w.set_stat_tx(Stat(r.stat_tx().0 ^ want_stat.0)); - unsafe { reg.write_value(w) }; + w.set_stat_tx(Stat::from_bits(r.stat_tx().to_bits() ^ want_stat.to_bits())); + reg.write_value(w); } EP_IN_WAKERS[ep_addr.index()].wake(); } - UsbDirection::Out => { + Direction::Out => { loop { let want_stat = match enabled { false => Stat::DISABLED, true => Stat::VALID, }; - let r = unsafe { reg.read() }; + let r = reg.read(); if r.stat_rx() == want_stat { break; } let mut w = invariant(r); - w.set_stat_rx(Stat(r.stat_rx().0 ^ want_stat.0)); - unsafe { reg.write_value(w) }; + w.set_stat_rx(Stat::from_bits(r.stat_rx().to_bits() ^ want_stat.to_bits())); + reg.write_value(w); } EP_OUT_WAKERS[ep_addr.index()].wake(); } } - trace!("EPR after: {:04x}", unsafe { reg.read() }.0); + trace!("EPR after: {:04x}", reg.read().0); } - type EnableFuture<'a> = impl Future + 'a where Self: 'a; + async fn enable(&mut self) {} + async fn disable(&mut self) {} - fn enable(&mut self) -> Self::EnableFuture<'_> { - async move {} - } - - type DisableFuture<'a> = impl Future + 'a where Self: 'a; - - fn disable(&mut self) -> Self::DisableFuture<'_> { - async move {} - } - - type RemoteWakeupFuture<'a> = impl Future> + 'a where Self: 'a; - - fn remote_wakeup(&mut self) -> Self::RemoteWakeupFuture<'_> { - async move { Err(Unsupported) } + async fn remote_wakeup(&mut self) -> Result<(), Unsupported> { + Err(Unsupported) } } trait Dir { - fn dir() -> UsbDirection; + fn dir() -> Direction; fn waker(i: usize) -> &'static AtomicWaker; } pub enum In {} impl Dir for In { - fn dir() -> UsbDirection { - UsbDirection::In + fn dir() -> Direction { + Direction::In } #[inline] @@ -635,8 +658,8 @@ impl Dir for In { pub enum Out {} impl Dir for Out { - fn dir() -> UsbDirection { - UsbDirection::Out + fn dir() -> Direction { + Direction::Out } #[inline] @@ -655,12 +678,12 @@ impl<'d, T: Instance, D> Endpoint<'d, T, D> { fn write_data(&mut self, buf: &[u8]) { let index = self.info.addr.index(); self.buf.write(buf); - unsafe { ep_in_len::(index).write_value(buf.len() as _) }; + btable::write_in_len::(index, self.buf.addr, buf.len() as _); } fn read_data(&mut self, buf: &mut [u8]) -> Result { let index = self.info.addr.index(); - let rx_len = unsafe { ep_out_len::(index).read() as usize } & 0x3FF; + let rx_len = btable::read_out_len::(index) as usize & 0x3FF; trace!("READ DONE, rx_len = {}", rx_len); if rx_len > buf.len() { return Err(EndpointError::BufferOverflow); @@ -675,24 +698,20 @@ impl<'d, T: Instance> driver::Endpoint for Endpoint<'d, T, In> { &self.info } - type WaitEnabledFuture<'a> = impl Future + 'a where Self: 'a; - - fn wait_enabled(&mut self) -> Self::WaitEnabledFuture<'_> { - async move { - trace!("wait_enabled OUT WAITING"); - let index = self.info.addr.index(); - poll_fn(|cx| { - EP_OUT_WAKERS[index].register(cx.waker()); - let regs = T::regs(); - if unsafe { regs.epr(index).read() }.stat_tx() == Stat::DISABLED { - Poll::Pending - } else { - Poll::Ready(()) - } - }) - .await; - trace!("wait_enabled OUT OK"); - } + async fn wait_enabled(&mut self) { + trace!("wait_enabled OUT WAITING"); + let index = self.info.addr.index(); + poll_fn(|cx| { + EP_OUT_WAKERS[index].register(cx.waker()); + let regs = T::regs(); + if regs.epr(index).read().stat_tx() == Stat::DISABLED { + Poll::Pending + } else { + Poll::Ready(()) + } + }) + .await; + trace!("wait_enabled OUT OK"); } } @@ -701,116 +720,100 @@ impl<'d, T: Instance> driver::Endpoint for Endpoint<'d, T, Out> { &self.info } - type WaitEnabledFuture<'a> = impl Future + 'a where Self: 'a; - - fn wait_enabled(&mut self) -> Self::WaitEnabledFuture<'_> { - async move { - trace!("wait_enabled OUT WAITING"); - let index = self.info.addr.index(); - poll_fn(|cx| { - EP_OUT_WAKERS[index].register(cx.waker()); - let regs = T::regs(); - if unsafe { regs.epr(index).read() }.stat_rx() == Stat::DISABLED { - Poll::Pending - } else { - Poll::Ready(()) - } - }) - .await; - trace!("wait_enabled OUT OK"); - } + async fn wait_enabled(&mut self) { + trace!("wait_enabled OUT WAITING"); + let index = self.info.addr.index(); + poll_fn(|cx| { + EP_OUT_WAKERS[index].register(cx.waker()); + let regs = T::regs(); + if regs.epr(index).read().stat_rx() == Stat::DISABLED { + Poll::Pending + } else { + Poll::Ready(()) + } + }) + .await; + trace!("wait_enabled OUT OK"); } } impl<'d, T: Instance> driver::EndpointOut for Endpoint<'d, T, Out> { - type ReadFuture<'a> = impl Future> + 'a where Self: 'a; - - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - async move { - trace!("READ WAITING, buf.len() = {}", buf.len()); - let index = self.info.addr.index(); - let stat = poll_fn(|cx| { - EP_OUT_WAKERS[index].register(cx.waker()); - let regs = T::regs(); - let stat = unsafe { regs.epr(index).read() }.stat_rx(); - if matches!(stat, Stat::NAK | Stat::DISABLED) { - Poll::Ready(stat) - } else { - Poll::Pending - } - }) - .await; - - if stat == Stat::DISABLED { - return Err(EndpointError::Disabled); - } - - let rx_len = self.read_data(buf)?; - + async fn read(&mut self, buf: &mut [u8]) -> Result { + trace!("READ WAITING, buf.len() = {}", buf.len()); + let index = self.info.addr.index(); + let stat = poll_fn(|cx| { + EP_OUT_WAKERS[index].register(cx.waker()); let regs = T::regs(); - unsafe { - regs.epr(index).write(|w| { - w.set_ep_type(convert_type(self.info.ep_type)); - w.set_ea(self.info.addr.index() as _); - w.set_stat_rx(Stat(Stat::NAK.0 ^ Stat::VALID.0)); - w.set_stat_tx(Stat(0)); - w.set_ctr_rx(true); // don't clear - w.set_ctr_tx(true); // don't clear - }) - }; - trace!("READ OK, rx_len = {}", rx_len); + let stat = regs.epr(index).read().stat_rx(); + if matches!(stat, Stat::NAK | Stat::DISABLED) { + Poll::Ready(stat) + } else { + Poll::Pending + } + }) + .await; - Ok(rx_len) + if stat == Stat::DISABLED { + return Err(EndpointError::Disabled); } + + let rx_len = self.read_data(buf)?; + + let regs = T::regs(); + regs.epr(index).write(|w| { + w.set_ep_type(convert_type(self.info.ep_type)); + w.set_ea(self.info.addr.index() as _); + w.set_stat_rx(Stat::from_bits(Stat::NAK.to_bits() ^ Stat::VALID.to_bits())); + w.set_stat_tx(Stat::from_bits(0)); + w.set_ctr_rx(true); // don't clear + w.set_ctr_tx(true); // don't clear + }); + trace!("READ OK, rx_len = {}", rx_len); + + Ok(rx_len) } } impl<'d, T: Instance> driver::EndpointIn for Endpoint<'d, T, In> { - type WriteFuture<'a> = impl Future> + 'a where Self: 'a; - - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { - async move { - if buf.len() > self.info.max_packet_size as usize { - return Err(EndpointError::BufferOverflow); - } - - let index = self.info.addr.index(); - - trace!("WRITE WAITING"); - let stat = poll_fn(|cx| { - EP_IN_WAKERS[index].register(cx.waker()); - let regs = T::regs(); - let stat = unsafe { regs.epr(index).read() }.stat_tx(); - if matches!(stat, Stat::NAK | Stat::DISABLED) { - Poll::Ready(stat) - } else { - Poll::Pending - } - }) - .await; - - if stat == Stat::DISABLED { - return Err(EndpointError::Disabled); - } - - self.write_data(buf); - - let regs = T::regs(); - unsafe { - regs.epr(index).write(|w| { - w.set_ep_type(convert_type(self.info.ep_type)); - w.set_ea(self.info.addr.index() as _); - w.set_stat_tx(Stat(Stat::NAK.0 ^ Stat::VALID.0)); - w.set_stat_rx(Stat(0)); - w.set_ctr_rx(true); // don't clear - w.set_ctr_tx(true); // don't clear - }) - }; - - trace!("WRITE OK"); - - Ok(()) + async fn write(&mut self, buf: &[u8]) -> Result<(), EndpointError> { + if buf.len() > self.info.max_packet_size as usize { + return Err(EndpointError::BufferOverflow); } + + let index = self.info.addr.index(); + + trace!("WRITE WAITING"); + let stat = poll_fn(|cx| { + EP_IN_WAKERS[index].register(cx.waker()); + let regs = T::regs(); + let stat = regs.epr(index).read().stat_tx(); + if matches!(stat, Stat::NAK | Stat::DISABLED) { + Poll::Ready(stat) + } else { + Poll::Pending + } + }) + .await; + + if stat == Stat::DISABLED { + return Err(EndpointError::Disabled); + } + + self.write_data(buf); + + let regs = T::regs(); + regs.epr(index).write(|w| { + w.set_ep_type(convert_type(self.info.ep_type)); + w.set_ea(self.info.addr.index() as _); + w.set_stat_tx(Stat::from_bits(Stat::NAK.to_bits() ^ Stat::VALID.to_bits())); + w.set_stat_rx(Stat::from_bits(0)); + w.set_ctr_rx(true); // don't clear + w.set_ctr_tx(true); // don't clear + }); + + trace!("WRITE OK"); + + Ok(()) } } @@ -822,84 +825,16 @@ pub struct ControlPipe<'d, T: Instance> { } impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { - type SetupFuture<'a> = impl Future + 'a where Self: 'a; - type DataOutFuture<'a> = impl Future> + 'a where Self: 'a; - type DataInFuture<'a> = impl Future> + 'a where Self: 'a; - type AcceptFuture<'a> = impl Future + 'a where Self: 'a; - type RejectFuture<'a> = impl Future + 'a where Self: 'a; - fn max_packet_size(&self) -> usize { usize::from(self.max_packet_size) } - fn setup<'a>(&'a mut self) -> Self::SetupFuture<'a> { - async move { - loop { - trace!("SETUP read waiting"); - poll_fn(|cx| { - EP_OUT_WAKERS[0].register(cx.waker()); - if EP0_SETUP.load(Ordering::Relaxed) { - Poll::Ready(()) - } else { - Poll::Pending - } - }) - .await; - - let mut buf = [0; 8]; - let rx_len = self.ep_out.read_data(&mut buf); - if rx_len != Ok(8) { - trace!("SETUP read failed: {:?}", rx_len); - continue; - } - - EP0_SETUP.store(false, Ordering::Relaxed); - - trace!("SETUP read ok"); - return buf; - } - } - } - - fn data_out<'a>(&'a mut self, buf: &'a mut [u8], first: bool, last: bool) -> Self::DataOutFuture<'a> { - async move { - let regs = T::regs(); - - // When a SETUP is received, Stat/Stat is set to NAK. - // On first transfer, we must set Stat=VALID, to get the OUT data stage. - // We want Stat=STALL so that the host gets a STALL if it switches to the status - // stage too soon, except in the last transfer we set Stat=NAK so that it waits - // for the status stage, which we will ACK or STALL later. - if first || last { - let mut stat_rx = 0; - let mut stat_tx = 0; - if first { - // change NAK -> VALID - stat_rx ^= Stat::NAK.0 ^ Stat::VALID.0; - stat_tx ^= Stat::NAK.0 ^ Stat::STALL.0; - } - if last { - // change STALL -> VALID - stat_tx ^= Stat::STALL.0 ^ Stat::NAK.0; - } - // Note: if this is the first AND last transfer, the above effectively - // changes stat_tx like NAK -> NAK, so noop. - unsafe { - regs.epr(0).write(|w| { - w.set_ep_type(EpType::CONTROL); - w.set_stat_rx(Stat(stat_rx)); - w.set_stat_tx(Stat(stat_tx)); - w.set_ctr_rx(true); // don't clear - w.set_ctr_tx(true); // don't clear - }) - } - } - - trace!("data_out WAITING, buf.len() = {}", buf.len()); + async fn setup(&mut self) -> [u8; 8] { + loop { + trace!("SETUP read waiting"); poll_fn(|cx| { EP_OUT_WAKERS[0].register(cx.waker()); - let regs = T::regs(); - if unsafe { regs.epr(0).read() }.stat_rx() == Stat::NAK { + if EP0_SETUP.load(Ordering::Relaxed) { Poll::Ready(()) } else { Poll::Pending @@ -907,157 +842,208 @@ impl<'d, T: Instance> driver::ControlPipe for ControlPipe<'d, T> { }) .await; - if EP0_SETUP.load(Ordering::Relaxed) { - trace!("received another SETUP, aborting data_out."); - return Err(EndpointError::Disabled); + let mut buf = [0; 8]; + let rx_len = self.ep_out.read_data(&mut buf); + if rx_len != Ok(8) { + trace!("SETUP read failed: {:?}", rx_len); + continue; } - let rx_len = self.ep_out.read_data(buf)?; + EP0_SETUP.store(false, Ordering::Relaxed); - unsafe { - regs.epr(0).write(|w| { - w.set_ep_type(EpType::CONTROL); - w.set_stat_rx(Stat(match last { - // If last, set STAT_RX=STALL. - true => Stat::NAK.0 ^ Stat::STALL.0, - // Otherwise, set STAT_RX=VALID, to allow the host to send the next packet. - false => Stat::NAK.0 ^ Stat::VALID.0, - })); - w.set_ctr_rx(true); // don't clear - w.set_ctr_tx(true); // don't clear - }) - }; - - Ok(rx_len) + trace!("SETUP read ok"); + return buf; } } - fn data_in<'a>(&'a mut self, buf: &'a [u8], first: bool, last: bool) -> Self::DataInFuture<'a> { - async move { - trace!("control: data_in"); + async fn data_out(&mut self, buf: &mut [u8], first: bool, last: bool) -> Result { + let regs = T::regs(); - if buf.len() > self.ep_in.info.max_packet_size as usize { - return Err(EndpointError::BufferOverflow); + // When a SETUP is received, Stat/Stat is set to NAK. + // On first transfer, we must set Stat=VALID, to get the OUT data stage. + // We want Stat=STALL so that the host gets a STALL if it switches to the status + // stage too soon, except in the last transfer we set Stat=NAK so that it waits + // for the status stage, which we will ACK or STALL later. + if first || last { + let mut stat_rx = 0; + let mut stat_tx = 0; + if first { + // change NAK -> VALID + stat_rx ^= Stat::NAK.to_bits() ^ Stat::VALID.to_bits(); + stat_tx ^= Stat::NAK.to_bits() ^ Stat::STALL.to_bits(); } - - let regs = T::regs(); - - // When a SETUP is received, Stat is set to NAK. - // We want it to be STALL in non-last transfers. - // We want it to be VALID in last transfer, so the HW does the status stage. - if first || last { - let mut stat_rx = 0; - if first { - // change NAK -> STALL - stat_rx ^= Stat::NAK.0 ^ Stat::STALL.0; - } - if last { - // change STALL -> VALID - stat_rx ^= Stat::STALL.0 ^ Stat::VALID.0; - } - // Note: if this is the first AND last transfer, the above effectively - // does a change of NAK -> VALID. - unsafe { - regs.epr(0).write(|w| { - w.set_ep_type(EpType::CONTROL); - w.set_stat_rx(Stat(stat_rx)); - w.set_ep_kind(last); // set OUT_STATUS if last. - w.set_ctr_rx(true); // don't clear - w.set_ctr_tx(true); // don't clear - }) - } + if last { + // change STALL -> VALID + stat_tx ^= Stat::STALL.to_bits() ^ Stat::NAK.to_bits(); } - - trace!("WRITE WAITING"); - poll_fn(|cx| { - EP_IN_WAKERS[0].register(cx.waker()); - EP_OUT_WAKERS[0].register(cx.waker()); - let regs = T::regs(); - if unsafe { regs.epr(0).read() }.stat_tx() == Stat::NAK { - Poll::Ready(()) - } else { - Poll::Pending - } - }) - .await; - - if EP0_SETUP.load(Ordering::Relaxed) { - trace!("received another SETUP, aborting data_in."); - return Err(EndpointError::Disabled); - } - - self.ep_in.write_data(buf); - - let regs = T::regs(); - unsafe { - regs.epr(0).write(|w| { - w.set_ep_type(EpType::CONTROL); - w.set_stat_tx(Stat(Stat::NAK.0 ^ Stat::VALID.0)); - w.set_ep_kind(last); // set OUT_STATUS if last. - w.set_ctr_rx(true); // don't clear - w.set_ctr_tx(true); // don't clear - }) - }; - - trace!("WRITE OK"); - - Ok(()) + // Note: if this is the first AND last transfer, the above effectively + // changes stat_tx like NAK -> NAK, so noop. + regs.epr(0).write(|w| { + w.set_ep_type(EpType::CONTROL); + w.set_stat_rx(Stat::from_bits(stat_rx)); + w.set_stat_tx(Stat::from_bits(stat_tx)); + w.set_ctr_rx(true); // don't clear + w.set_ctr_tx(true); // don't clear + }); } + + trace!("data_out WAITING, buf.len() = {}", buf.len()); + poll_fn(|cx| { + EP_OUT_WAKERS[0].register(cx.waker()); + let regs = T::regs(); + if regs.epr(0).read().stat_rx() == Stat::NAK { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + + if EP0_SETUP.load(Ordering::Relaxed) { + trace!("received another SETUP, aborting data_out."); + return Err(EndpointError::Disabled); + } + + let rx_len = self.ep_out.read_data(buf)?; + + regs.epr(0).write(|w| { + w.set_ep_type(EpType::CONTROL); + w.set_stat_rx(Stat::from_bits(match last { + // If last, set STAT_RX=STALL. + true => Stat::NAK.to_bits() ^ Stat::STALL.to_bits(), + // Otherwise, set STAT_RX=VALID, to allow the host to send the next packet. + false => Stat::NAK.to_bits() ^ Stat::VALID.to_bits(), + })); + w.set_ctr_rx(true); // don't clear + w.set_ctr_tx(true); // don't clear + }); + + Ok(rx_len) } - fn accept<'a>(&'a mut self) -> Self::AcceptFuture<'a> { - async move { - let regs = T::regs(); - trace!("control: accept"); + async fn data_in(&mut self, data: &[u8], first: bool, last: bool) -> Result<(), EndpointError> { + trace!("control: data_in"); - self.ep_in.write_data(&[]); - - // Set OUT=stall, IN=accept - unsafe { - let epr = regs.epr(0).read(); - regs.epr(0).write(|w| { - w.set_ep_type(EpType::CONTROL); - w.set_stat_rx(Stat(epr.stat_rx().0 ^ Stat::STALL.0)); - w.set_stat_tx(Stat(epr.stat_tx().0 ^ Stat::VALID.0)); - w.set_ctr_rx(true); // don't clear - w.set_ctr_tx(true); // don't clear - }); - } - trace!("control: accept WAITING"); - - // Wait is needed, so that we don't set the address too soon, breaking the status stage. - // (embassy-usb sets the address after accept() returns) - poll_fn(|cx| { - EP_IN_WAKERS[0].register(cx.waker()); - let regs = T::regs(); - if unsafe { regs.epr(0).read() }.stat_tx() == Stat::NAK { - Poll::Ready(()) - } else { - Poll::Pending - } - }) - .await; - - trace!("control: accept OK"); + if data.len() > self.ep_in.info.max_packet_size as usize { + return Err(EndpointError::BufferOverflow); } + + let regs = T::regs(); + + // When a SETUP is received, Stat is set to NAK. + // We want it to be STALL in non-last transfers. + // We want it to be VALID in last transfer, so the HW does the status stage. + if first || last { + let mut stat_rx = 0; + if first { + // change NAK -> STALL + stat_rx ^= Stat::NAK.to_bits() ^ Stat::STALL.to_bits(); + } + if last { + // change STALL -> VALID + stat_rx ^= Stat::STALL.to_bits() ^ Stat::VALID.to_bits(); + } + // Note: if this is the first AND last transfer, the above effectively + // does a change of NAK -> VALID. + regs.epr(0).write(|w| { + w.set_ep_type(EpType::CONTROL); + w.set_stat_rx(Stat::from_bits(stat_rx)); + w.set_ep_kind(last); // set OUT_STATUS if last. + w.set_ctr_rx(true); // don't clear + w.set_ctr_tx(true); // don't clear + }); + } + + trace!("WRITE WAITING"); + poll_fn(|cx| { + EP_IN_WAKERS[0].register(cx.waker()); + EP_OUT_WAKERS[0].register(cx.waker()); + let regs = T::regs(); + if regs.epr(0).read().stat_tx() == Stat::NAK { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + + if EP0_SETUP.load(Ordering::Relaxed) { + trace!("received another SETUP, aborting data_in."); + return Err(EndpointError::Disabled); + } + + self.ep_in.write_data(data); + + let regs = T::regs(); + regs.epr(0).write(|w| { + w.set_ep_type(EpType::CONTROL); + w.set_stat_tx(Stat::from_bits(Stat::NAK.to_bits() ^ Stat::VALID.to_bits())); + w.set_ep_kind(last); // set OUT_STATUS if last. + w.set_ctr_rx(true); // don't clear + w.set_ctr_tx(true); // don't clear + }); + + trace!("WRITE OK"); + + Ok(()) } - fn reject<'a>(&'a mut self) -> Self::RejectFuture<'a> { - async move { - let regs = T::regs(); - trace!("control: reject"); + async fn accept(&mut self) { + let regs = T::regs(); + trace!("control: accept"); - // Set IN+OUT to stall - unsafe { - let epr = regs.epr(0).read(); - regs.epr(0).write(|w| { - w.set_ep_type(EpType::CONTROL); - w.set_stat_rx(Stat(epr.stat_rx().0 ^ Stat::STALL.0)); - w.set_stat_tx(Stat(epr.stat_tx().0 ^ Stat::STALL.0)); - w.set_ctr_rx(true); // don't clear - w.set_ctr_tx(true); // don't clear - }); + self.ep_in.write_data(&[]); + + // Set OUT=stall, IN=accept + let epr = regs.epr(0).read(); + regs.epr(0).write(|w| { + w.set_ep_type(EpType::CONTROL); + w.set_stat_rx(Stat::from_bits(epr.stat_rx().to_bits() ^ Stat::STALL.to_bits())); + w.set_stat_tx(Stat::from_bits(epr.stat_tx().to_bits() ^ Stat::VALID.to_bits())); + w.set_ctr_rx(true); // don't clear + w.set_ctr_tx(true); // don't clear + }); + trace!("control: accept WAITING"); + + // Wait is needed, so that we don't set the address too soon, breaking the status stage. + // (embassy-usb sets the address after accept() returns) + poll_fn(|cx| { + EP_IN_WAKERS[0].register(cx.waker()); + let regs = T::regs(); + if regs.epr(0).read().stat_tx() == Stat::NAK { + Poll::Ready(()) + } else { + Poll::Pending } - } + }) + .await; + + trace!("control: accept OK"); + } + + async fn reject(&mut self) { + let regs = T::regs(); + trace!("control: reject"); + + // Set IN+OUT to stall + let epr = regs.epr(0).read(); + regs.epr(0).write(|w| { + w.set_ep_type(EpType::CONTROL); + w.set_stat_rx(Stat::from_bits(epr.stat_rx().to_bits() ^ Stat::STALL.to_bits())); + w.set_stat_tx(Stat::from_bits(epr.stat_tx().to_bits() ^ Stat::STALL.to_bits())); + w.set_ctr_rx(true); // don't clear + w.set_ctr_tx(true); // don't clear + }); + } + + async fn accept_set_address(&mut self, addr: u8) { + self.accept().await; + + let regs = T::regs(); + trace!("setting addr: {}", addr); + regs.daddr().write(|w| { + w.set_ef(true); + w.set_add(addr); + }); } } diff --git a/embassy-stm32/src/usb_otg.rs b/embassy-stm32/src/usb_otg.rs deleted file mode 100644 index f7faf12a8..000000000 --- a/embassy-stm32/src/usb_otg.rs +++ /dev/null @@ -1,213 +0,0 @@ -use core::marker::PhantomData; - -use embassy_hal_common::into_ref; - -use crate::gpio::sealed::AFType; -use crate::rcc::RccPeripheral; -use crate::{peripherals, Peripheral}; - -macro_rules! config_ulpi_pins { - ($($pin:ident),*) => { - into_ref!($($pin),*); - // NOTE(unsafe) Exclusive access to the registers - critical_section::with(|_| unsafe { - $( - $pin.set_as_af($pin.af_num(), AFType::OutputPushPull); - #[cfg(gpio_v2)] - $pin.set_speed(crate::gpio::Speed::VeryHigh); - )* - }) - }; -} - -/// USB PHY type -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum PhyType { - /// Internal Full-Speed PHY - /// - /// Available on most High-Speed peripherals. - InternalFullSpeed, - /// Internal High-Speed PHY - /// - /// Available on a few STM32 chips. - InternalHighSpeed, - /// External ULPI High-Speed PHY - ExternalHighSpeed, -} - -pub struct UsbOtg<'d, T: Instance> { - phantom: PhantomData<&'d mut T>, - _phy_type: PhyType, -} - -impl<'d, T: Instance> UsbOtg<'d, T> { - /// Initializes USB OTG peripheral with internal Full-Speed PHY - pub fn new_fs( - _peri: impl Peripheral

+ 'd, - dp: impl Peripheral

> + 'd, - dm: impl Peripheral

> + 'd, - ) -> Self { - into_ref!(dp, dm); - - unsafe { - dp.set_as_af(dp.af_num(), AFType::OutputPushPull); - dm.set_as_af(dm.af_num(), AFType::OutputPushPull); - } - - Self { - phantom: PhantomData, - _phy_type: PhyType::InternalFullSpeed, - } - } - - /// Initializes USB OTG peripheral with external High-Speed PHY - pub fn new_hs_ulpi( - _peri: impl Peripheral

+ 'd, - ulpi_clk: impl Peripheral

> + 'd, - ulpi_dir: impl Peripheral

> + 'd, - ulpi_nxt: impl Peripheral

> + 'd, - ulpi_stp: impl Peripheral

> + 'd, - ulpi_d0: impl Peripheral

> + 'd, - ulpi_d1: impl Peripheral

> + 'd, - ulpi_d2: impl Peripheral

> + 'd, - ulpi_d3: impl Peripheral

> + 'd, - ulpi_d4: impl Peripheral

> + 'd, - ulpi_d5: impl Peripheral

> + 'd, - ulpi_d6: impl Peripheral

> + 'd, - ulpi_d7: impl Peripheral

> + 'd, - ) -> Self { - config_ulpi_pins!( - ulpi_clk, ulpi_dir, ulpi_nxt, ulpi_stp, ulpi_d0, ulpi_d1, ulpi_d2, ulpi_d3, ulpi_d4, ulpi_d5, ulpi_d6, - ulpi_d7 - ); - - Self { - phantom: PhantomData, - _phy_type: PhyType::ExternalHighSpeed, - } - } -} - -impl<'d, T: Instance> Drop for UsbOtg<'d, T> { - fn drop(&mut self) { - T::reset(); - T::disable(); - } -} - -pub(crate) mod sealed { - pub trait Instance { - const REGISTERS: *const (); - const HIGH_SPEED: bool; - const FIFO_DEPTH_WORDS: usize; - const ENDPOINT_COUNT: usize; - } -} - -pub trait Instance: sealed::Instance + RccPeripheral {} - -// Internal PHY pins -pin_trait!(DpPin, Instance); -pin_trait!(DmPin, Instance); - -// External PHY pins -pin_trait!(UlpiClkPin, Instance); -pin_trait!(UlpiDirPin, Instance); -pin_trait!(UlpiNxtPin, Instance); -pin_trait!(UlpiStpPin, Instance); -pin_trait!(UlpiD0Pin, Instance); -pin_trait!(UlpiD1Pin, Instance); -pin_trait!(UlpiD2Pin, Instance); -pin_trait!(UlpiD3Pin, Instance); -pin_trait!(UlpiD4Pin, Instance); -pin_trait!(UlpiD5Pin, Instance); -pin_trait!(UlpiD6Pin, Instance); -pin_trait!(UlpiD7Pin, Instance); - -foreach_peripheral!( - (otgfs, $inst:ident) => { - impl sealed::Instance for peripherals::$inst { - const REGISTERS: *const () = crate::pac::$inst.0 as *const (); - const HIGH_SPEED: bool = false; - - cfg_if::cfg_if! { - if #[cfg(stm32f1)] { - const FIFO_DEPTH_WORDS: usize = 128; - const ENDPOINT_COUNT: usize = 8; - } else if #[cfg(any( - stm32f2, - stm32f401, - stm32f405, - stm32f407, - stm32f411, - stm32f415, - stm32f417, - stm32f427, - stm32f429, - stm32f437, - stm32f439, - ))] { - const FIFO_DEPTH_WORDS: usize = 320; - const ENDPOINT_COUNT: usize = 4; - } else if #[cfg(any( - stm32f412, - stm32f413, - stm32f423, - stm32f446, - stm32f469, - stm32f479, - stm32f7, - stm32l4, - stm32u5, - ))] { - const FIFO_DEPTH_WORDS: usize = 320; - const ENDPOINT_COUNT: usize = 6; - } else if #[cfg(stm32g0x1)] { - const FIFO_DEPTH_WORDS: usize = 512; - const ENDPOINT_COUNT: usize = 8; - } else { - compile_error!("USB_OTG_FS peripheral is not supported by this chip."); - } - } - } - - impl Instance for peripherals::$inst {} - }; - - (otghs, $inst:ident) => { - impl sealed::Instance for peripherals::$inst { - const REGISTERS: *const () = crate::pac::$inst.0 as *const (); - const HIGH_SPEED: bool = true; - - cfg_if::cfg_if! { - if #[cfg(any( - stm32f2, - stm32f405, - stm32f407, - stm32f415, - stm32f417, - stm32f427, - stm32f429, - stm32f437, - stm32f439, - ))] { - const FIFO_DEPTH_WORDS: usize = 1024; - const ENDPOINT_COUNT: usize = 6; - } else if #[cfg(any( - stm32f446, - stm32f469, - stm32f479, - stm32f7, - stm32h7, - ))] { - const FIFO_DEPTH_WORDS: usize = 1024; - const ENDPOINT_COUNT: usize = 9; - } else { - compile_error!("USB_OTG_HS peripheral is not supported by this chip."); - } - } - } - - impl Instance for peripherals::$inst {} - }; -); diff --git a/embassy-stm32/src/usb_otg/mod.rs b/embassy-stm32/src/usb_otg/mod.rs new file mode 100644 index 000000000..12e5f0e60 --- /dev/null +++ b/embassy-stm32/src/usb_otg/mod.rs @@ -0,0 +1,165 @@ +use crate::rcc::RccPeripheral; +use crate::{interrupt, peripherals}; + +#[cfg(feature = "nightly")] +mod usb; +#[cfg(feature = "nightly")] +pub use usb::*; + +// Using Instance::ENDPOINT_COUNT requires feature(const_generic_expr) so just define maximum eps +#[cfg(feature = "nightly")] +const MAX_EP_COUNT: usize = 9; + +pub(crate) mod sealed { + pub trait Instance { + const HIGH_SPEED: bool; + const FIFO_DEPTH_WORDS: u16; + const ENDPOINT_COUNT: usize; + + fn regs() -> crate::pac::otg::Otg; + #[cfg(feature = "nightly")] + fn state() -> &'static super::State<{ super::MAX_EP_COUNT }>; + } +} + +pub trait Instance: sealed::Instance + RccPeripheral { + type Interrupt: interrupt::typelevel::Interrupt; +} + +// Internal PHY pins +pin_trait!(DpPin, Instance); +pin_trait!(DmPin, Instance); + +// External PHY pins +pin_trait!(UlpiClkPin, Instance); +pin_trait!(UlpiDirPin, Instance); +pin_trait!(UlpiNxtPin, Instance); +pin_trait!(UlpiStpPin, Instance); +pin_trait!(UlpiD0Pin, Instance); +pin_trait!(UlpiD1Pin, Instance); +pin_trait!(UlpiD2Pin, Instance); +pin_trait!(UlpiD3Pin, Instance); +pin_trait!(UlpiD4Pin, Instance); +pin_trait!(UlpiD5Pin, Instance); +pin_trait!(UlpiD6Pin, Instance); +pin_trait!(UlpiD7Pin, Instance); + +foreach_interrupt!( + (USB_OTG_FS, otg, $block:ident, GLOBAL, $irq:ident) => { + impl sealed::Instance for peripherals::USB_OTG_FS { + const HIGH_SPEED: bool = false; + + cfg_if::cfg_if! { + if #[cfg(stm32f1)] { + const FIFO_DEPTH_WORDS: u16 = 128; + const ENDPOINT_COUNT: usize = 8; + } else if #[cfg(any( + stm32f2, + stm32f401, + stm32f405, + stm32f407, + stm32f411, + stm32f415, + stm32f417, + stm32f427, + stm32f429, + stm32f437, + stm32f439, + ))] { + const FIFO_DEPTH_WORDS: u16 = 320; + const ENDPOINT_COUNT: usize = 4; + } else if #[cfg(any( + stm32f412, + stm32f413, + stm32f423, + stm32f446, + stm32f469, + stm32f479, + stm32f7, + stm32l4, + stm32u5, + ))] { + const FIFO_DEPTH_WORDS: u16 = 320; + const ENDPOINT_COUNT: usize = 6; + } else if #[cfg(stm32g0x1)] { + const FIFO_DEPTH_WORDS: u16 = 512; + const ENDPOINT_COUNT: usize = 8; + } else if #[cfg(stm32h7)] { + const FIFO_DEPTH_WORDS: u16 = 1024; + const ENDPOINT_COUNT: usize = 9; + } else if #[cfg(stm32u5)] { + const FIFO_DEPTH_WORDS: u16 = 320; + const ENDPOINT_COUNT: usize = 6; + } else { + compile_error!("USB_OTG_FS peripheral is not supported by this chip."); + } + } + + fn regs() -> crate::pac::otg::Otg { + crate::pac::USB_OTG_FS + } + + #[cfg(feature = "nightly")] + fn state() -> &'static State { + static STATE: State = State::new(); + &STATE + } + } + + impl Instance for peripherals::USB_OTG_FS { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; + + (USB_OTG_HS, otg, $block:ident, GLOBAL, $irq:ident) => { + impl sealed::Instance for peripherals::USB_OTG_HS { + const HIGH_SPEED: bool = true; + + cfg_if::cfg_if! { + if #[cfg(any( + stm32f2, + stm32f405, + stm32f407, + stm32f415, + stm32f417, + stm32f427, + stm32f429, + stm32f437, + stm32f439, + ))] { + const FIFO_DEPTH_WORDS: u16 = 1024; + const ENDPOINT_COUNT: usize = 6; + } else if #[cfg(any( + stm32f446, + stm32f469, + stm32f479, + stm32f7, + stm32h7, + ))] { + const FIFO_DEPTH_WORDS: u16 = 1024; + const ENDPOINT_COUNT: usize = 9; + } else if #[cfg(stm32u5)] { + const FIFO_DEPTH_WORDS: u16 = 1024; + const ENDPOINT_COUNT: usize = 9; + } else { + compile_error!("USB_OTG_HS peripheral is not supported by this chip."); + } + } + + fn regs() -> crate::pac::otg::Otg { + // OTG HS registers are a superset of FS registers + unsafe { crate::pac::otg::Otg::from_ptr(crate::pac::USB_OTG_HS.as_ptr()) } + } + + #[cfg(feature = "nightly")] + fn state() -> &'static State { + static STATE: State = State::new(); + &STATE + } + } + + impl Instance for peripherals::USB_OTG_HS { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +); diff --git a/embassy-stm32/src/usb_otg/usb.rs b/embassy-stm32/src/usb_otg/usb.rs new file mode 100644 index 000000000..fd0e22adf --- /dev/null +++ b/embassy-stm32/src/usb_otg/usb.rs @@ -0,0 +1,1464 @@ +use core::cell::UnsafeCell; +use core::marker::PhantomData; +use core::sync::atomic::{AtomicBool, AtomicU16, Ordering}; +use core::task::Poll; + +use embassy_hal_common::{into_ref, Peripheral}; +use embassy_sync::waitqueue::AtomicWaker; +use embassy_usb_driver::{ + self, Bus as _, Direction, EndpointAddress, EndpointAllocError, EndpointError, EndpointIn, EndpointInfo, + EndpointOut, EndpointType, Event, Unsupported, +}; +use futures::future::poll_fn; + +use super::*; +use crate::gpio::sealed::AFType; +use crate::interrupt; +use crate::interrupt::typelevel::Interrupt; +use crate::pac::otg::{regs, vals}; +use crate::rcc::sealed::RccPeripheral; +use crate::time::Hertz; + +/// Interrupt handler. +pub struct InterruptHandler { + _phantom: PhantomData, +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + trace!("irq"); + let r = T::regs(); + let state = T::state(); + + let ints = r.gintsts().read(); + if ints.wkupint() || ints.usbsusp() || ints.usbrst() || ints.enumdne() || ints.otgint() || ints.srqint() { + // Mask interrupts and notify `Bus` to process them + r.gintmsk().write(|_| {}); + T::state().bus_waker.wake(); + } + + // Handle RX + while r.gintsts().read().rxflvl() { + let status = r.grxstsp().read(); + let ep_num = status.epnum() as usize; + let len = status.bcnt() as usize; + + assert!(ep_num < T::ENDPOINT_COUNT); + + match status.pktstsd() { + vals::Pktstsd::SETUP_DATA_RX => { + trace!("SETUP_DATA_RX"); + assert!(len == 8, "invalid SETUP packet length={}", len); + assert!(ep_num == 0, "invalid SETUP packet endpoint={}", ep_num); + + if state.ep0_setup_ready.load(Ordering::Relaxed) == false { + // SAFETY: exclusive access ensured by atomic bool + let data = unsafe { &mut *state.ep0_setup_data.get() }; + data[0..4].copy_from_slice(&r.fifo(0).read().0.to_ne_bytes()); + data[4..8].copy_from_slice(&r.fifo(0).read().0.to_ne_bytes()); + state.ep0_setup_ready.store(true, Ordering::Release); + state.ep_out_wakers[0].wake(); + } else { + error!("received SETUP before previous finished processing"); + // discard FIFO + r.fifo(0).read(); + r.fifo(0).read(); + } + } + vals::Pktstsd::OUT_DATA_RX => { + trace!("OUT_DATA_RX ep={} len={}", ep_num, len); + + if state.ep_out_size[ep_num].load(Ordering::Acquire) == EP_OUT_BUFFER_EMPTY { + // SAFETY: Buffer size is allocated to be equal to endpoint's maximum packet size + // We trust the peripheral to not exceed its configured MPSIZ + let buf = unsafe { core::slice::from_raw_parts_mut(*state.ep_out_buffers[ep_num].get(), len) }; + + for chunk in buf.chunks_mut(4) { + // RX FIFO is shared so always read from fifo(0) + let data = r.fifo(0).read().0; + chunk.copy_from_slice(&data.to_ne_bytes()[0..chunk.len()]); + } + + state.ep_out_size[ep_num].store(len as u16, Ordering::Release); + state.ep_out_wakers[ep_num].wake(); + } else { + error!("ep_out buffer overflow index={}", ep_num); + + // discard FIFO data + let len_words = (len + 3) / 4; + for _ in 0..len_words { + r.fifo(0).read().data(); + } + } + } + vals::Pktstsd::OUT_DATA_DONE => { + trace!("OUT_DATA_DONE ep={}", ep_num); + } + vals::Pktstsd::SETUP_DATA_DONE => { + trace!("SETUP_DATA_DONE ep={}", ep_num); + } + x => trace!("unknown PKTSTS: {}", x.to_bits()), + } + } + + // IN endpoint interrupt + if ints.iepint() { + let mut ep_mask = r.daint().read().iepint(); + let mut ep_num = 0; + + // Iterate over endpoints while there are non-zero bits in the mask + while ep_mask != 0 { + if ep_mask & 1 != 0 { + let ep_ints = r.diepint(ep_num).read(); + + // clear all + r.diepint(ep_num).write_value(ep_ints); + + // TXFE is cleared in DIEPEMPMSK + if ep_ints.txfe() { + critical_section::with(|_| { + r.diepempmsk().modify(|w| { + w.set_ineptxfem(w.ineptxfem() & !(1 << ep_num)); + }); + }); + } + + state.ep_in_wakers[ep_num].wake(); + trace!("in ep={} irq val={:08x}", ep_num, ep_ints.0); + } + + ep_mask >>= 1; + ep_num += 1; + } + } + + // not needed? reception handled in rxflvl + // OUT endpoint interrupt + // if ints.oepint() { + // let mut ep_mask = r.daint().read().oepint(); + // let mut ep_num = 0; + + // while ep_mask != 0 { + // if ep_mask & 1 != 0 { + // let ep_ints = r.doepint(ep_num).read(); + // // clear all + // r.doepint(ep_num).write_value(ep_ints); + // state.ep_out_wakers[ep_num].wake(); + // trace!("out ep={} irq val={:08x}", ep_num, ep_ints.0); + // } + + // ep_mask >>= 1; + // ep_num += 1; + // } + // } + } +} + +macro_rules! config_ulpi_pins { + ($($pin:ident),*) => { + into_ref!($($pin),*); + critical_section::with(|_| { + $( + $pin.set_as_af($pin.af_num(), AFType::OutputPushPull); + #[cfg(gpio_v2)] + $pin.set_speed(crate::gpio::Speed::VeryHigh); + )* + }) + }; +} + +// From `synopsys-usb-otg` crate: +// This calculation doesn't correspond to one in a Reference Manual. +// In fact, the required number of words is higher than indicated in RM. +// The following numbers are pessimistic and were figured out empirically. +const RX_FIFO_EXTRA_SIZE_WORDS: u16 = 30; + +/// USB PHY type +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum PhyType { + /// Internal Full-Speed PHY + /// + /// Available on most High-Speed peripherals. + InternalFullSpeed, + /// Internal High-Speed PHY + /// + /// Available on a few STM32 chips. + InternalHighSpeed, + /// External ULPI High-Speed PHY + ExternalHighSpeed, +} + +impl PhyType { + pub fn internal(&self) -> bool { + match self { + PhyType::InternalFullSpeed | PhyType::InternalHighSpeed => true, + PhyType::ExternalHighSpeed => false, + } + } + + pub fn high_speed(&self) -> bool { + match self { + PhyType::InternalFullSpeed => false, + PhyType::ExternalHighSpeed | PhyType::InternalHighSpeed => true, + } + } + + pub fn to_dspd(&self) -> vals::Dspd { + match self { + PhyType::InternalFullSpeed => vals::Dspd::FULL_SPEED_INTERNAL, + PhyType::InternalHighSpeed => vals::Dspd::HIGH_SPEED, + PhyType::ExternalHighSpeed => vals::Dspd::HIGH_SPEED, + } + } +} + +/// Indicates that [State::ep_out_buffers] is empty. +const EP_OUT_BUFFER_EMPTY: u16 = u16::MAX; + +pub struct State { + /// Holds received SETUP packets. Available if [State::ep0_setup_ready] is true. + ep0_setup_data: UnsafeCell<[u8; 8]>, + ep0_setup_ready: AtomicBool, + ep_in_wakers: [AtomicWaker; EP_COUNT], + ep_out_wakers: [AtomicWaker; EP_COUNT], + /// RX FIFO is shared so extra buffers are needed to dequeue all data without waiting on each endpoint. + /// Buffers are ready when associated [State::ep_out_size] != [EP_OUT_BUFFER_EMPTY]. + ep_out_buffers: [UnsafeCell<*mut u8>; EP_COUNT], + ep_out_size: [AtomicU16; EP_COUNT], + bus_waker: AtomicWaker, +} + +unsafe impl Send for State {} +unsafe impl Sync for State {} + +impl State { + pub const fn new() -> Self { + const NEW_AW: AtomicWaker = AtomicWaker::new(); + const NEW_BUF: UnsafeCell<*mut u8> = UnsafeCell::new(0 as _); + const NEW_SIZE: AtomicU16 = AtomicU16::new(EP_OUT_BUFFER_EMPTY); + + Self { + ep0_setup_data: UnsafeCell::new([0u8; 8]), + ep0_setup_ready: AtomicBool::new(false), + ep_in_wakers: [NEW_AW; EP_COUNT], + ep_out_wakers: [NEW_AW; EP_COUNT], + ep_out_buffers: [NEW_BUF; EP_COUNT], + ep_out_size: [NEW_SIZE; EP_COUNT], + bus_waker: NEW_AW, + } + } +} + +#[derive(Debug, Clone, Copy)] +struct EndpointData { + ep_type: EndpointType, + max_packet_size: u16, + fifo_size_words: u16, +} + +#[non_exhaustive] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct Config { + /// Enable VBUS detection. + /// + /// The USB spec requires USB devices monitor for USB cable plug/unplug and react accordingly. + /// This is done by checkihg whether there is 5V on the VBUS pin or not. + /// + /// If your device is bus-powered (powers itself from the USB host via VBUS), then this is optional. + /// (if there's no power in VBUS your device would be off anyway, so it's fine to always assume + /// there's power in VBUS, i.e. the USB cable is always plugged in.) + /// + /// If your device is self-powered (i.e. it gets power from a source other than the USB cable, and + /// therefore can stay powered through USB cable plug/unplug) then you MUST set this to true. + /// + /// If you set this to true, you must connect VBUS to PA9 for FS, PB13 for HS, possibly with a + /// voltage divider. See ST application note AN4879 and the reference manual for more details. + pub vbus_detection: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { vbus_detection: true } + } +} + +pub struct Driver<'d, T: Instance> { + config: Config, + phantom: PhantomData<&'d mut T>, + ep_in: [Option; MAX_EP_COUNT], + ep_out: [Option; MAX_EP_COUNT], + ep_out_buffer: &'d mut [u8], + ep_out_buffer_offset: usize, + phy_type: PhyType, +} + +impl<'d, T: Instance> Driver<'d, T> { + /// Initializes USB OTG peripheral with internal Full-Speed PHY. + /// + /// # Arguments + /// + /// * `ep_out_buffer` - An internal buffer used to temporarily store recevied packets. + /// Must be large enough to fit all OUT endpoint max packet sizes. + /// Endpoint allocation will fail if it is too small. + pub fn new_fs( + _peri: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + dp: impl Peripheral

> + 'd, + dm: impl Peripheral

> + 'd, + ep_out_buffer: &'d mut [u8], + config: Config, + ) -> Self { + into_ref!(dp, dm); + + dp.set_as_af(dp.af_num(), AFType::OutputPushPull); + dm.set_as_af(dm.af_num(), AFType::OutputPushPull); + + Self { + config, + phantom: PhantomData, + ep_in: [None; MAX_EP_COUNT], + ep_out: [None; MAX_EP_COUNT], + ep_out_buffer, + ep_out_buffer_offset: 0, + phy_type: PhyType::InternalFullSpeed, + } + } + + /// Initializes USB OTG peripheral with external High-Speed PHY. + /// + /// # Arguments + /// + /// * `ep_out_buffer` - An internal buffer used to temporarily store recevied packets. + /// Must be large enough to fit all OUT endpoint max packet sizes. + /// Endpoint allocation will fail if it is too small. + pub fn new_hs_ulpi( + _peri: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding> + 'd, + ulpi_clk: impl Peripheral

> + 'd, + ulpi_dir: impl Peripheral

> + 'd, + ulpi_nxt: impl Peripheral

> + 'd, + ulpi_stp: impl Peripheral

> + 'd, + ulpi_d0: impl Peripheral

> + 'd, + ulpi_d1: impl Peripheral

> + 'd, + ulpi_d2: impl Peripheral

> + 'd, + ulpi_d3: impl Peripheral

> + 'd, + ulpi_d4: impl Peripheral

> + 'd, + ulpi_d5: impl Peripheral

> + 'd, + ulpi_d6: impl Peripheral

> + 'd, + ulpi_d7: impl Peripheral

> + 'd, + ep_out_buffer: &'d mut [u8], + config: Config, + ) -> Self { + assert!(T::HIGH_SPEED == true, "Peripheral is not capable of high-speed USB"); + + config_ulpi_pins!( + ulpi_clk, ulpi_dir, ulpi_nxt, ulpi_stp, ulpi_d0, ulpi_d1, ulpi_d2, ulpi_d3, ulpi_d4, ulpi_d5, ulpi_d6, + ulpi_d7 + ); + + Self { + config, + phantom: PhantomData, + ep_in: [None; MAX_EP_COUNT], + ep_out: [None; MAX_EP_COUNT], + ep_out_buffer, + ep_out_buffer_offset: 0, + phy_type: PhyType::ExternalHighSpeed, + } + } + + // Returns total amount of words (u32) allocated in dedicated FIFO + fn allocated_fifo_words(&self) -> u16 { + RX_FIFO_EXTRA_SIZE_WORDS + ep_fifo_size(&self.ep_out) + ep_fifo_size(&self.ep_in) + } + + fn alloc_endpoint( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + ) -> Result, EndpointAllocError> { + trace!( + "allocating type={:?} mps={:?} interval_ms={}, dir={:?}", + ep_type, + max_packet_size, + interval_ms, + D::dir() + ); + + if D::dir() == Direction::Out { + if self.ep_out_buffer_offset + max_packet_size as usize >= self.ep_out_buffer.len() { + error!("Not enough endpoint out buffer capacity"); + return Err(EndpointAllocError); + } + }; + + let fifo_size_words = match D::dir() { + Direction::Out => (max_packet_size + 3) / 4, + // INEPTXFD requires minimum size of 16 words + Direction::In => u16::max((max_packet_size + 3) / 4, 16), + }; + + if fifo_size_words + self.allocated_fifo_words() > T::FIFO_DEPTH_WORDS { + error!("Not enough FIFO capacity"); + return Err(EndpointAllocError); + } + + let eps = match D::dir() { + Direction::Out => &mut self.ep_out, + Direction::In => &mut self.ep_in, + }; + + // Find free endpoint slot + let slot = eps.iter_mut().enumerate().find(|(i, ep)| { + if *i == 0 && ep_type != EndpointType::Control { + // reserved for control pipe + false + } else { + ep.is_none() + } + }); + + let index = match slot { + Some((index, ep)) => { + *ep = Some(EndpointData { + ep_type, + max_packet_size, + fifo_size_words, + }); + index + } + None => { + error!("No free endpoints available"); + return Err(EndpointAllocError); + } + }; + + trace!(" index={}", index); + + if D::dir() == Direction::Out { + // Buffer capacity check was done above, now allocation cannot fail + unsafe { + *T::state().ep_out_buffers[index].get() = + self.ep_out_buffer.as_mut_ptr().offset(self.ep_out_buffer_offset as _); + } + self.ep_out_buffer_offset += max_packet_size as usize; + } + + Ok(Endpoint { + _phantom: PhantomData, + info: EndpointInfo { + addr: EndpointAddress::from_parts(index, D::dir()), + ep_type, + max_packet_size, + interval_ms, + }, + }) + } +} + +impl<'d, T: Instance> embassy_usb_driver::Driver<'d> for Driver<'d, T> { + type EndpointOut = Endpoint<'d, T, Out>; + type EndpointIn = Endpoint<'d, T, In>; + type ControlPipe = ControlPipe<'d, T>; + type Bus = Bus<'d, T>; + + fn alloc_endpoint_in( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + ) -> Result { + self.alloc_endpoint(ep_type, max_packet_size, interval_ms) + } + + fn alloc_endpoint_out( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + ) -> Result { + self.alloc_endpoint(ep_type, max_packet_size, interval_ms) + } + + fn start(mut self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) { + let ep_out = self + .alloc_endpoint(EndpointType::Control, control_max_packet_size, 0) + .unwrap(); + let ep_in = self + .alloc_endpoint(EndpointType::Control, control_max_packet_size, 0) + .unwrap(); + assert_eq!(ep_out.info.addr.index(), 0); + assert_eq!(ep_in.info.addr.index(), 0); + + trace!("start"); + + ( + Bus { + config: self.config, + phantom: PhantomData, + ep_in: self.ep_in, + ep_out: self.ep_out, + phy_type: self.phy_type, + inited: false, + }, + ControlPipe { + _phantom: PhantomData, + max_packet_size: control_max_packet_size, + ep_out, + ep_in, + }, + ) + } +} + +pub struct Bus<'d, T: Instance> { + config: Config, + phantom: PhantomData<&'d mut T>, + ep_in: [Option; MAX_EP_COUNT], + ep_out: [Option; MAX_EP_COUNT], + phy_type: PhyType, + inited: bool, +} + +impl<'d, T: Instance> Bus<'d, T> { + fn restore_irqs() { + T::regs().gintmsk().write(|w| { + w.set_usbrst(true); + w.set_enumdnem(true); + w.set_usbsuspm(true); + w.set_wuim(true); + w.set_iepint(true); + w.set_oepint(true); + w.set_rxflvlm(true); + w.set_srqim(true); + w.set_otgint(true); + }); + } +} + +impl<'d, T: Instance> Bus<'d, T> { + fn init(&mut self) { + #[cfg(stm32l4)] + { + crate::peripherals::PWR::enable(); + critical_section::with(|_| crate::pac::PWR.cr2().modify(|w| w.set_usv(true))); + } + + #[cfg(stm32f7)] + { + // Enable ULPI clock if external PHY is used + let ulpien = !self.phy_type.internal(); + critical_section::with(|_| { + crate::pac::RCC.ahb1enr().modify(|w| { + if T::HIGH_SPEED { + w.set_usb_otg_hsulpien(ulpien); + } else { + w.set_usb_otg_hsen(ulpien); + } + }); + + // Low power mode + crate::pac::RCC.ahb1lpenr().modify(|w| { + if T::HIGH_SPEED { + w.set_usb_otg_hsulpilpen(ulpien); + } else { + w.set_usb_otg_hslpen(ulpien); + } + }); + }); + } + + #[cfg(stm32h7)] + { + // If true, VDD33USB is generated by internal regulator from VDD50USB + // If false, VDD33USB and VDD50USB must be suplied directly with 3.3V (default on nucleo) + // TODO: unhardcode + let internal_regulator = false; + + // Enable USB power + critical_section::with(|_| { + crate::pac::PWR.cr3().modify(|w| { + w.set_usb33den(true); + w.set_usbregen(internal_regulator); + }) + }); + + // Wait for USB power to stabilize + while !crate::pac::PWR.cr3().read().usb33rdy() {} + + // Use internal 48MHz HSI clock. Should be enabled in RCC by default. + critical_section::with(|_| { + crate::pac::RCC + .d2ccip2r() + .modify(|w| w.set_usbsel(crate::pac::rcc::vals::Usbsel::HSI48)) + }); + + // Enable ULPI clock if external PHY is used + let ulpien = !self.phy_type.internal(); + critical_section::with(|_| { + crate::pac::RCC.ahb1enr().modify(|w| { + if T::HIGH_SPEED { + w.set_usb_otg_hs_ulpien(ulpien); + } else { + w.set_usb_otg_fs_ulpien(ulpien); + } + }); + crate::pac::RCC.ahb1lpenr().modify(|w| { + if T::HIGH_SPEED { + w.set_usb_otg_hs_ulpilpen(ulpien); + } else { + w.set_usb_otg_fs_ulpilpen(ulpien); + } + }); + }); + } + + #[cfg(stm32u5)] + { + // Enable USB power + critical_section::with(|_| { + crate::pac::RCC.ahb3enr().modify(|w| { + w.set_pwren(true); + }); + cortex_m::asm::delay(2); + + crate::pac::PWR.svmcr().modify(|w| { + w.set_usv(true); + w.set_uvmen(true); + }); + }); + + // Wait for USB power to stabilize + while !crate::pac::PWR.svmsr().read().vddusbrdy() {} + + // Select HSI48 as USB clock source. + critical_section::with(|_| { + crate::pac::RCC.ccipr1().modify(|w| { + w.set_iclksel(crate::pac::rcc::vals::Iclksel::HSI48); + }) + }); + } + + ::enable(); + ::reset(); + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + let r = T::regs(); + let core_id = r.cid().read().0; + trace!("Core id {:08x}", core_id); + + // Wait for AHB ready. + while !r.grstctl().read().ahbidl() {} + + // Configure as device. + r.gusbcfg().write(|w| { + // Force device mode + w.set_fdmod(true); + // Enable internal full-speed PHY + w.set_physel(self.phy_type.internal() && !self.phy_type.high_speed()); + }); + + // Configuring Vbus sense and SOF output + match core_id { + 0x0000_1200 | 0x0000_1100 => { + assert!(self.phy_type != PhyType::InternalHighSpeed); + + r.gccfg_v1().modify(|w| { + // Enable internal full-speed PHY, logic is inverted + w.set_pwrdwn(self.phy_type.internal()); + }); + + // F429-like chips have the GCCFG.NOVBUSSENS bit + r.gccfg_v1().modify(|w| { + w.set_novbussens(!self.config.vbus_detection); + w.set_vbusasen(false); + w.set_vbusbsen(self.config.vbus_detection); + w.set_sofouten(false); + }); + } + 0x0000_2000 | 0x0000_2100 | 0x0000_2300 | 0x0000_3000 | 0x0000_3100 => { + // F446-like chips have the GCCFG.VBDEN bit with the opposite meaning + r.gccfg_v2().modify(|w| { + // Enable internal full-speed PHY, logic is inverted + w.set_pwrdwn(self.phy_type.internal() && !self.phy_type.high_speed()); + w.set_phyhsen(self.phy_type.internal() && self.phy_type.high_speed()); + }); + + r.gccfg_v2().modify(|w| { + w.set_vbden(self.config.vbus_detection); + }); + + // Force B-peripheral session + r.gotgctl().modify(|w| { + w.set_bvaloen(!self.config.vbus_detection); + w.set_bvaloval(true); + }); + } + _ => unimplemented!("Unknown USB core id {:X}", core_id), + } + + // Soft disconnect. + r.dctl().write(|w| w.set_sdis(true)); + + // Set speed. + r.dcfg().write(|w| { + w.set_pfivl(vals::Pfivl::FRAME_INTERVAL_80); + w.set_dspd(self.phy_type.to_dspd()); + }); + + // Unmask transfer complete EP interrupt + r.diepmsk().write(|w| { + w.set_xfrcm(true); + }); + + // Unmask and clear core interrupts + Bus::::restore_irqs(); + r.gintsts().write_value(regs::Gintsts(0xFFFF_FFFF)); + + // Unmask global interrupt + r.gahbcfg().write(|w| { + w.set_gint(true); // unmask global interrupt + }); + + // Connect + r.dctl().write(|w| w.set_sdis(false)); + } + + fn init_fifo(&mut self) { + trace!("init_fifo"); + + let r = T::regs(); + + // Configure RX fifo size. All endpoints share the same FIFO area. + let rx_fifo_size_words = RX_FIFO_EXTRA_SIZE_WORDS + ep_fifo_size(&self.ep_out); + trace!("configuring rx fifo size={}", rx_fifo_size_words); + + r.grxfsiz().modify(|w| w.set_rxfd(rx_fifo_size_words)); + + // Configure TX (USB in direction) fifo size for each endpoint + let mut fifo_top = rx_fifo_size_words; + for i in 0..T::ENDPOINT_COUNT { + if let Some(ep) = self.ep_in[i] { + trace!( + "configuring tx fifo ep={}, offset={}, size={}", + i, + fifo_top, + ep.fifo_size_words + ); + + let dieptxf = if i == 0 { r.dieptxf0() } else { r.dieptxf(i - 1) }; + + dieptxf.write(|w| { + w.set_fd(ep.fifo_size_words); + w.set_sa(fifo_top); + }); + + fifo_top += ep.fifo_size_words; + } + } + + assert!( + fifo_top <= T::FIFO_DEPTH_WORDS, + "FIFO allocations exceeded maximum capacity" + ); + + // Flush fifos + r.grstctl().write(|w| { + w.set_rxfflsh(true); + w.set_txfflsh(true); + w.set_txfnum(0x10); + }); + loop { + let x = r.grstctl().read(); + if !x.rxfflsh() && !x.txfflsh() { + break; + } + } + } + + fn configure_endpoints(&mut self) { + trace!("configure_endpoints"); + + let r = T::regs(); + + // Configure IN endpoints + for (index, ep) in self.ep_in.iter().enumerate() { + if let Some(ep) = ep { + critical_section::with(|_| { + r.diepctl(index).write(|w| { + if index == 0 { + w.set_mpsiz(ep0_mpsiz(ep.max_packet_size)); + } else { + w.set_mpsiz(ep.max_packet_size); + w.set_eptyp(to_eptyp(ep.ep_type)); + w.set_sd0pid_sevnfrm(true); + w.set_txfnum(index as _); + w.set_snak(true); + } + }); + }); + } + } + + // Configure OUT endpoints + for (index, ep) in self.ep_out.iter().enumerate() { + if let Some(ep) = ep { + critical_section::with(|_| { + r.doepctl(index).write(|w| { + if index == 0 { + w.set_mpsiz(ep0_mpsiz(ep.max_packet_size)); + } else { + w.set_mpsiz(ep.max_packet_size); + w.set_eptyp(to_eptyp(ep.ep_type)); + w.set_sd0pid_sevnfrm(true); + } + }); + + r.doeptsiz(index).modify(|w| { + w.set_xfrsiz(ep.max_packet_size as _); + if index == 0 { + w.set_rxdpid_stupcnt(1); + } else { + w.set_pktcnt(1); + } + }); + }); + } + } + + // Enable IRQs for allocated endpoints + r.daintmsk().modify(|w| { + w.set_iepm(ep_irq_mask(&self.ep_in)); + // OUT interrupts not used, handled in RXFLVL + // w.set_oepm(ep_irq_mask(&self.ep_out)); + }); + } + + fn disable_all_endpoints(&mut self) { + for i in 0..T::ENDPOINT_COUNT { + self.endpoint_set_enabled(EndpointAddress::from_parts(i, Direction::In), false); + self.endpoint_set_enabled(EndpointAddress::from_parts(i, Direction::Out), false); + } + } + + fn disable(&mut self) { + T::Interrupt::disable(); + + ::disable(); + + #[cfg(stm32l4)] + crate::pac::PWR.cr2().modify(|w| w.set_usv(false)); + // Cannot disable PWR, because other peripherals might be using it + } +} + +impl<'d, T: Instance> embassy_usb_driver::Bus for Bus<'d, T> { + async fn poll(&mut self) -> Event { + poll_fn(move |cx| { + if !self.inited { + self.init(); + self.inited = true; + + // If no vbus detection, just return a single PowerDetected event at startup. + if !self.config.vbus_detection { + return Poll::Ready(Event::PowerDetected); + } + } + + let r = T::regs(); + + T::state().bus_waker.register(cx.waker()); + + let ints = r.gintsts().read(); + + if ints.srqint() { + trace!("vbus detected"); + + r.gintsts().write(|w| w.set_srqint(true)); // clear + Self::restore_irqs(); + + if self.config.vbus_detection { + return Poll::Ready(Event::PowerDetected); + } + } + + if ints.otgint() { + let otgints = r.gotgint().read(); + r.gotgint().write_value(otgints); // clear all + Self::restore_irqs(); + + if otgints.sedet() { + trace!("vbus removed"); + if self.config.vbus_detection { + self.disable_all_endpoints(); + return Poll::Ready(Event::PowerRemoved); + } + } + } + + if ints.usbrst() { + trace!("reset"); + + self.init_fifo(); + self.configure_endpoints(); + + // Reset address + critical_section::with(|_| { + r.dcfg().modify(|w| { + w.set_dad(0); + }); + }); + + r.gintsts().write(|w| w.set_usbrst(true)); // clear + Self::restore_irqs(); + } + + if ints.enumdne() { + trace!("enumdne"); + + let speed = r.dsts().read().enumspd(); + trace!(" speed={}", speed.to_bits()); + + r.gusbcfg().modify(|w| { + w.set_trdt(calculate_trdt(speed, T::frequency())); + }); + + r.gintsts().write(|w| w.set_enumdne(true)); // clear + Self::restore_irqs(); + + return Poll::Ready(Event::Reset); + } + + if ints.usbsusp() { + trace!("suspend"); + r.gintsts().write(|w| w.set_usbsusp(true)); // clear + Self::restore_irqs(); + return Poll::Ready(Event::Suspend); + } + + if ints.wkupint() { + trace!("resume"); + r.gintsts().write(|w| w.set_wkupint(true)); // clear + Self::restore_irqs(); + return Poll::Ready(Event::Resume); + } + + Poll::Pending + }) + .await + } + + fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool) { + trace!("endpoint_set_stalled ep={:?} en={}", ep_addr, stalled); + + assert!( + ep_addr.index() < T::ENDPOINT_COUNT, + "endpoint_set_stalled index {} out of range", + ep_addr.index() + ); + + let regs = T::regs(); + match ep_addr.direction() { + Direction::Out => { + critical_section::with(|_| { + regs.doepctl(ep_addr.index()).modify(|w| { + w.set_stall(stalled); + }); + }); + + T::state().ep_out_wakers[ep_addr.index()].wake(); + } + Direction::In => { + critical_section::with(|_| { + regs.diepctl(ep_addr.index()).modify(|w| { + w.set_stall(stalled); + }); + }); + + T::state().ep_in_wakers[ep_addr.index()].wake(); + } + } + } + + fn endpoint_is_stalled(&mut self, ep_addr: EndpointAddress) -> bool { + assert!( + ep_addr.index() < T::ENDPOINT_COUNT, + "endpoint_is_stalled index {} out of range", + ep_addr.index() + ); + + let regs = T::regs(); + + match ep_addr.direction() { + Direction::Out => regs.doepctl(ep_addr.index()).read().stall(), + Direction::In => regs.diepctl(ep_addr.index()).read().stall(), + } + } + + fn endpoint_set_enabled(&mut self, ep_addr: EndpointAddress, enabled: bool) { + trace!("endpoint_set_enabled ep={:?} en={}", ep_addr, enabled); + + assert!( + ep_addr.index() < T::ENDPOINT_COUNT, + "endpoint_set_enabled index {} out of range", + ep_addr.index() + ); + + let r = T::regs(); + match ep_addr.direction() { + Direction::Out => { + critical_section::with(|_| { + // cancel transfer if active + if !enabled && r.doepctl(ep_addr.index()).read().epena() { + r.doepctl(ep_addr.index()).modify(|w| { + w.set_snak(true); + w.set_epdis(true); + }) + } + + r.doepctl(ep_addr.index()).modify(|w| { + w.set_usbaep(enabled); + }); + + // Flush tx fifo + r.grstctl().write(|w| { + w.set_txfflsh(true); + w.set_txfnum(ep_addr.index() as _); + }); + loop { + let x = r.grstctl().read(); + if !x.txfflsh() { + break; + } + } + }); + + // Wake `Endpoint::wait_enabled()` + T::state().ep_out_wakers[ep_addr.index()].wake(); + } + Direction::In => { + critical_section::with(|_| { + // cancel transfer if active + if !enabled && r.diepctl(ep_addr.index()).read().epena() { + r.diepctl(ep_addr.index()).modify(|w| { + w.set_snak(true); // set NAK + w.set_epdis(true); + }) + } + + r.diepctl(ep_addr.index()).modify(|w| { + w.set_usbaep(enabled); + w.set_cnak(enabled); // clear NAK that might've been set by SNAK above. + }) + }); + + // Wake `Endpoint::wait_enabled()` + T::state().ep_in_wakers[ep_addr.index()].wake(); + } + } + } + + async fn enable(&mut self) { + trace!("enable"); + // TODO: enable the peripheral once enable/disable semantics are cleared up in embassy-usb + } + + async fn disable(&mut self) { + trace!("disable"); + + // TODO: disable the peripheral once enable/disable semantics are cleared up in embassy-usb + //Bus::disable(self); + } + + async fn remote_wakeup(&mut self) -> Result<(), Unsupported> { + Err(Unsupported) + } +} + +impl<'d, T: Instance> Drop for Bus<'d, T> { + fn drop(&mut self) { + Bus::disable(self); + } +} + +trait Dir { + fn dir() -> Direction; +} + +pub enum In {} +impl Dir for In { + fn dir() -> Direction { + Direction::In + } +} + +pub enum Out {} +impl Dir for Out { + fn dir() -> Direction { + Direction::Out + } +} + +pub struct Endpoint<'d, T: Instance, D> { + _phantom: PhantomData<(&'d mut T, D)>, + info: EndpointInfo, +} + +impl<'d, T: Instance> embassy_usb_driver::Endpoint for Endpoint<'d, T, In> { + fn info(&self) -> &EndpointInfo { + &self.info + } + + async fn wait_enabled(&mut self) { + poll_fn(|cx| { + let ep_index = self.info.addr.index(); + + T::state().ep_in_wakers[ep_index].register(cx.waker()); + + if T::regs().diepctl(ep_index).read().usbaep() { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await + } +} + +impl<'d, T: Instance> embassy_usb_driver::Endpoint for Endpoint<'d, T, Out> { + fn info(&self) -> &EndpointInfo { + &self.info + } + + async fn wait_enabled(&mut self) { + poll_fn(|cx| { + let ep_index = self.info.addr.index(); + + T::state().ep_out_wakers[ep_index].register(cx.waker()); + + if T::regs().doepctl(ep_index).read().usbaep() { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await + } +} + +impl<'d, T: Instance> embassy_usb_driver::EndpointOut for Endpoint<'d, T, Out> { + async fn read(&mut self, buf: &mut [u8]) -> Result { + trace!("read start len={}", buf.len()); + + poll_fn(|cx| { + let r = T::regs(); + let index = self.info.addr.index(); + let state = T::state(); + + state.ep_out_wakers[index].register(cx.waker()); + + let doepctl = r.doepctl(index).read(); + trace!("read ep={:?}: doepctl {:08x}", self.info.addr, doepctl.0,); + if !doepctl.usbaep() { + trace!("read ep={:?} error disabled", self.info.addr); + return Poll::Ready(Err(EndpointError::Disabled)); + } + + let len = state.ep_out_size[index].load(Ordering::Relaxed); + if len != EP_OUT_BUFFER_EMPTY { + trace!("read ep={:?} done len={}", self.info.addr, len); + + if len as usize > buf.len() { + return Poll::Ready(Err(EndpointError::BufferOverflow)); + } + + // SAFETY: exclusive access ensured by `ep_out_size` atomic variable + let data = unsafe { core::slice::from_raw_parts(*state.ep_out_buffers[index].get(), len as usize) }; + buf[..len as usize].copy_from_slice(data); + + // Release buffer + state.ep_out_size[index].store(EP_OUT_BUFFER_EMPTY, Ordering::Release); + + critical_section::with(|_| { + // Receive 1 packet + T::regs().doeptsiz(index).modify(|w| { + w.set_xfrsiz(self.info.max_packet_size as _); + w.set_pktcnt(1); + }); + + // Clear NAK to indicate we are ready to receive more data + T::regs().doepctl(index).modify(|w| { + w.set_cnak(true); + }); + }); + + Poll::Ready(Ok(len as usize)) + } else { + Poll::Pending + } + }) + .await + } +} + +impl<'d, T: Instance> embassy_usb_driver::EndpointIn for Endpoint<'d, T, In> { + async fn write(&mut self, buf: &[u8]) -> Result<(), EndpointError> { + trace!("write ep={:?} data={:?}", self.info.addr, buf); + + if buf.len() > self.info.max_packet_size as usize { + return Err(EndpointError::BufferOverflow); + } + + let r = T::regs(); + let index = self.info.addr.index(); + let state = T::state(); + + // Wait for previous transfer to complete and check if endpoint is disabled + poll_fn(|cx| { + state.ep_in_wakers[index].register(cx.waker()); + + let diepctl = r.diepctl(index).read(); + let dtxfsts = r.dtxfsts(index).read(); + trace!( + "write ep={:?}: diepctl {:08x} ftxfsts {:08x}", + self.info.addr, + diepctl.0, + dtxfsts.0 + ); + if !diepctl.usbaep() { + trace!("write ep={:?} wait for prev: error disabled", self.info.addr); + Poll::Ready(Err(EndpointError::Disabled)) + } else if !diepctl.epena() { + trace!("write ep={:?} wait for prev: ready", self.info.addr); + Poll::Ready(Ok(())) + } else { + trace!("write ep={:?} wait for prev: pending", self.info.addr); + Poll::Pending + } + }) + .await?; + + if buf.len() > 0 { + poll_fn(|cx| { + state.ep_in_wakers[index].register(cx.waker()); + + let size_words = (buf.len() + 3) / 4; + + let fifo_space = r.dtxfsts(index).read().ineptfsav() as usize; + if size_words > fifo_space { + // Not enough space in fifo, enable tx fifo empty interrupt + critical_section::with(|_| { + r.diepempmsk().modify(|w| { + w.set_ineptxfem(w.ineptxfem() | (1 << index)); + }); + }); + + trace!("tx fifo for ep={} full, waiting for txfe", index); + + Poll::Pending + } else { + trace!("write ep={:?} wait for fifo: ready", self.info.addr); + Poll::Ready(()) + } + }) + .await + } + + // Setup transfer size + r.dieptsiz(index).write(|w| { + w.set_mcnt(1); + w.set_pktcnt(1); + w.set_xfrsiz(buf.len() as _); + }); + + critical_section::with(|_| { + // Enable endpoint + r.diepctl(index).modify(|w| { + w.set_cnak(true); + w.set_epena(true); + }); + }); + + // Write data to FIFO + for chunk in buf.chunks(4) { + let mut tmp = [0u8; 4]; + tmp[0..chunk.len()].copy_from_slice(chunk); + r.fifo(index).write_value(regs::Fifo(u32::from_ne_bytes(tmp))); + } + + trace!("write done ep={:?}", self.info.addr); + + Ok(()) + } +} + +pub struct ControlPipe<'d, T: Instance> { + _phantom: PhantomData<&'d mut T>, + max_packet_size: u16, + ep_in: Endpoint<'d, T, In>, + ep_out: Endpoint<'d, T, Out>, +} + +impl<'d, T: Instance> embassy_usb_driver::ControlPipe for ControlPipe<'d, T> { + fn max_packet_size(&self) -> usize { + usize::from(self.max_packet_size) + } + + async fn setup(&mut self) -> [u8; 8] { + poll_fn(|cx| { + let state = T::state(); + + state.ep_out_wakers[0].register(cx.waker()); + + if state.ep0_setup_ready.load(Ordering::Relaxed) { + let data = unsafe { *state.ep0_setup_data.get() }; + state.ep0_setup_ready.store(false, Ordering::Release); + + // EP0 should not be controlled by `Bus` so this RMW does not need a critical section + // Receive 1 SETUP packet + T::regs().doeptsiz(self.ep_out.info.addr.index()).modify(|w| { + w.set_rxdpid_stupcnt(1); + }); + + // Clear NAK to indicate we are ready to receive more data + T::regs().doepctl(self.ep_out.info.addr.index()).modify(|w| { + w.set_cnak(true); + }); + + trace!("SETUP received: {:?}", data); + Poll::Ready(data) + } else { + trace!("SETUP waiting"); + Poll::Pending + } + }) + .await + } + + async fn data_out(&mut self, buf: &mut [u8], _first: bool, _last: bool) -> Result { + trace!("control: data_out"); + let len = self.ep_out.read(buf).await?; + trace!("control: data_out read: {:?}", &buf[..len]); + Ok(len) + } + + async fn data_in(&mut self, data: &[u8], _first: bool, last: bool) -> Result<(), EndpointError> { + trace!("control: data_in write: {:?}", data); + self.ep_in.write(data).await?; + + // wait for status response from host after sending the last packet + if last { + trace!("control: data_in waiting for status"); + self.ep_out.read(&mut []).await?; + trace!("control: complete"); + } + + Ok(()) + } + + async fn accept(&mut self) { + trace!("control: accept"); + + self.ep_in.write(&[]).await.ok(); + + trace!("control: accept OK"); + } + + async fn reject(&mut self) { + trace!("control: reject"); + + // EP0 should not be controlled by `Bus` so this RMW does not need a critical section + let regs = T::regs(); + regs.diepctl(self.ep_in.info.addr.index()).modify(|w| { + w.set_stall(true); + }); + regs.doepctl(self.ep_out.info.addr.index()).modify(|w| { + w.set_stall(true); + }); + } + + async fn accept_set_address(&mut self, addr: u8) { + trace!("setting addr: {}", addr); + critical_section::with(|_| { + T::regs().dcfg().modify(|w| { + w.set_dad(addr); + }); + }); + + // synopsys driver requires accept to be sent after changing address + self.accept().await + } +} + +/// Translates HAL [EndpointType] into PAC [vals::Eptyp] +fn to_eptyp(ep_type: EndpointType) -> vals::Eptyp { + match ep_type { + EndpointType::Control => vals::Eptyp::CONTROL, + EndpointType::Isochronous => vals::Eptyp::ISOCHRONOUS, + EndpointType::Bulk => vals::Eptyp::BULK, + EndpointType::Interrupt => vals::Eptyp::INTERRUPT, + } +} + +/// Calculates total allocated FIFO size in words +fn ep_fifo_size(eps: &[Option]) -> u16 { + eps.iter().map(|ep| ep.map(|ep| ep.fifo_size_words).unwrap_or(0)).sum() +} + +/// Generates IRQ mask for enabled endpoints +fn ep_irq_mask(eps: &[Option]) -> u16 { + eps.iter().enumerate().fold( + 0, + |mask, (index, ep)| { + if ep.is_some() { + mask | (1 << index) + } else { + mask + } + }, + ) +} + +/// Calculates MPSIZ value for EP0, which uses special values. +fn ep0_mpsiz(max_packet_size: u16) -> u16 { + match max_packet_size { + 8 => 0b11, + 16 => 0b10, + 32 => 0b01, + 64 => 0b00, + other => panic!("Unsupported EP0 size: {}", other), + } +} + +fn calculate_trdt(speed: vals::Dspd, ahb_freq: Hertz) -> u8 { + match speed { + vals::Dspd::HIGH_SPEED => { + // From RM0431 (F72xx), RM0090 (F429), RM0390 (F446) + if ahb_freq.0 >= 30_000_000 { + 0x9 + } else { + panic!("AHB frequency is too low") + } + } + vals::Dspd::FULL_SPEED_EXTERNAL | vals::Dspd::FULL_SPEED_INTERNAL => { + // From RM0431 (F72xx), RM0090 (F429) + match ahb_freq.0 { + 0..=14_199_999 => panic!("AHB frequency is too low"), + 14_200_000..=14_999_999 => 0xF, + 15_000_000..=15_999_999 => 0xE, + 16_000_000..=17_199_999 => 0xD, + 17_200_000..=18_499_999 => 0xC, + 18_500_000..=19_999_999 => 0xB, + 20_000_000..=21_799_999 => 0xA, + 21_800_000..=23_999_999 => 0x9, + 24_000_000..=27_499_999 => 0x8, + 27_500_000..=31_999_999 => 0x7, // 27.7..32 in code from CubeIDE + 32_000_000..=u32::MAX => 0x6, + } + } + _ => unimplemented!(), + } +} diff --git a/embassy-stm32/src/wdg/mod.rs b/embassy-stm32/src/wdg/mod.rs index 85176eefc..b03e81d6e 100644 --- a/embassy-stm32/src/wdg/mod.rs +++ b/embassy-stm32/src/wdg/mod.rs @@ -13,13 +13,13 @@ pub struct IndependentWatchdog<'d, T: Instance> { const MAX_RL: u16 = 0xFFF; /// Calculates maximum watchdog timeout in us (RL = 0xFFF) for a given prescaler -const fn max_timeout(prescaler: u8) -> u32 { - 1_000_000 * MAX_RL as u32 / (LSI_FREQ.0 / prescaler as u32) +const fn get_timeout_us(prescaler: u16, reload_value: u16) -> u32 { + 1_000_000 * (reload_value + 1) as u32 / (LSI_FREQ.0 / prescaler as u32) } /// Calculates watchdog reload value for the given prescaler and desired timeout -const fn reload_value(prescaler: u8, timeout_us: u32) -> u16 { - (timeout_us / prescaler as u32 * LSI_FREQ.0 / 1_000_000) as u16 +const fn reload_value(prescaler: u16, timeout_us: u32) -> u16 { + (timeout_us / prescaler as u32 * LSI_FREQ.0 / 1_000_000) as u16 - 1 } impl<'d, T: Instance> IndependentWatchdog<'d, T> { @@ -33,12 +33,12 @@ impl<'d, T: Instance> IndependentWatchdog<'d, T> { // Find lowest prescaler value, which makes watchdog period longer or equal to timeout. // This iterates from 4 (2^2) to 256 (2^8). let psc_power = unwrap!((2..=8).find(|psc_power| { - let psc = 2u8.pow(*psc_power); - timeout_us <= max_timeout(psc) + let psc = 2u16.pow(*psc_power); + timeout_us <= get_timeout_us(psc, MAX_RL) })); // Prescaler value - let psc = 2u8.pow(psc_power); + let psc = 2u16.pow(psc_power); // Convert prescaler power to PR register value let pr = psc_power as u8 - 2; @@ -48,22 +48,28 @@ impl<'d, T: Instance> IndependentWatchdog<'d, T> { let rl = reload_value(psc, timeout_us); let wdg = T::regs(); - unsafe { - wdg.kr().write(|w| w.set_key(Key::ENABLE)); - wdg.pr().write(|w| w.set_pr(Pr(pr))); - wdg.rlr().write(|w| w.set_rl(rl)); - } + wdg.kr().write(|w| w.set_key(Key::ENABLE)); + wdg.pr().write(|w| w.set_pr(Pr::from_bits(pr))); + wdg.rlr().write(|w| w.set_rl(rl)); + + trace!( + "Watchdog configured with {}us timeout, desired was {}us (PR={}, RL={})", + get_timeout_us(psc, rl), + timeout_us, + pr, + rl + ); IndependentWatchdog { wdg: PhantomData::default(), } } - pub unsafe fn unleash(&mut self) { + pub fn unleash(&mut self) { T::regs().kr().write(|w| w.set_key(Key::START)); } - pub unsafe fn pet(&mut self) { + pub fn pet(&mut self) { T::regs().kr().write(|w| w.set_key(Key::RESET)); } } @@ -87,3 +93,27 @@ foreach_peripheral!( impl Instance for crate::peripherals::$inst {} }; ); + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn can_compute_timeout_us() { + assert_eq!(125, get_timeout_us(4, 0)); + assert_eq!(512_000, get_timeout_us(4, MAX_RL)); + + assert_eq!(8_000, get_timeout_us(256, 0)); + assert_eq!(32768_000, get_timeout_us(256, MAX_RL)); + + assert_eq!(8000_000, get_timeout_us(64, 3999)); + } + + #[test] + fn can_compute_reload_value() { + assert_eq!(0xFFF, reload_value(4, 512_000)); + assert_eq!(0xFFF, reload_value(256, 32768_000)); + + assert_eq!(3999, reload_value(64, 8000_000)); + } +} diff --git a/embassy-sync/CHANGELOG.md b/embassy-sync/CHANGELOG.md new file mode 100644 index 000000000..a60f3f7c4 --- /dev/null +++ b/embassy-sync/CHANGELOG.md @@ -0,0 +1,22 @@ +# Changelog + +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). + +## 0.2.0 - 2023-04-13 + +- pubsub: Fix messages not getting popped when the last subscriber that needed them gets dropped. +- pubsub: Move instead of clone messages when the last subscriber pops them. +- pubsub: Pop messages which count is 0 after unsubscribe. +- Update `embedded-io` from `0.3` to `0.4` (uses `async fn` in traits). +- impl `Default` for `WakerRegistration` +- impl `Default` for `Signal` +- Remove unnecessary uses of `atomic-polyfill` +- Add `#[must_use]` to all futures. + + +## 0.1.0 - 2022-08-26 + +- First release \ No newline at end of file diff --git a/embassy-sync/Cargo.toml b/embassy-sync/Cargo.toml index 0d14bba55..340724eab 100644 --- a/embassy-sync/Cargo.toml +++ b/embassy-sync/Cargo.toml @@ -1,7 +1,17 @@ [package] name = "embassy-sync" -version = "0.1.0" +version = "0.2.0" edition = "2021" +description = "no-std, no-alloc synchronization primitives with async support" +repository = "https://github.com/embassy-rs/embassy" +readme = "README.md" +license = "MIT OR Apache-2.0" +categories = [ + "embedded", + "no-std", + "concurrency", + "asynchronous", +] [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-sync-v$VERSION/embassy-sync/src/" @@ -9,19 +19,23 @@ src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-sync/ features = ["nightly"] target = "thumbv7em-none-eabi" +[package.metadata.docs.rs] +features = ["nightly"] + [features] nightly = ["embedded-io/async"] +std = [] +turbowakers = [] [dependencies] defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } futures-util = { version = "0.3.17", default-features = false } -atomic-polyfill = "1.0.1" critical-section = "1.1" heapless = "0.7.5" cfg-if = "1.0.0" -embedded-io = "0.3.0" +embedded-io = "0.4.0" [dev-dependencies] futures-executor = { version = "0.3.17", features = [ "thread-pool" ] } @@ -31,4 +45,4 @@ futures-util = { version = "0.3.17", features = [ "channel" ] } # Enable critical-section implementation for std, for tests critical-section = { version = "1.1", features = ["std"] } -static_cell = "1.0" +static_cell = "1.1" diff --git a/embassy-sync/README.md b/embassy-sync/README.md index 106295c0d..cc65cf6ef 100644 --- a/embassy-sync/README.md +++ b/embassy-sync/README.md @@ -1,12 +1,32 @@ # embassy-sync -Synchronization primitives and data structures with an async API: +An [Embassy](https://embassy.dev) project. + +Synchronization primitives and data structures with async support: - [`Channel`](channel::Channel) - A Multiple Producer Multiple Consumer (MPMC) channel. Each message is only received by a single consumer. - [`PubSubChannel`](pubsub::PubSubChannel) - A broadcast channel (publish-subscribe) channel. Each message is received by all consumers. - [`Signal`](signal::Signal) - Signalling latest value to a single consumer. -- [`Mutex`](mutex::Mutex) - A Mutex for synchronizing state between asynchronous tasks. +- [`Mutex`](mutex::Mutex) - Mutex for synchronizing state between asynchronous tasks. - [`Pipe`](pipe::Pipe) - Byte stream implementing `embedded_io` traits. - [`WakerRegistration`](waitqueue::WakerRegistration) - Utility to register and wake a `Waker`. - [`AtomicWaker`](waitqueue::AtomicWaker) - A variant of `WakerRegistration` accessible using a non-mut API. - [`MultiWakerRegistration`](waitqueue::MultiWakerRegistration) - Utility registering and waking multiple `Waker`'s. + +## Interoperability + +Futures from this crate can run on any executor. + +## Minimum supported Rust version (MSRV) + +Embassy is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release. + +## License + +This work is licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. diff --git a/embassy-sync/src/channel.rs b/embassy-sync/src/channel.rs index 76f42d0e7..77352874d 100644 --- a/embassy-sync/src/channel.rs +++ b/embassy-sync/src/channel.rs @@ -181,6 +181,7 @@ where } /// Future returned by [`Channel::recv`] and [`Receiver::recv`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] pub struct RecvFuture<'ch, M, T, const N: usize> where M: RawMutex, @@ -203,6 +204,7 @@ where } /// Future returned by [`DynamicReceiver::recv`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] pub struct DynamicRecvFuture<'ch, T> { channel: &'ch dyn DynamicChannel, } @@ -219,6 +221,7 @@ impl<'ch, T> Future for DynamicRecvFuture<'ch, T> { } /// Future returned by [`Channel::send`] and [`Sender::send`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] pub struct SendFuture<'ch, M, T, const N: usize> where M: RawMutex, @@ -250,6 +253,7 @@ where impl<'ch, M, T, const N: usize> Unpin for SendFuture<'ch, M, T, N> where M: RawMutex {} /// Future returned by [`DynamicSender::send`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] pub struct DynamicSendFuture<'ch, T> { channel: &'ch dyn DynamicChannel, message: Option, diff --git a/embassy-sync/src/fmt.rs b/embassy-sync/src/fmt.rs index f8bb0a035..066970813 100644 --- a/embassy-sync/src/fmt.rs +++ b/embassy-sync/src/fmt.rs @@ -195,9 +195,6 @@ macro_rules! unwrap { } } -#[cfg(feature = "defmt-timestamp-uptime")] -defmt::timestamp! {"{=u64:us}", crate::time::Instant::now().as_micros() } - #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct NoneError; diff --git a/embassy-sync/src/lib.rs b/embassy-sync/src/lib.rs index 25150e8aa..53d95d081 100644 --- a/embassy-sync/src/lib.rs +++ b/embassy-sync/src/lib.rs @@ -1,5 +1,5 @@ #![cfg_attr(not(any(feature = "std", feature = "wasm")), no_std)] -#![cfg_attr(feature = "nightly", feature(generic_associated_types, type_alias_impl_trait))] +#![cfg_attr(feature = "nightly", feature(async_fn_in_trait, impl_trait_projections))] #![allow(clippy::new_without_default)] #![doc = include_str!("../README.md")] #![warn(missing_docs)] diff --git a/embassy-sync/src/mutex.rs b/embassy-sync/src/mutex.rs index 75a6e8dd3..fcf056d36 100644 --- a/embassy-sync/src/mutex.rs +++ b/embassy-sync/src/mutex.rs @@ -2,11 +2,10 @@ //! //! This module provides a mutex that can be used to synchronize data between asynchronous tasks. use core::cell::{RefCell, UnsafeCell}; +use core::future::poll_fn; use core::ops::{Deref, DerefMut}; use core::task::Poll; -use futures_util::future::poll_fn; - use crate::blocking_mutex::raw::RawMutex; use crate::blocking_mutex::Mutex as BlockingMutex; use crate::waitqueue::WakerRegistration; @@ -111,6 +110,22 @@ where Ok(MutexGuard { mutex: self }) } + + /// Consumes this mutex, returning the underlying data. + pub fn into_inner(self) -> T + where + T: Sized, + { + self.inner.into_inner() + } + + /// Returns a mutable reference to the underlying data. + /// + /// Since this call borrows the Mutex mutably, no actual locking needs to + /// take place -- the mutable borrow statically guarantees no locks exist. + pub fn get_mut(&mut self) -> &mut T { + self.inner.get_mut() + } } /// Async mutex guard. diff --git a/embassy-sync/src/pipe.rs b/embassy-sync/src/pipe.rs index 7d64b648e..13bf4ef01 100644 --- a/embassy-sync/src/pipe.rs +++ b/embassy-sync/src/pipe.rs @@ -32,22 +32,23 @@ impl<'p, M, const N: usize> Writer<'p, M, N> where M: RawMutex, { - /// Writes a value. + /// Write some bytes to the pipe. /// /// See [`Pipe::write()`] pub fn write<'a>(&'a self, buf: &'a [u8]) -> WriteFuture<'a, M, N> { self.pipe.write(buf) } - /// Attempt to immediately write a message. + /// Attempt to immediately write some bytes to the pipe. /// - /// See [`Pipe::write()`] + /// See [`Pipe::try_write()`] pub fn try_write(&self, buf: &[u8]) -> Result { self.pipe.try_write(buf) } } /// Future returned by [`Pipe::write`] and [`Writer::write`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] pub struct WriteFuture<'p, M, const N: usize> where M: RawMutex, @@ -94,22 +95,23 @@ impl<'p, M, const N: usize> Reader<'p, M, N> where M: RawMutex, { - /// Reads a value. + /// Read some bytes from the pipe. /// /// See [`Pipe::read()`] pub fn read<'a>(&'a self, buf: &'a mut [u8]) -> ReadFuture<'a, M, N> { self.pipe.read(buf) } - /// Attempt to immediately read a message. + /// Attempt to immediately read some bytes from the pipe. /// - /// See [`Pipe::read()`] + /// See [`Pipe::try_read()`] pub fn try_read(&self, buf: &mut [u8]) -> Result { self.pipe.try_read(buf) } } /// Future returned by [`Pipe::read`] and [`Reader::read`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] pub struct ReadFuture<'p, M, const N: usize> where M: RawMutex, @@ -219,12 +221,11 @@ impl PipeState { } } -/// A bounded pipe for communicating between asynchronous tasks +/// A bounded byte-oriented pipe for communicating between asynchronous tasks /// with backpressure. /// -/// The pipe will buffer up to the provided number of messages. Once the -/// buffer is full, attempts to `write` new messages will wait until a message is -/// read from the pipe. +/// The pipe will buffer up to the provided number of bytes. Once the +/// buffer is full, attempts to `write` new bytes will wait until buffer space is freed up. /// /// All data written will become available in the same order as it was written. pub struct Pipe @@ -275,40 +276,66 @@ where Reader { pipe: self } } - /// Write a value, waiting until there is capacity. + /// Write some bytes to the pipe. /// - /// Writeing completes when the value has been pushed to the pipe's queue. - /// This doesn't mean the value has been read yet. + /// This method writes a nonzero amount of bytes from `buf` into the pipe, and + /// returns the amount of bytes written. + /// + /// If it is not possible to write a nonzero amount of bytes because the pipe's buffer is full, + /// this method will wait until it isn't. See [`try_write`](Self::try_write) for a variant that + /// returns an error instead of waiting. + /// + /// It is not guaranteed that all bytes in the buffer are written, even if there's enough + /// free space in the pipe buffer for all. In other words, it is possible for `write` to return + /// without writing all of `buf` (returning a number less than `buf.len()`) and still leave + /// free space in the pipe buffer. You should always `write` in a loop, or use helpers like + /// `write_all` from the `embedded-io` crate. pub fn write<'a>(&'a self, buf: &'a [u8]) -> WriteFuture<'a, M, N> { WriteFuture { pipe: self, buf } } - /// Attempt to immediately write a message. + /// Write all bytes to the pipe. /// - /// This method differs from [`write`](Pipe::write) by returning immediately if the pipe's - /// buffer is full, instead of waiting. + /// This method writes all bytes from `buf` into the pipe + pub async fn write_all(&self, mut buf: &[u8]) { + while !buf.is_empty() { + let n = self.write(buf).await; + buf = &buf[n..]; + } + } + + /// Attempt to immediately write some bytes to the pipe. /// - /// # Errors - /// - /// If the pipe capacity has been reached, i.e., the pipe has `n` - /// buffered values where `n` is the argument passed to [`Pipe`], then an - /// error is returned. + /// This method will either write a nonzero amount of bytes to the pipe immediately, + /// or return an error if the pipe is empty. See [`write`](Self::write) for a variant + /// that waits instead of returning an error. pub fn try_write(&self, buf: &[u8]) -> Result { self.lock(|c| c.try_write(buf)) } - /// Receive the next value. + /// Read some bytes from the pipe. /// - /// If there are no messages in the pipe's buffer, this method will - /// wait until a message is written. + /// This method reads a nonzero amount of bytes from the pipe into `buf` and + /// returns the amount of bytes read. + /// + /// If it is not possible to read a nonzero amount of bytes because the pipe's buffer is empty, + /// this method will wait until it isn't. See [`try_read`](Self::try_read) for a variant that + /// returns an error instead of waiting. + /// + /// It is not guaranteed that all bytes in the buffer are read, even if there's enough + /// space in `buf` for all. In other words, it is possible for `read` to return + /// without filling `buf` (returning a number less than `buf.len()`) and still leave bytes + /// in the pipe buffer. You should always `read` in a loop, or use helpers like + /// `read_exact` from the `embedded-io` crate. pub fn read<'a>(&'a self, buf: &'a mut [u8]) -> ReadFuture<'a, M, N> { ReadFuture { pipe: self, buf } } - /// Attempt to immediately read a message. + /// Attempt to immediately read some bytes from the pipe. /// - /// This method will either read a message from the pipe immediately or return an error - /// if the pipe is empty. + /// This method will either read a nonzero amount of bytes from the pipe immediately, + /// or return an error if the pipe is empty. See [`read`](Self::read) for a variant + /// that waits instead of returning an error. pub fn try_read(&self, buf: &mut [u8]) -> Result { self.lock(|c| c.try_read(buf)) } @@ -352,8 +379,6 @@ where mod io_impls { use core::convert::Infallible; - use futures_util::FutureExt; - use super::*; impl embedded_io::Io for Pipe { @@ -361,30 +386,18 @@ mod io_impls { } impl embedded_io::asynch::Read for Pipe { - type ReadFuture<'a> = impl Future> - where - Self: 'a; - - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - Pipe::read(self, buf).map(Ok) + async fn read(&mut self, buf: &mut [u8]) -> Result { + Ok(Pipe::read(self, buf).await) } } impl embedded_io::asynch::Write for Pipe { - type WriteFuture<'a> = impl Future> - where - Self: 'a; - - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { - Pipe::write(self, buf).map(Ok) + async fn write(&mut self, buf: &[u8]) -> Result { + Ok(Pipe::write(self, buf).await) } - type FlushFuture<'a> = impl Future> - where - Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - futures_util::future::ready(Ok(())) + async fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) } } @@ -393,30 +406,18 @@ mod io_impls { } impl embedded_io::asynch::Read for &Pipe { - type ReadFuture<'a> = impl Future> - where - Self: 'a; - - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - Pipe::read(self, buf).map(Ok) + async fn read(&mut self, buf: &mut [u8]) -> Result { + Ok(Pipe::read(self, buf).await) } } impl embedded_io::asynch::Write for &Pipe { - type WriteFuture<'a> = impl Future> - where - Self: 'a; - - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { - Pipe::write(self, buf).map(Ok) + async fn write(&mut self, buf: &[u8]) -> Result { + Ok(Pipe::write(self, buf).await) } - type FlushFuture<'a> = impl Future> - where - Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - futures_util::future::ready(Ok(())) + async fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) } } @@ -425,12 +426,8 @@ mod io_impls { } impl embedded_io::asynch::Read for Reader<'_, M, N> { - type ReadFuture<'a> = impl Future> - where - Self: 'a; - - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { - Reader::read(self, buf).map(Ok) + async fn read(&mut self, buf: &mut [u8]) -> Result { + Ok(Reader::read(self, buf).await) } } @@ -439,20 +436,12 @@ mod io_impls { } impl embedded_io::asynch::Write for Writer<'_, M, N> { - type WriteFuture<'a> = impl Future> - where - Self: 'a; - - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a> { - Writer::write(self, buf).map(Ok) + async fn write(&mut self, buf: &[u8]) -> Result { + Ok(Writer::write(self, buf).await) } - type FlushFuture<'a> = impl Future> - where - Self: 'a; - - fn flush<'a>(&'a mut self) -> Self::FlushFuture<'a> { - futures_util::future::ready(Ok(())) + async fn flush(&mut self) -> Result<(), Self::Error> { + Ok(()) } } } diff --git a/embassy-sync/src/pubsub/mod.rs b/embassy-sync/src/pubsub/mod.rs index 62a9e4763..6afd54af5 100644 --- a/embassy-sync/src/pubsub/mod.rs +++ b/embassy-sync/src/pubsub/mod.rs @@ -4,7 +4,7 @@ use core::cell::RefCell; use core::fmt::Debug; -use core::task::{Context, Poll, Waker}; +use core::task::{Context, Poll}; use heapless::Deque; @@ -179,7 +179,7 @@ impl { if let Some(cx) = cx { - s.register_subscriber_waker(cx.waker()); + s.subscriber_wakers.register(cx.waker()); } Poll::Pending } @@ -192,6 +192,10 @@ impl u64 { + self.inner.lock(|s| s.borrow().next_message_id - next_message_id) + } + fn publish_with_context(&self, message: T, cx: Option<&mut Context<'_>>) -> Result<(), T> { self.inner.lock(|s| { let mut s = s.borrow_mut(); @@ -202,7 +206,7 @@ impl { if let Some(cx) = cx { - s.register_publisher_waker(cx.waker()); + s.publisher_wakers.register(cx.waker()); } Err(message) } @@ -217,6 +221,13 @@ impl usize { + self.inner.lock(|s| { + let s = s.borrow(); + s.queue.capacity() - s.queue.len() + }) + } + fn unregister_subscriber(&self, subscriber_next_message_id: u64) { self.inner.lock(|s| { let mut s = s.borrow_mut(); @@ -311,44 +322,19 @@ impl PubSubSta // We're reading this item, so decrement the counter queue_item.1 -= 1; - let message = queue_item.0.clone(); - if current_message_index == 0 && queue_item.1 == 0 { - self.queue.pop_front(); + let message = if current_message_index == 0 && queue_item.1 == 0 { + let (message, _) = self.queue.pop_front().unwrap(); self.publisher_wakers.wake(); - } + // Return pop'd message without clone + message + } else { + queue_item.0.clone() + }; Some(WaitResult::Message(message)) } - fn register_subscriber_waker(&mut self, waker: &Waker) { - match self.subscriber_wakers.register(waker) { - Ok(()) => {} - Err(_) => { - // All waker slots were full. This can only happen when there was a subscriber that now has dropped. - // We need to throw it away. It's a bit inefficient, but we can wake everything. - // Any future that is still active will simply reregister. - // This won't happen a lot, so it's ok. - self.subscriber_wakers.wake(); - self.subscriber_wakers.register(waker).unwrap(); - } - } - } - - fn register_publisher_waker(&mut self, waker: &Waker) { - match self.publisher_wakers.register(waker) { - Ok(()) => {} - Err(_) => { - // All waker slots were full. This can only happen when there was a publisher that now has dropped. - // We need to throw it away. It's a bit inefficient, but we can wake everything. - // Any future that is still active will simply reregister. - // This won't happen a lot, so it's ok. - self.publisher_wakers.wake(); - self.publisher_wakers.register(waker).unwrap(); - } - } - } - fn unregister_subscriber(&mut self, subscriber_next_message_id: u64) { self.subscriber_count -= 1; @@ -360,6 +346,20 @@ impl PubSubSta .iter_mut() .skip(current_message_index) .for_each(|(_, counter)| *counter -= 1); + + let mut wake_publishers = false; + while let Some((_, count)) = self.queue.front() { + if *count == 0 { + self.queue.pop_front().unwrap(); + wake_publishers = true; + } else { + break; + } + } + + if wake_publishers { + self.publisher_wakers.wake(); + } } } @@ -388,6 +388,10 @@ pub trait PubSubBehavior { /// If the message is not yet present and a context is given, then its waker is registered in the subsriber wakers. fn get_message_with_context(&self, next_message_id: &mut u64, cx: Option<&mut Context<'_>>) -> Poll>; + /// Get the amount of messages that are between the given the next_message_id and the most recent message. + /// This is not necessarily the amount of messages a subscriber can still received as it may have lagged. + fn available(&self, next_message_id: u64) -> u64; + /// Try to publish a message to the queue. /// /// If the queue is full and a context is given, then its waker is registered in the publisher wakers. @@ -396,6 +400,9 @@ pub trait PubSubBehavior { /// Publish a message immediately fn publish_immediate(&self, message: T); + /// The amount of messages that can still be published without having to wait or without having to lag the subscribers + fn space(&self) -> usize; + /// Let the channel know that a subscriber has dropped fn unregister_subscriber(&self, subscriber_next_message_id: u64); @@ -539,4 +546,113 @@ mod tests { drop(sub0); } + + #[futures_test::test] + async fn correct_available() { + let channel = PubSubChannel::::new(); + + let sub0 = channel.subscriber().unwrap(); + let mut sub1 = channel.subscriber().unwrap(); + let pub0 = channel.publisher().unwrap(); + + assert_eq!(sub0.available(), 0); + assert_eq!(sub1.available(), 0); + + pub0.publish(42).await; + + assert_eq!(sub0.available(), 1); + assert_eq!(sub1.available(), 1); + + sub1.next_message().await; + + assert_eq!(sub1.available(), 0); + + pub0.publish(42).await; + + assert_eq!(sub0.available(), 2); + assert_eq!(sub1.available(), 1); + } + + #[futures_test::test] + async fn correct_space() { + let channel = PubSubChannel::::new(); + + let mut sub0 = channel.subscriber().unwrap(); + let mut sub1 = channel.subscriber().unwrap(); + let pub0 = channel.publisher().unwrap(); + + assert_eq!(pub0.space(), 4); + + pub0.publish(42).await; + + assert_eq!(pub0.space(), 3); + + pub0.publish(42).await; + + assert_eq!(pub0.space(), 2); + + sub0.next_message().await; + sub0.next_message().await; + + assert_eq!(pub0.space(), 2); + + sub1.next_message().await; + assert_eq!(pub0.space(), 3); + sub1.next_message().await; + assert_eq!(pub0.space(), 4); + } + + #[futures_test::test] + async fn empty_channel_when_last_subscriber_is_dropped() { + let channel = PubSubChannel::::new(); + + let pub0 = channel.publisher().unwrap(); + let mut sub0 = channel.subscriber().unwrap(); + let mut sub1 = channel.subscriber().unwrap(); + + assert_eq!(4, pub0.space()); + + pub0.publish(1).await; + pub0.publish(2).await; + + assert_eq!(2, channel.space()); + + assert_eq!(1, sub0.try_next_message_pure().unwrap()); + assert_eq!(2, sub0.try_next_message_pure().unwrap()); + + assert_eq!(2, channel.space()); + + drop(sub0); + + assert_eq!(2, channel.space()); + + assert_eq!(1, sub1.try_next_message_pure().unwrap()); + + assert_eq!(3, channel.space()); + + drop(sub1); + + assert_eq!(4, channel.space()); + } + + struct CloneCallCounter(usize); + + impl Clone for CloneCallCounter { + fn clone(&self) -> Self { + Self(self.0 + 1) + } + } + + #[futures_test::test] + async fn skip_clone_for_last_message() { + let channel = PubSubChannel::::new(); + let pub0 = channel.publisher().unwrap(); + let mut sub0 = channel.subscriber().unwrap(); + let mut sub1 = channel.subscriber().unwrap(); + + pub0.publish(CloneCallCounter(0)).await; + + assert_eq!(1, sub0.try_next_message_pure().unwrap().0); + assert_eq!(0, sub1.try_next_message_pure().unwrap().0); + } } diff --git a/embassy-sync/src/pubsub/publisher.rs b/embassy-sync/src/pubsub/publisher.rs index 705797f60..e1edc9eb9 100644 --- a/embassy-sync/src/pubsub/publisher.rs +++ b/embassy-sync/src/pubsub/publisher.rs @@ -42,6 +42,14 @@ impl<'a, PSB: PubSubBehavior + ?Sized, T: Clone> Pub<'a, PSB, T> { pub fn try_publish(&self, message: T) -> Result<(), T> { self.channel.publish_with_context(message, None) } + + /// The amount of messages that can still be published without having to wait or without having to lag the subscribers + /// + /// *Note: In the time between checking this and a publish action, other publishers may have had time to publish something. + /// So checking doesn't give any guarantees.* + pub fn space(&self) -> usize { + self.channel.space() + } } impl<'a, PSB: PubSubBehavior + ?Sized, T: Clone> Drop for Pub<'a, PSB, T> { @@ -115,6 +123,14 @@ impl<'a, PSB: PubSubBehavior + ?Sized, T: Clone> ImmediatePub<'a, PSB, T> { pub fn try_publish(&self, message: T) -> Result<(), T> { self.channel.publish_with_context(message, None) } + + /// The amount of messages that can still be published without having to wait or without having to lag the subscribers + /// + /// *Note: In the time between checking this and a publish action, other publishers may have had time to publish something. + /// So checking doesn't give any guarantees.* + pub fn space(&self) -> usize { + self.channel.space() + } } /// An immediate publisher that holds a dynamic reference to the channel @@ -158,6 +174,7 @@ impl<'a, M: RawMutex, T: Clone, const CAP: usize, const SUBS: usize, const PUBS: } /// Future for the publisher wait action +#[must_use = "futures do nothing unless you `.await` or poll them"] pub struct PublisherWaitFuture<'s, 'a, PSB: PubSubBehavior + ?Sized, T: Clone> { /// The message we need to publish message: Option, diff --git a/embassy-sync/src/pubsub/subscriber.rs b/embassy-sync/src/pubsub/subscriber.rs index b9a2cbe18..f420a75f0 100644 --- a/embassy-sync/src/pubsub/subscriber.rs +++ b/embassy-sync/src/pubsub/subscriber.rs @@ -64,6 +64,11 @@ impl<'a, PSB: PubSubBehavior + ?Sized, T: Clone> Sub<'a, PSB, T> { } } } + + /// The amount of messages this subscriber hasn't received yet + pub fn available(&self) -> u64 { + self.channel.available(self.next_message_id) + } } impl<'a, PSB: PubSubBehavior + ?Sized, T: Clone> Drop for Sub<'a, PSB, T> { @@ -135,6 +140,7 @@ impl<'a, M: RawMutex, T: Clone, const CAP: usize, const SUBS: usize, const PUBS: } /// Future for the subscriber wait action +#[must_use = "futures do nothing unless you `.await` or poll them"] pub struct SubscriberWaitFuture<'s, 'a, PSB: PubSubBehavior + ?Sized, T: Clone> { subscriber: &'s mut Sub<'a, PSB, T>, } diff --git a/embassy-sync/src/signal.rs b/embassy-sync/src/signal.rs index f6ebeb9b9..bea67d8be 100644 --- a/embassy-sync/src/signal.rs +++ b/embassy-sync/src/signal.rs @@ -1,9 +1,11 @@ //! A synchronization primitive for passing the latest value to a task. -use core::cell::UnsafeCell; -use core::future::Future; -use core::mem; +use core::cell::Cell; +use core::future::{poll_fn, Future}; use core::task::{Context, Poll, Waker}; +use crate::blocking_mutex::raw::RawMutex; +use crate::blocking_mutex::Mutex; + /// Single-slot signaling primitive. /// /// This is similar to a [`Channel`](crate::channel::Channel) with a buffer size of 1, except @@ -20,16 +22,20 @@ use core::task::{Context, Poll, Waker}; /// /// ``` /// use embassy_sync::signal::Signal; +/// use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; /// /// enum SomeCommand { /// On, /// Off, /// } /// -/// static SOME_SIGNAL: Signal = Signal::new(); +/// static SOME_SIGNAL: Signal = Signal::new(); /// ``` -pub struct Signal { - state: UnsafeCell>, +pub struct Signal +where + M: RawMutex, +{ + state: Mutex>>, } enum State { @@ -38,24 +44,36 @@ enum State { Signaled(T), } -unsafe impl Send for Signal {} -unsafe impl Sync for Signal {} - -impl Signal { +impl Signal +where + M: RawMutex, +{ /// Create a new `Signal`. pub const fn new() -> Self { Self { - state: UnsafeCell::new(State::None), + state: Mutex::new(Cell::new(State::None)), } } } -impl Signal { +impl Default for Signal +where + M: RawMutex, +{ + fn default() -> Self { + Self::new() + } +} + +impl Signal +where + M: RawMutex, +{ /// Mark this Signal as signaled. pub fn signal(&self, val: T) { - critical_section::with(|_| unsafe { - let state = &mut *self.state.get(); - if let State::Waiting(waker) = mem::replace(state, State::Signaled(val)) { + self.state.lock(|cell| { + let state = cell.replace(State::Signaled(val)); + if let State::Waiting(waker) = state { waker.wake(); } }) @@ -63,38 +81,46 @@ impl Signal { /// Remove the queued value in this `Signal`, if any. pub fn reset(&self) { - critical_section::with(|_| unsafe { - let state = &mut *self.state.get(); - *state = State::None - }) + self.state.lock(|cell| cell.set(State::None)); } - /// Manually poll the Signal future. - pub fn poll_wait(&self, cx: &mut Context<'_>) -> Poll { - critical_section::with(|_| unsafe { - let state = &mut *self.state.get(); + fn poll_wait(&self, cx: &mut Context<'_>) -> Poll { + self.state.lock(|cell| { + let state = cell.replace(State::None); match state { State::None => { - *state = State::Waiting(cx.waker().clone()); + cell.set(State::Waiting(cx.waker().clone())); Poll::Pending } - State::Waiting(w) if w.will_wake(cx.waker()) => Poll::Pending, - State::Waiting(_) => panic!("waker overflow"), - State::Signaled(_) => match mem::replace(state, State::None) { - State::Signaled(res) => Poll::Ready(res), - _ => unreachable!(), - }, + State::Waiting(w) if w.will_wake(cx.waker()) => { + cell.set(State::Waiting(w)); + Poll::Pending + } + State::Waiting(w) => { + cell.set(State::Waiting(cx.waker().clone())); + w.wake(); + Poll::Pending + } + State::Signaled(res) => Poll::Ready(res), } }) } /// Future that completes when this Signal has been signaled. pub fn wait(&self) -> impl Future + '_ { - futures_util::future::poll_fn(move |cx| self.poll_wait(cx)) + poll_fn(move |cx| self.poll_wait(cx)) } /// non-blocking method to check whether this signal has been signaled. pub fn signaled(&self) -> bool { - critical_section::with(|_| matches!(unsafe { &*self.state.get() }, State::Signaled(_))) + self.state.lock(|cell| { + let state = cell.replace(State::None); + + let res = matches!(state, State::Signaled(_)); + + cell.set(state); + + res + }) } } diff --git a/embassy-sync/src/waitqueue/atomic_waker.rs b/embassy-sync/src/waitqueue/atomic_waker.rs new file mode 100644 index 000000000..63fe04a6e --- /dev/null +++ b/embassy-sync/src/waitqueue/atomic_waker.rs @@ -0,0 +1,41 @@ +use core::cell::Cell; +use core::task::Waker; + +use crate::blocking_mutex::raw::CriticalSectionRawMutex; +use crate::blocking_mutex::Mutex; + +/// Utility struct to register and wake a waker. +pub struct AtomicWaker { + waker: Mutex>>, +} + +impl AtomicWaker { + /// Create a new `AtomicWaker`. + pub const fn new() -> Self { + Self { + waker: Mutex::const_new(CriticalSectionRawMutex::new(), Cell::new(None)), + } + } + + /// Register a waker. Overwrites the previous waker, if any. + pub fn register(&self, w: &Waker) { + critical_section::with(|cs| { + let cell = self.waker.borrow(cs); + cell.set(match cell.replace(None) { + Some(w2) if (w2.will_wake(w)) => Some(w2), + _ => Some(w.clone()), + }) + }) + } + + /// Wake the registered waker, if any. + pub fn wake(&self) { + critical_section::with(|cs| { + let cell = self.waker.borrow(cs); + if let Some(w) = cell.replace(None) { + w.wake_by_ref(); + cell.set(Some(w)); + } + }) + } +} diff --git a/embassy-sync/src/waitqueue/atomic_waker_turbo.rs b/embassy-sync/src/waitqueue/atomic_waker_turbo.rs new file mode 100644 index 000000000..5c6a96ec8 --- /dev/null +++ b/embassy-sync/src/waitqueue/atomic_waker_turbo.rs @@ -0,0 +1,30 @@ +use core::ptr; +use core::ptr::NonNull; +use core::sync::atomic::{AtomicPtr, Ordering}; +use core::task::Waker; + +/// Utility struct to register and wake a waker. +pub struct AtomicWaker { + waker: AtomicPtr<()>, +} + +impl AtomicWaker { + /// Create a new `AtomicWaker`. + pub const fn new() -> Self { + Self { + waker: AtomicPtr::new(ptr::null_mut()), + } + } + + /// Register a waker. Overwrites the previous waker, if any. + pub fn register(&self, w: &Waker) { + self.waker.store(w.as_turbo_ptr().as_ptr() as _, Ordering::Release); + } + + /// Wake the registered waker, if any. + pub fn wake(&self) { + if let Some(ptr) = NonNull::new(self.waker.load(Ordering::Acquire)) { + unsafe { Waker::from_turbo_ptr(ptr) }.wake(); + } + } +} diff --git a/embassy-sync/src/waitqueue/mod.rs b/embassy-sync/src/waitqueue/mod.rs index 6661a6b61..6b0b0c64e 100644 --- a/embassy-sync/src/waitqueue/mod.rs +++ b/embassy-sync/src/waitqueue/mod.rs @@ -1,7 +1,11 @@ //! Async low-level wait queues -mod waker; -pub use waker::*; +#[cfg_attr(feature = "turbowakers", path = "atomic_waker_turbo.rs")] +mod atomic_waker; +pub use atomic_waker::*; + +mod waker_registration; +pub use waker_registration::*; mod multi_waker; pub use multi_waker::*; diff --git a/embassy-sync/src/waitqueue/multi_waker.rs b/embassy-sync/src/waitqueue/multi_waker.rs index 325d2cb3a..824d192da 100644 --- a/embassy-sync/src/waitqueue/multi_waker.rs +++ b/embassy-sync/src/waitqueue/multi_waker.rs @@ -1,33 +1,58 @@ use core::task::Waker; -use super::WakerRegistration; +use heapless::Vec; /// Utility struct to register and wake multiple wakers. pub struct MultiWakerRegistration { - wakers: [WakerRegistration; N], + wakers: Vec, } impl MultiWakerRegistration { /// Create a new empty instance pub const fn new() -> Self { - const WAKER: WakerRegistration = WakerRegistration::new(); - Self { wakers: [WAKER; N] } + Self { wakers: Vec::new() } } /// Register a waker. If the buffer is full the function returns it in the error - pub fn register<'a>(&mut self, w: &'a Waker) -> Result<(), &'a Waker> { - if let Some(waker_slot) = self.wakers.iter_mut().find(|waker_slot| !waker_slot.occupied()) { - waker_slot.register(w); - Ok(()) - } else { - Err(w) + pub fn register<'a>(&mut self, w: &'a Waker) { + // If we already have some waker that wakes the same task as `w`, do nothing. + // This avoids cloning wakers, and avoids unnecessary mass-wakes. + for w2 in &self.wakers { + if w.will_wake(w2) { + return; + } + } + + if self.wakers.is_full() { + // All waker slots were full. It's a bit inefficient, but we can wake everything. + // Any future that is still active will simply reregister. + // This won't happen a lot, so it's ok. + self.wake(); + } + + if self.wakers.push(w.clone()).is_err() { + // This can't happen unless N=0 + // (Either `wakers` wasn't full, or it was in which case `wake()` empied it) + panic!("tried to push a waker to a zero-length MultiWakerRegistration") } } /// Wake all registered wakers. This clears the buffer pub fn wake(&mut self) { - for waker_slot in self.wakers.iter_mut() { - waker_slot.wake() + // heapless::Vec has no `drain()`, do it unsafely ourselves... + + // First set length to 0, without dropping the contents. + // This is necessary for soundness: if wake() panics and we're using panic=unwind. + // Setting len=0 upfront ensures other code can't observe the vec in an inconsistent state. + // (it'll leak wakers, but that's not UB) + let len = self.wakers.len(); + unsafe { self.wakers.set_len(0) } + + for i in 0..len { + // Move a waker out of the vec. + let waker = unsafe { self.wakers.as_mut_ptr().add(i).read() }; + // Wake it by value, which consumes (drops) it. + waker.wake(); } } } diff --git a/embassy-sync/src/waitqueue/waker.rs b/embassy-sync/src/waitqueue/waker_registration.rs similarity index 62% rename from embassy-sync/src/waitqueue/waker.rs rename to embassy-sync/src/waitqueue/waker_registration.rs index 64e300eb8..9b666e7c4 100644 --- a/embassy-sync/src/waitqueue/waker.rs +++ b/embassy-sync/src/waitqueue/waker_registration.rs @@ -1,12 +1,8 @@ -use core::cell::Cell; use core::mem; use core::task::Waker; -use crate::blocking_mutex::raw::CriticalSectionRawMutex; -use crate::blocking_mutex::Mutex; - /// Utility struct to register and wake a waker. -#[derive(Debug)] +#[derive(Debug, Default)] pub struct WakerRegistration { waker: Option, } @@ -54,39 +50,3 @@ impl WakerRegistration { self.waker.is_some() } } - -/// Utility struct to register and wake a waker. -pub struct AtomicWaker { - waker: Mutex>>, -} - -impl AtomicWaker { - /// Create a new `AtomicWaker`. - pub const fn new() -> Self { - Self { - waker: Mutex::const_new(CriticalSectionRawMutex::new(), Cell::new(None)), - } - } - - /// Register a waker. Overwrites the previous waker, if any. - pub fn register(&self, w: &Waker) { - critical_section::with(|cs| { - let cell = self.waker.borrow(cs); - cell.set(match cell.replace(None) { - Some(w2) if (w2.will_wake(w)) => Some(w2), - _ => Some(w.clone()), - }) - }) - } - - /// Wake the registered waker, if any. - pub fn wake(&self) { - critical_section::with(|cs| { - let cell = self.waker.borrow(cs); - if let Some(w) = cell.replace(None) { - w.wake_by_ref(); - cell.set(Some(w)); - } - }) - } -} diff --git a/embassy-time/CHANGELOG.md b/embassy-time/CHANGELOG.md new file mode 100644 index 000000000..26640d930 --- /dev/null +++ b/embassy-time/CHANGELOG.md @@ -0,0 +1,29 @@ +# Changelog + +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). + +## 0.1.2 - 2023-07-05 + +- Update `embedded-hal-async` to `0.2.0-alpha.2`. +- Update `embedded-hal v1` to `1.0.0-alpha.11`. (Note: v0.2 support is kept unchanged). + +## 0.1.1 - 2023-04-13 + +- Update `embedded-hal-async` to `0.2.0-alpha.1` (uses `async fn` in traits). +- Update `embedded-hal v1` to `1.0.0-alpha.10`. (Note: v0.2 support is kept unchanged). +- Remove dep on `embassy-sync`. +- Fix reentrancy issues in the `std` time driver (#1177) +- Add `Duration::from_hz()`. +- impl `From` conversions to/from `core::time::Duration`. +- Add `#[must_use]` to all futures. +- Add inherent `async fn tick()` to `Ticker`, so you can use it directly without the `Stream` trait. +- Add more tick rates. +- impl `Default` for `Signal` +- Remove unnecessary uses of `atomic-polyfill` + +## 0.1.0 - 2022-08-26 + +- First release \ No newline at end of file diff --git a/embassy-time/Cargo.toml b/embassy-time/Cargo.toml index f0f622aba..0afb1103d 100644 --- a/embassy-time/Cargo.toml +++ b/embassy-time/Cargo.toml @@ -1,8 +1,17 @@ [package] name = "embassy-time" -version = "0.1.0" +version = "0.1.2" edition = "2021" - +description = "Instant and Duration for embedded no-std systems, with async timer support" +repository = "https://github.com/embassy-rs/embassy" +readme = "README.md" +license = "MIT OR Apache-2.0" +categories = [ + "embedded", + "no-std", + "concurrency", + "asynchronous", +] [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-time-v$VERSION/embassy-time/src/" @@ -10,9 +19,12 @@ src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-time/ features = ["nightly", "defmt", "unstable-traits", "std"] target = "x86_64-unknown-linux-gnu" +[package.metadata.docs.rs] +features = ["nightly", "defmt", "unstable-traits", "std"] + [features] -std = ["tick-1mhz"] -wasm = ["dep:wasm-bindgen", "dep:js-sys", "dep:wasm-timer", "tick-1mhz"] +std = ["tick-hz-1_000_000", "critical-section/std"] +wasm = ["dep:wasm-bindgen", "dep:js-sys", "dep:wasm-timer", "tick-hz-1_000_000"] # Enable nightly-only features nightly = ["embedded-hal-async"] @@ -25,30 +37,136 @@ unstable-traits = ["embedded-hal-1"] # To use this you must have a time driver provided. defmt-timestamp-uptime = ["defmt"] +# Create a global, generic queue that can be used with any executor +# To use this you must have a time driver provided. +generic-queue = [] + +# Set the number of timers for the generic queue. +# +# At most 1 `generic-queue-*` feature can be enabled. If none is enabled, a default of 64 timers is used. +# +# When using embassy-time from libraries, you should *not* enable any `generic-queue-*` feature, to allow the +# end user to pick. +generic-queue-8 = ["generic-queue"] +generic-queue-16 = ["generic-queue"] +generic-queue-32 = ["generic-queue"] +generic-queue-64 = ["generic-queue"] +generic-queue-128 = ["generic-queue"] + # Set the `embassy_time` tick rate. -# NOTE: This feature is only intended to be enabled by crates providing the time driver implementation. -# If you're not writing your own driver, check the driver documentation to customize the tick rate. -# If you're writing a driver and your tick rate is not listed here, please add it and send a PR! -tick-32768hz = [] -tick-1000hz = [] -tick-1mhz = [] -tick-16mhz = [] +# +# At most 1 `tick-*` feature can be enabled. If none is enabled, a default of 1MHz is used. +# +# If the time driver in use supports using arbitrary tick rates, you can enable one `tick-*` +# feature from your binary crate to set the tick rate. The driver will use configured tick rate. +# If the time driver supports a fixed tick rate, it will enable one feature itself, so you should +# not enable one. Check the time driver documentation for details. +# +# When using embassy-time from libraries, you should *not* enable any `tick-*` feature, to allow the +# end user or the driver to pick. + +# BEGIN TICKS +# Generated by gen_tick.py. DO NOT EDIT. +tick-hz-1 = [] +tick-hz-10 = [] +tick-hz-100 = [] +tick-hz-1_000 = [] +tick-hz-10_000 = [] +tick-hz-100_000 = [] +tick-hz-1_000_000 = [] +tick-hz-10_000_000 = [] +tick-hz-100_000_000 = [] +tick-hz-1_000_000_000 = [] +tick-hz-2 = [] +tick-hz-4 = [] +tick-hz-8 = [] +tick-hz-16 = [] +tick-hz-32 = [] +tick-hz-64 = [] +tick-hz-128 = [] +tick-hz-256 = [] +tick-hz-512 = [] +tick-hz-1_024 = [] +tick-hz-2_048 = [] +tick-hz-4_096 = [] +tick-hz-8_192 = [] +tick-hz-16_384 = [] +tick-hz-32_768 = [] +tick-hz-65_536 = [] +tick-hz-131_072 = [] +tick-hz-262_144 = [] +tick-hz-524_288 = [] +tick-hz-1_048_576 = [] +tick-hz-2_097_152 = [] +tick-hz-4_194_304 = [] +tick-hz-8_388_608 = [] +tick-hz-16_777_216 = [] +tick-hz-2_000 = [] +tick-hz-4_000 = [] +tick-hz-8_000 = [] +tick-hz-16_000 = [] +tick-hz-32_000 = [] +tick-hz-64_000 = [] +tick-hz-128_000 = [] +tick-hz-256_000 = [] +tick-hz-512_000 = [] +tick-hz-1_024_000 = [] +tick-hz-2_048_000 = [] +tick-hz-4_096_000 = [] +tick-hz-8_192_000 = [] +tick-hz-16_384_000 = [] +tick-hz-32_768_000 = [] +tick-hz-65_536_000 = [] +tick-hz-131_072_000 = [] +tick-hz-262_144_000 = [] +tick-hz-524_288_000 = [] +tick-hz-2_000_000 = [] +tick-hz-3_000_000 = [] +tick-hz-4_000_000 = [] +tick-hz-6_000_000 = [] +tick-hz-8_000_000 = [] +tick-hz-9_000_000 = [] +tick-hz-12_000_000 = [] +tick-hz-16_000_000 = [] +tick-hz-18_000_000 = [] +tick-hz-24_000_000 = [] +tick-hz-32_000_000 = [] +tick-hz-36_000_000 = [] +tick-hz-48_000_000 = [] +tick-hz-64_000_000 = [] +tick-hz-72_000_000 = [] +tick-hz-96_000_000 = [] +tick-hz-128_000_000 = [] +tick-hz-144_000_000 = [] +tick-hz-192_000_000 = [] +tick-hz-256_000_000 = [] +tick-hz-288_000_000 = [] +tick-hz-384_000_000 = [] +tick-hz-512_000_000 = [] +tick-hz-576_000_000 = [] +tick-hz-768_000_000 = [] +# END TICKS [dependencies] defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } embedded-hal-02 = { package = "embedded-hal", version = "0.2.6" } -embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.8", optional = true} -embedded-hal-async = { version = "0.1.0-alpha.1", optional = true} +embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11", optional = true} +embedded-hal-async = { version = "=0.2.0-alpha.2", optional = true} futures-util = { version = "0.3.17", default-features = false } -embassy-macros = { version = "0.1.0", path = "../embassy-macros"} atomic-polyfill = "1.0.1" critical-section = "1.1" cfg-if = "1.0.0" +heapless = "0.7" # WASM dependencies wasm-bindgen = { version = "0.2.81", optional = true } js-sys = { version = "0.3", optional = true } -wasm-timer = { version = "0.2.5", optional = true } \ No newline at end of file +wasm-timer = { version = "0.2.5", optional = true } + +[dev-dependencies] +serial_test = "0.9" +critical-section = { version = "1.1", features = ["std"] } +embassy-executor = { version = "0.2.0", path = "../embassy-executor", features = ["nightly"] } diff --git a/embassy-time/gen_tick.py b/embassy-time/gen_tick.py new file mode 100644 index 000000000..15e65187b --- /dev/null +++ b/embassy-time/gen_tick.py @@ -0,0 +1,54 @@ +import os +import toml +from glob import glob + +abspath = os.path.abspath(__file__) +dname = os.path.dirname(abspath) +os.chdir(dname) + +ticks = [] +for i in range(10): + ticks.append(10**i) +for i in range(1, 25): + ticks.append(2**i) +for i in range(1, 20): + ticks.append(2**i * 1000) +for i in range(1, 10): + ticks.append(2**i * 1000000) + ticks.append(2**i * 9 // 8 * 1000000) + ticks.append(2**i * 3 // 2 * 1000000) + +seen = set() +ticks = [x for x in ticks if not (x in seen or seen.add(x))] + +# ========= Update Cargo.toml + +things = {f'tick-hz-{hz:_}': [] for hz in ticks} + +SEPARATOR_START = '# BEGIN TICKS\n' +SEPARATOR_END = '# END TICKS\n' +HELP = '# Generated by gen_tick.py. DO NOT EDIT.\n' +with open('Cargo.toml', 'r') as f: + data = f.read() +before, data = data.split(SEPARATOR_START, maxsplit=1) +_, after = data.split(SEPARATOR_END, maxsplit=1) +data = before + SEPARATOR_START + HELP + \ + toml.dumps(things) + SEPARATOR_END + after +with open('Cargo.toml', 'w') as f: + f.write(data) + +# ========= Update src/tick.rs + +with open('src/tick.rs', 'w') as f: + + f.write('// Generated by gen_tick.py. DO NOT EDIT.\n\n') + for hz in ticks: + f.write( + f'#[cfg(feature = "tick-hz-{hz:_}")] pub const TICK_HZ: u64 = {hz:_};\n') + f.write('#[cfg(not(any(\n') + for hz in ticks: + f.write(f'feature = "tick-hz-{hz:_}",\n') + f.write(')))] pub const TICK_HZ: u64 = 1_000_000;') + + +os.system('rustfmt src/tick.rs') diff --git a/embassy-time/src/delay.rs b/embassy-time/src/delay.rs index d010fff98..cf1918724 100644 --- a/embassy-time/src/delay.rs +++ b/embassy-time/src/delay.rs @@ -18,39 +18,29 @@ pub struct Delay; mod eh1 { use super::*; - impl embedded_hal_1::delay::blocking::DelayUs for Delay { - type Error = core::convert::Infallible; - - fn delay_us(&mut self, us: u32) -> Result<(), Self::Error> { - Ok(block_for(Duration::from_micros(us as u64))) + impl embedded_hal_1::delay::DelayUs for Delay { + fn delay_us(&mut self, us: u32) { + block_for(Duration::from_micros(us as u64)) } - fn delay_ms(&mut self, ms: u32) -> Result<(), Self::Error> { - Ok(block_for(Duration::from_millis(ms as u64))) + fn delay_ms(&mut self, ms: u32) { + block_for(Duration::from_millis(ms as u64)) } } } -cfg_if::cfg_if! { - if #[cfg(all(feature = "unstable-traits", feature = "nightly"))] { - use crate::Timer; - use core::future::Future; - use futures_util::FutureExt; +#[cfg(all(feature = "unstable-traits", feature = "nightly"))] +mod eha { + use super::*; + use crate::Timer; - impl embedded_hal_async::delay::DelayUs for Delay { - type Error = core::convert::Infallible; + impl embedded_hal_async::delay::DelayUs for Delay { + async fn delay_us(&mut self, micros: u32) { + Timer::after(Duration::from_micros(micros as _)).await + } - type DelayUsFuture<'a> = impl Future> + 'a where Self: 'a; - - fn delay_us(&mut self, micros: u32) -> Self::DelayUsFuture<'_> { - Timer::after(Duration::from_micros(micros as _)).map(Ok) - } - - type DelayMsFuture<'a> = impl Future> + 'a where Self: 'a; - - fn delay_ms(&mut self, millis: u32) -> Self::DelayMsFuture<'_> { - Timer::after(Duration::from_millis(millis as _)).map(Ok) - } + async fn delay_ms(&mut self, millis: u32) { + Timer::after(Duration::from_millis(millis as _)).await } } } diff --git a/embassy-time/src/driver.rs b/embassy-time/src/driver.rs index 79ae14b91..5fe7becaf 100644 --- a/embassy-time/src/driver.rs +++ b/embassy-time/src/driver.rs @@ -36,7 +36,7 @@ //! ``` //! use embassy_time::driver::{Driver, AlarmHandle}; //! -//! struct MyDriver{}; // not public! +//! struct MyDriver{} // not public! //! embassy_time::time_driver_impl!(static DRIVER: MyDriver = MyDriver{}); //! //! impl Driver for MyDriver { @@ -49,7 +49,7 @@ //! fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { //! todo!() //! } -//! fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) { +//! fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool { //! todo!() //! } //! } @@ -105,20 +105,21 @@ pub trait Driver: Send + Sync + 'static { /// Sets an alarm at the given timestamp. When the current timestamp reaches the alarm /// timestamp, the provided callback function will be called. /// - /// If `timestamp` is already in the past, the alarm callback must be immediately fired. - /// In this case, it is allowed (but not mandatory) to call the alarm callback synchronously from `set_alarm`. + /// The `Driver` implementation should guarantee that the alarm callback is never called synchronously from `set_alarm`. + /// Rather - if `timestamp` is already in the past - `false` should be returned and alarm should not be set, + /// or alternatively, the driver should return `true` and arrange to call the alarm callback as soon as possible, but not synchronously. /// /// When callback is called, it is guaranteed that now() will return a value greater or equal than timestamp. /// /// Only one alarm can be active at a time for each AlarmHandle. This overwrites any previously-set alarm if any. - fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64); + fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool; } extern "Rust" { fn _embassy_time_now() -> u64; fn _embassy_time_allocate_alarm() -> Option; fn _embassy_time_set_alarm_callback(alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()); - fn _embassy_time_set_alarm(alarm: AlarmHandle, timestamp: u64); + fn _embassy_time_set_alarm(alarm: AlarmHandle, timestamp: u64) -> bool; } /// See [`Driver::now`] @@ -139,7 +140,7 @@ pub fn set_alarm_callback(alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ( } /// See [`Driver::set_alarm`] -pub fn set_alarm(alarm: AlarmHandle, timestamp: u64) { +pub fn set_alarm(alarm: AlarmHandle, timestamp: u64) -> bool { unsafe { _embassy_time_set_alarm(alarm, timestamp) } } @@ -167,7 +168,7 @@ macro_rules! time_driver_impl { } #[no_mangle] - fn _embassy_time_set_alarm(alarm: $crate::driver::AlarmHandle, timestamp: u64) { + fn _embassy_time_set_alarm(alarm: $crate::driver::AlarmHandle, timestamp: u64) -> bool { <$t as $crate::driver::Driver>::set_alarm(&$name, alarm, timestamp) } }; diff --git a/embassy-time/src/driver_std.rs b/embassy-time/src/driver_std.rs index 2ddb2e604..32db47a37 100644 --- a/embassy-time/src/driver_std.rs +++ b/embassy-time/src/driver_std.rs @@ -1,10 +1,11 @@ -use std::cell::UnsafeCell; +use core::sync::atomic::{AtomicU8, Ordering}; +use std::cell::{RefCell, UnsafeCell}; use std::mem::MaybeUninit; use std::sync::{Condvar, Mutex, Once}; use std::time::{Duration as StdDuration, Instant as StdInstant}; use std::{mem, ptr, thread}; -use atomic_polyfill::{AtomicU8, Ordering}; +use critical_section::Mutex as CsMutex; use crate::driver::{AlarmHandle, Driver}; @@ -35,7 +36,10 @@ struct TimeDriver { alarm_count: AtomicU8, once: Once, - alarms: UninitCell>, + // The STD Driver implementation requires the alarms' mutex to be reentrant, which the STD Mutex isn't + // Fortunately, mutexes based on the `critical-section` crate are reentrant, because the critical sections + // themselves are reentrant + alarms: UninitCell>>, zero_instant: UninitCell, signaler: UninitCell, } @@ -53,7 +57,7 @@ crate::time_driver_impl!(static DRIVER: TimeDriver = TimeDriver { impl TimeDriver { fn init(&self) { self.once.call_once(|| unsafe { - self.alarms.write(Mutex::new([ALARM_NEW; ALARM_COUNT])); + self.alarms.write(CsMutex::new(RefCell::new([ALARM_NEW; ALARM_COUNT]))); self.zero_instant.write(StdInstant::now()); self.signaler.write(Signaler::new()); @@ -66,25 +70,38 @@ impl TimeDriver { loop { let now = DRIVER.now(); - let mut next_alarm = u64::MAX; - { - let alarms = &mut *unsafe { DRIVER.alarms.as_ref() }.lock().unwrap(); - for alarm in alarms { - if alarm.timestamp <= now { - alarm.timestamp = u64::MAX; + let next_alarm = critical_section::with(|cs| { + let alarms = unsafe { DRIVER.alarms.as_ref() }.borrow(cs); + loop { + let pending = alarms + .borrow_mut() + .iter_mut() + .find(|alarm| alarm.timestamp <= now) + .map(|alarm| { + alarm.timestamp = u64::MAX; - // Call after clearing alarm, so the callback can set another alarm. + (alarm.callback, alarm.ctx) + }); + if let Some((callback, ctx)) = pending { // safety: // - we can ignore the possiblity of `f` being unset (null) because of the safety contract of `allocate_alarm`. // - other than that we only store valid function pointers into alarm.callback - let f: fn(*mut ()) = unsafe { mem::transmute(alarm.callback) }; - f(alarm.ctx); + let f: fn(*mut ()) = unsafe { mem::transmute(callback) }; + f(ctx); } else { - next_alarm = next_alarm.min(alarm.timestamp); + // No alarm due + break; } } - } + + alarms + .borrow() + .iter() + .map(|alarm| alarm.timestamp) + .min() + .unwrap_or(u64::MAX) + }); // Ensure we don't overflow let until = zero @@ -121,18 +138,24 @@ impl Driver for TimeDriver { fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { self.init(); - let mut alarms = unsafe { self.alarms.as_ref() }.lock().unwrap(); - let alarm = &mut alarms[alarm.id() as usize]; - alarm.callback = callback as *const (); - alarm.ctx = ctx; + critical_section::with(|cs| { + let mut alarms = unsafe { self.alarms.as_ref() }.borrow_ref_mut(cs); + let alarm = &mut alarms[alarm.id() as usize]; + alarm.callback = callback as *const (); + alarm.ctx = ctx; + }); } - fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) { + fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool { self.init(); - let mut alarms = unsafe { self.alarms.as_ref() }.lock().unwrap(); - let alarm = &mut alarms[alarm.id() as usize]; - alarm.timestamp = timestamp; - unsafe { self.signaler.as_ref() }.signal(); + critical_section::with(|cs| { + let mut alarms = unsafe { self.alarms.as_ref() }.borrow_ref_mut(cs); + let alarm = &mut alarms[alarm.id() as usize]; + alarm.timestamp = timestamp; + unsafe { self.signaler.as_ref() }.signal(); + }); + + true } } diff --git a/embassy-time/src/driver_wasm.rs b/embassy-time/src/driver_wasm.rs index e4497e6a2..0f672dc75 100644 --- a/embassy-time/src/driver_wasm.rs +++ b/embassy-time/src/driver_wasm.rs @@ -1,9 +1,9 @@ +use core::sync::atomic::{AtomicU8, Ordering}; use std::cell::UnsafeCell; use std::mem::MaybeUninit; use std::ptr; use std::sync::{Mutex, Once}; -use atomic_polyfill::{AtomicU8, Ordering}; use wasm_bindgen::prelude::*; use wasm_timer::Instant as StdInstant; @@ -90,15 +90,23 @@ impl Driver for TimeDriver { })); } - fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) { + fn set_alarm(&self, alarm: AlarmHandle, timestamp: u64) -> bool { self.init(); let mut alarms = unsafe { self.alarms.as_ref() }.lock().unwrap(); let alarm = &mut alarms[alarm.id() as usize]; - let timeout = (timestamp - self.now()) as u32; if let Some(token) = alarm.token { clearTimeout(token); } - alarm.token = Some(setTimeout(alarm.closure.as_ref().unwrap(), timeout / 1000)); + + let now = self.now(); + if timestamp <= now { + false + } else { + let timeout = (timestamp - now) as u32; + alarm.token = Some(setTimeout(alarm.closure.as_ref().unwrap(), timeout / 1000)); + + true + } } } diff --git a/embassy-time/src/duration.rs b/embassy-time/src/duration.rs index dc4f16bd4..8366455be 100644 --- a/embassy-time/src/duration.rs +++ b/embassy-time/src/duration.rs @@ -1,7 +1,7 @@ use core::fmt; use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign}; -use super::{GCD_1K, GCD_1M, TICKS_PER_SECOND}; +use super::{GCD_1K, GCD_1M, TICK_HZ}; #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -23,17 +23,17 @@ impl Duration { /// Convert the `Duration` to seconds, rounding down. pub const fn as_secs(&self) -> u64 { - self.ticks / TICKS_PER_SECOND + self.ticks / TICK_HZ } /// Convert the `Duration` to milliseconds, rounding down. pub const fn as_millis(&self) -> u64 { - self.ticks * (1000 / GCD_1K) / (TICKS_PER_SECOND / GCD_1K) + self.ticks * (1000 / GCD_1K) / (TICK_HZ / GCD_1K) } /// Convert the `Duration` to microseconds, rounding down. pub const fn as_micros(&self) -> u64 { - self.ticks * (1_000_000 / GCD_1M) / (TICKS_PER_SECOND / GCD_1M) + self.ticks * (1_000_000 / GCD_1M) / (TICK_HZ / GCD_1M) } /// Creates a duration from the specified number of clock ticks @@ -43,15 +43,13 @@ impl Duration { /// Creates a duration from the specified number of seconds, rounding up. pub const fn from_secs(secs: u64) -> Duration { - Duration { - ticks: secs * TICKS_PER_SECOND, - } + Duration { ticks: secs * TICK_HZ } } /// Creates a duration from the specified number of milliseconds, rounding up. pub const fn from_millis(millis: u64) -> Duration { Duration { - ticks: div_ceil(millis * (TICKS_PER_SECOND / GCD_1K), 1000 / GCD_1K), + ticks: div_ceil(millis * (TICK_HZ / GCD_1K), 1000 / GCD_1K), } } @@ -59,21 +57,19 @@ impl Duration { /// NOTE: Delays this small may be inaccurate. pub const fn from_micros(micros: u64) -> Duration { Duration { - ticks: div_ceil(micros * (TICKS_PER_SECOND / GCD_1M), 1_000_000 / GCD_1M), + ticks: div_ceil(micros * (TICK_HZ / GCD_1M), 1_000_000 / GCD_1M), } } /// Creates a duration from the specified number of seconds, rounding down. pub const fn from_secs_floor(secs: u64) -> Duration { - Duration { - ticks: secs * TICKS_PER_SECOND, - } + Duration { ticks: secs * TICK_HZ } } /// Creates a duration from the specified number of milliseconds, rounding down. pub const fn from_millis_floor(millis: u64) -> Duration { Duration { - ticks: millis * (TICKS_PER_SECOND / GCD_1K) / (1000 / GCD_1K), + ticks: millis * (TICK_HZ / GCD_1K) / (1000 / GCD_1K), } } @@ -81,10 +77,24 @@ impl Duration { /// NOTE: Delays this small may be inaccurate. pub const fn from_micros_floor(micros: u64) -> Duration { Duration { - ticks: micros * (TICKS_PER_SECOND / GCD_1M) / (1_000_000 / GCD_1M), + ticks: micros * (TICK_HZ / GCD_1M) / (1_000_000 / GCD_1M), } } + /// Creates a duration corresponding to the specified Hz. + /// NOTE: Giving this function a hz >= the TICK_HZ of your platform will clamp the Duration to 1 + /// tick. Doing so will not deadlock, but will certainly not produce the desired output. + pub const fn from_hz(hz: u64) -> Duration { + let ticks = { + if hz >= TICK_HZ { + 1 + } else { + (TICK_HZ + hz / 2) / hz + } + }; + Duration { ticks } + } + /// Adds one Duration to another, returning a new Duration or None in the event of an overflow. pub fn checked_add(self, rhs: Duration) -> Option { self.ticks.checked_add(rhs.ticks).map(|ticks| Duration { ticks }) @@ -182,3 +192,19 @@ impl<'a> fmt::Display for Duration { const fn div_ceil(num: u64, den: u64) -> u64 { (num + den - 1) / den } + +impl TryFrom for Duration { + type Error = >::Error; + + /// Converts using [`Duration::from_micros`]. Fails if value can not be represented as u64. + fn try_from(value: core::time::Duration) -> Result { + Ok(Self::from_micros(value.as_micros().try_into()?)) + } +} + +impl From for core::time::Duration { + /// Converts using [`Duration::as_micros`]. + fn from(value: Duration) -> Self { + core::time::Duration::from_micros(value.as_micros()) + } +} diff --git a/embassy-time/src/instant.rs b/embassy-time/src/instant.rs index 6a4925f47..44f226f62 100644 --- a/embassy-time/src/instant.rs +++ b/embassy-time/src/instant.rs @@ -1,7 +1,7 @@ use core::fmt; use core::ops::{Add, AddAssign, Sub, SubAssign}; -use super::{driver, Duration, GCD_1K, GCD_1M, TICKS_PER_SECOND}; +use super::{driver, Duration, GCD_1K, GCD_1M, TICK_HZ}; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -29,21 +29,21 @@ impl Instant { /// Create an Instant from a microsecond count since system boot. pub const fn from_micros(micros: u64) -> Self { Self { - ticks: micros * (TICKS_PER_SECOND / GCD_1M) / (1_000_000 / GCD_1M), + ticks: micros * (TICK_HZ / GCD_1M) / (1_000_000 / GCD_1M), } } /// Create an Instant from a millisecond count since system boot. pub const fn from_millis(millis: u64) -> Self { Self { - ticks: millis * (TICKS_PER_SECOND / GCD_1K) / (1000 / GCD_1K), + ticks: millis * (TICK_HZ / GCD_1K) / (1000 / GCD_1K), } } /// Create an Instant from a second count since system boot. pub const fn from_secs(seconds: u64) -> Self { Self { - ticks: seconds * TICKS_PER_SECOND, + ticks: seconds * TICK_HZ, } } @@ -54,17 +54,17 @@ impl Instant { /// Seconds since system boot. pub const fn as_secs(&self) -> u64 { - self.ticks / TICKS_PER_SECOND + self.ticks / TICK_HZ } /// Milliseconds since system boot. pub const fn as_millis(&self) -> u64 { - self.ticks * (1000 / GCD_1K) / (TICKS_PER_SECOND / GCD_1K) + self.ticks * (1000 / GCD_1K) / (TICK_HZ / GCD_1K) } /// Microseconds since system boot. pub const fn as_micros(&self) -> u64 { - self.ticks * (1_000_000 / GCD_1M) / (TICKS_PER_SECOND / GCD_1M) + self.ticks * (1_000_000 / GCD_1M) / (TICK_HZ / GCD_1M) } /// Duration between this Instant and another Instant diff --git a/embassy-time/src/lib.rs b/embassy-time/src/lib.rs index a6c5d78cc..8f57eabcb 100644 --- a/embassy-time/src/lib.rs +++ b/embassy-time/src/lib.rs @@ -1,5 +1,5 @@ -#![cfg_attr(not(any(feature = "std", feature = "wasm")), no_std)] -#![cfg_attr(feature = "nightly", feature(generic_associated_types, type_alias_impl_trait))] +#![cfg_attr(not(any(feature = "std", feature = "wasm", test)), no_std)] +#![cfg_attr(feature = "nightly", feature(async_fn_in_trait))] #![doc = include_str!("../README.md")] #![allow(clippy::new_without_default)] #![warn(missing_docs)] @@ -11,37 +11,29 @@ mod delay; pub mod driver; mod duration; mod instant; +pub mod queue; +mod tick; mod timer; #[cfg(feature = "std")] mod driver_std; #[cfg(feature = "wasm")] mod driver_wasm; +#[cfg(feature = "generic-queue")] +mod queue_generic; pub use delay::{block_for, Delay}; pub use duration::Duration; pub use instant::Instant; pub use timer::{with_timeout, Ticker, TimeoutError, Timer}; -#[cfg(feature = "tick-1000hz")] -const TPS: u64 = 1_000; - -#[cfg(feature = "tick-32768hz")] -const TPS: u64 = 32_768; - -#[cfg(feature = "tick-1mhz")] -const TPS: u64 = 1_000_000; - -#[cfg(feature = "tick-16mhz")] -const TPS: u64 = 16_000_000; - /// Ticks per second of the global timebase. /// /// This value is specified by the `tick-*` Cargo features, which /// should be set by the time driver. Some drivers support a fixed tick rate, others /// allow you to choose a tick rate with Cargo features of their own. You should not /// set the `tick-*` features for embassy yourself as an end user. -pub const TICKS_PER_SECOND: u64 = TPS; +pub const TICK_HZ: u64 = tick::TICK_HZ; const fn gcd(a: u64, b: u64) -> u64 { if b == 0 { @@ -51,8 +43,8 @@ const fn gcd(a: u64, b: u64) -> u64 { } } -pub(crate) const GCD_1K: u64 = gcd(TICKS_PER_SECOND, 1_000); -pub(crate) const GCD_1M: u64 = gcd(TICKS_PER_SECOND, 1_000_000); +pub(crate) const GCD_1K: u64 = gcd(TICK_HZ, 1_000); +pub(crate) const GCD_1M: u64 = gcd(TICK_HZ, 1_000_000); #[cfg(feature = "defmt-timestamp-uptime")] defmt::timestamp! {"{=u64:us}", Instant::now().as_micros() } diff --git a/embassy-time/src/queue.rs b/embassy-time/src/queue.rs new file mode 100644 index 000000000..c6f8b440a --- /dev/null +++ b/embassy-time/src/queue.rs @@ -0,0 +1,58 @@ +//! Timer queue implementation +//! +//! This module defines the interface a timer queue needs to implement to power the `embassy_time` module. +//! +//! # Implementing a timer queue +//! +//! - Define a struct `MyTimerQueue` +//! - Implement [`TimerQueue`] for it +//! - Register it as the global timer queue with [`timer_queue_impl`](crate::timer_queue_impl). +//! +//! # Linkage details +//! +//! Check the documentation of the [`driver`](crate::driver) module for more information. +//! +//! Similarly to driver, if there is none or multiple timer queues in the crate tree, linking will fail. +//! +//! # Example +//! +//! ``` +//! use core::task::Waker; +//! +//! use embassy_time::Instant; +//! use embassy_time::queue::{TimerQueue}; +//! +//! struct MyTimerQueue{}; // not public! +//! embassy_time::timer_queue_impl!(static QUEUE: MyTimerQueue = MyTimerQueue{}); +//! +//! impl TimerQueue for MyTimerQueue { +//! fn schedule_wake(&'static self, at: Instant, waker: &Waker) { +//! todo!() +//! } +//! } +//! ``` +use core::task::Waker; + +use crate::Instant; + +/// Timer queue +pub trait TimerQueue { + /// Schedules a waker in the queue to be awoken at moment `at`. + /// If this moment is in the past, the waker might be awoken immediately. + fn schedule_wake(&'static self, at: Instant, waker: &Waker); +} + +/// Set the TimerQueue implementation. +/// +/// See the module documentation for an example. +#[macro_export] +macro_rules! timer_queue_impl { + (static $name:ident: $t: ty = $val:expr) => { + static $name: $t = $val; + + #[no_mangle] + fn _embassy_time_schedule_wake(at: $crate::Instant, waker: &core::task::Waker) { + <$t as $crate::queue::TimerQueue>::schedule_wake(&$name, at, waker); + } + }; +} diff --git a/embassy-time/src/queue_generic.rs b/embassy-time/src/queue_generic.rs new file mode 100644 index 000000000..77947ab29 --- /dev/null +++ b/embassy-time/src/queue_generic.rs @@ -0,0 +1,450 @@ +use core::cell::RefCell; +use core::cmp::{min, Ordering}; +use core::task::Waker; + +use critical_section::Mutex; +use heapless::Vec; + +use crate::driver::{allocate_alarm, set_alarm, set_alarm_callback, AlarmHandle}; +use crate::queue::TimerQueue; +use crate::Instant; + +#[cfg(feature = "generic-queue-8")] +const QUEUE_SIZE: usize = 8; +#[cfg(feature = "generic-queue-16")] +const QUEUE_SIZE: usize = 16; +#[cfg(feature = "generic-queue-32")] +const QUEUE_SIZE: usize = 32; +#[cfg(feature = "generic-queue-64")] +const QUEUE_SIZE: usize = 64; +#[cfg(feature = "generic-queue-128")] +const QUEUE_SIZE: usize = 128; +#[cfg(not(any( + feature = "generic-queue-8", + feature = "generic-queue-16", + feature = "generic-queue-32", + feature = "generic-queue-64", + feature = "generic-queue-128" +)))] +const QUEUE_SIZE: usize = 64; + +#[derive(Debug)] +struct Timer { + at: Instant, + waker: Waker, +} + +impl PartialEq for Timer { + fn eq(&self, other: &Self) -> bool { + self.at == other.at + } +} + +impl Eq for Timer {} + +impl PartialOrd for Timer { + fn partial_cmp(&self, other: &Self) -> Option { + self.at.partial_cmp(&other.at) + } +} + +impl Ord for Timer { + fn cmp(&self, other: &Self) -> Ordering { + self.at.cmp(&other.at) + } +} + +struct InnerQueue { + queue: Vec, + alarm: AlarmHandle, +} + +impl InnerQueue { + fn schedule_wake(&mut self, at: Instant, waker: &Waker) { + self.queue + .iter_mut() + .find(|timer| timer.waker.will_wake(waker)) + .map(|timer| { + timer.at = min(timer.at, at); + }) + .unwrap_or_else(|| { + let mut timer = Timer { + waker: waker.clone(), + at, + }; + + loop { + match self.queue.push(timer) { + Ok(()) => break, + Err(e) => timer = e, + } + + self.queue.pop().unwrap().waker.wake(); + } + }); + + // Don't wait for the alarm callback to trigger and directly + // dispatch all timers that are already due + // + // Then update the alarm if necessary + self.dispatch(); + } + + fn dispatch(&mut self) { + loop { + let now = Instant::now(); + + let mut next_alarm = Instant::MAX; + + let mut i = 0; + while i < self.queue.len() { + let timer = &self.queue[i]; + if timer.at <= now { + let timer = self.queue.swap_remove(i); + timer.waker.wake(); + } else { + next_alarm = min(next_alarm, timer.at); + i += 1; + } + } + + if self.update_alarm(next_alarm) { + break; + } + } + } + + fn update_alarm(&mut self, next_alarm: Instant) -> bool { + if next_alarm == Instant::MAX { + true + } else { + set_alarm(self.alarm, next_alarm.as_ticks()) + } + } + + fn handle_alarm(&mut self) { + self.dispatch(); + } +} + +struct Queue { + inner: Mutex>>, +} + +impl Queue { + const fn new() -> Self { + Self { + inner: Mutex::new(RefCell::new(None)), + } + } + + fn schedule_wake(&'static self, at: Instant, waker: &Waker) { + critical_section::with(|cs| { + let mut inner = self.inner.borrow_ref_mut(cs); + + if inner.is_none() {} + + inner + .get_or_insert_with(|| { + let handle = unsafe { allocate_alarm() }.unwrap(); + set_alarm_callback(handle, Self::handle_alarm_callback, self as *const _ as _); + InnerQueue { + queue: Vec::new(), + alarm: handle, + } + }) + .schedule_wake(at, waker) + }); + } + + fn handle_alarm(&self) { + critical_section::with(|cs| self.inner.borrow_ref_mut(cs).as_mut().unwrap().handle_alarm()) + } + + fn handle_alarm_callback(ctx: *mut ()) { + unsafe { (ctx as *const Self).as_ref().unwrap() }.handle_alarm(); + } +} + +impl TimerQueue for Queue { + fn schedule_wake(&'static self, at: Instant, waker: &Waker) { + Queue::schedule_wake(self, at, waker); + } +} + +crate::timer_queue_impl!(static QUEUE: Queue = Queue::new()); + +#[cfg(test)] +mod tests { + use core::cell::Cell; + use core::task::{RawWaker, RawWakerVTable, Waker}; + use std::rc::Rc; + use std::sync::Mutex; + + use serial_test::serial; + + use crate::driver::{AlarmHandle, Driver}; + use crate::queue_generic::QUEUE; + use crate::Instant; + + struct InnerTestDriver { + now: u64, + alarm: u64, + callback: fn(*mut ()), + ctx: *mut (), + } + + impl InnerTestDriver { + const fn new() -> Self { + Self { + now: 0, + alarm: u64::MAX, + callback: Self::noop, + ctx: core::ptr::null_mut(), + } + } + + fn noop(_ctx: *mut ()) {} + } + + unsafe impl Send for InnerTestDriver {} + + struct TestDriver(Mutex); + + impl TestDriver { + const fn new() -> Self { + Self(Mutex::new(InnerTestDriver::new())) + } + + fn reset(&self) { + *self.0.lock().unwrap() = InnerTestDriver::new(); + } + + fn set_now(&self, now: u64) { + let notify = { + let mut inner = self.0.lock().unwrap(); + + if inner.now < now { + inner.now = now; + + if inner.alarm <= now { + inner.alarm = u64::MAX; + + Some((inner.callback, inner.ctx)) + } else { + None + } + } else { + panic!("Going back in time?"); + } + }; + + if let Some((callback, ctx)) = notify { + (callback)(ctx); + } + } + } + + impl Driver for TestDriver { + fn now(&self) -> u64 { + self.0.lock().unwrap().now + } + + unsafe fn allocate_alarm(&self) -> Option { + Some(AlarmHandle::new(0)) + } + + fn set_alarm_callback(&self, _alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { + let mut inner = self.0.lock().unwrap(); + + inner.callback = callback; + inner.ctx = ctx; + } + + fn set_alarm(&self, _alarm: AlarmHandle, timestamp: u64) -> bool { + let mut inner = self.0.lock().unwrap(); + + if timestamp <= inner.now { + false + } else { + inner.alarm = timestamp; + true + } + } + } + + struct TestWaker { + pub awoken: Rc>, + pub waker: Waker, + } + + impl TestWaker { + fn new() -> Self { + let flag = Rc::new(Cell::new(false)); + + const VTABLE: RawWakerVTable = RawWakerVTable::new( + |data: *const ()| { + unsafe { + Rc::increment_strong_count(data as *const Cell); + } + + RawWaker::new(data as _, &VTABLE) + }, + |data: *const ()| unsafe { + let data = data as *const Cell; + data.as_ref().unwrap().set(true); + Rc::decrement_strong_count(data); + }, + |data: *const ()| unsafe { + (data as *const Cell).as_ref().unwrap().set(true); + }, + |data: *const ()| unsafe { + Rc::decrement_strong_count(data); + }, + ); + + let raw = RawWaker::new(Rc::into_raw(flag.clone()) as _, &VTABLE); + + Self { + awoken: flag.clone(), + waker: unsafe { Waker::from_raw(raw) }, + } + } + } + + crate::time_driver_impl!(static DRIVER: TestDriver = TestDriver::new()); + + fn setup() { + DRIVER.reset(); + critical_section::with(|cs| *QUEUE.inner.borrow_ref_mut(cs) = None); + } + + fn queue_len() -> usize { + critical_section::with(|cs| { + QUEUE + .inner + .borrow_ref(cs) + .as_ref() + .map(|inner| inner.queue.iter().count()) + .unwrap_or(0) + }) + } + + #[test] + #[serial] + fn test_schedule() { + setup(); + + assert_eq!(queue_len(), 0); + + let waker = TestWaker::new(); + + QUEUE.schedule_wake(Instant::from_secs(1), &waker.waker); + + assert!(!waker.awoken.get()); + assert_eq!(queue_len(), 1); + } + + #[test] + #[serial] + fn test_schedule_same() { + setup(); + + let waker = TestWaker::new(); + + QUEUE.schedule_wake(Instant::from_secs(1), &waker.waker); + + assert_eq!(queue_len(), 1); + + QUEUE.schedule_wake(Instant::from_secs(1), &waker.waker); + + assert_eq!(queue_len(), 1); + + QUEUE.schedule_wake(Instant::from_secs(100), &waker.waker); + + assert_eq!(queue_len(), 1); + + let waker2 = TestWaker::new(); + + QUEUE.schedule_wake(Instant::from_secs(100), &waker2.waker); + + assert_eq!(queue_len(), 2); + } + + #[test] + #[serial] + fn test_trigger() { + setup(); + + let waker = TestWaker::new(); + + QUEUE.schedule_wake(Instant::from_secs(100), &waker.waker); + + assert!(!waker.awoken.get()); + + DRIVER.set_now(Instant::from_secs(99).as_ticks()); + + assert!(!waker.awoken.get()); + + assert_eq!(queue_len(), 1); + + DRIVER.set_now(Instant::from_secs(100).as_ticks()); + + assert!(waker.awoken.get()); + + assert_eq!(queue_len(), 0); + } + + #[test] + #[serial] + fn test_immediate_trigger() { + setup(); + + let waker = TestWaker::new(); + + QUEUE.schedule_wake(Instant::from_secs(100), &waker.waker); + + DRIVER.set_now(Instant::from_secs(50).as_ticks()); + + let waker2 = TestWaker::new(); + + QUEUE.schedule_wake(Instant::from_secs(40), &waker2.waker); + + assert!(!waker.awoken.get()); + assert!(waker2.awoken.get()); + assert_eq!(queue_len(), 1); + } + + #[test] + #[serial] + fn test_queue_overflow() { + setup(); + + for i in 1..super::QUEUE_SIZE { + let waker = TestWaker::new(); + + QUEUE.schedule_wake(Instant::from_secs(310), &waker.waker); + + assert_eq!(queue_len(), i); + assert!(!waker.awoken.get()); + } + + let first_waker = TestWaker::new(); + + QUEUE.schedule_wake(Instant::from_secs(300), &first_waker.waker); + + assert_eq!(queue_len(), super::QUEUE_SIZE); + assert!(!first_waker.awoken.get()); + + let second_waker = TestWaker::new(); + + QUEUE.schedule_wake(Instant::from_secs(305), &second_waker.waker); + + assert_eq!(queue_len(), super::QUEUE_SIZE); + assert!(first_waker.awoken.get()); + + QUEUE.schedule_wake(Instant::from_secs(320), &TestWaker::new().waker); + assert_eq!(queue_len(), super::QUEUE_SIZE); + assert!(second_waker.awoken.get()); + } +} diff --git a/embassy-time/src/tick.rs b/embassy-time/src/tick.rs new file mode 100644 index 000000000..608bc44f1 --- /dev/null +++ b/embassy-time/src/tick.rs @@ -0,0 +1,239 @@ +// Generated by gen_tick.py. DO NOT EDIT. + +#[cfg(feature = "tick-hz-1")] +pub const TICK_HZ: u64 = 1; +#[cfg(feature = "tick-hz-10")] +pub const TICK_HZ: u64 = 10; +#[cfg(feature = "tick-hz-100")] +pub const TICK_HZ: u64 = 100; +#[cfg(feature = "tick-hz-1_000")] +pub const TICK_HZ: u64 = 1_000; +#[cfg(feature = "tick-hz-10_000")] +pub const TICK_HZ: u64 = 10_000; +#[cfg(feature = "tick-hz-100_000")] +pub const TICK_HZ: u64 = 100_000; +#[cfg(feature = "tick-hz-1_000_000")] +pub const TICK_HZ: u64 = 1_000_000; +#[cfg(feature = "tick-hz-10_000_000")] +pub const TICK_HZ: u64 = 10_000_000; +#[cfg(feature = "tick-hz-100_000_000")] +pub const TICK_HZ: u64 = 100_000_000; +#[cfg(feature = "tick-hz-1_000_000_000")] +pub const TICK_HZ: u64 = 1_000_000_000; +#[cfg(feature = "tick-hz-2")] +pub const TICK_HZ: u64 = 2; +#[cfg(feature = "tick-hz-4")] +pub const TICK_HZ: u64 = 4; +#[cfg(feature = "tick-hz-8")] +pub const TICK_HZ: u64 = 8; +#[cfg(feature = "tick-hz-16")] +pub const TICK_HZ: u64 = 16; +#[cfg(feature = "tick-hz-32")] +pub const TICK_HZ: u64 = 32; +#[cfg(feature = "tick-hz-64")] +pub const TICK_HZ: u64 = 64; +#[cfg(feature = "tick-hz-128")] +pub const TICK_HZ: u64 = 128; +#[cfg(feature = "tick-hz-256")] +pub const TICK_HZ: u64 = 256; +#[cfg(feature = "tick-hz-512")] +pub const TICK_HZ: u64 = 512; +#[cfg(feature = "tick-hz-1_024")] +pub const TICK_HZ: u64 = 1_024; +#[cfg(feature = "tick-hz-2_048")] +pub const TICK_HZ: u64 = 2_048; +#[cfg(feature = "tick-hz-4_096")] +pub const TICK_HZ: u64 = 4_096; +#[cfg(feature = "tick-hz-8_192")] +pub const TICK_HZ: u64 = 8_192; +#[cfg(feature = "tick-hz-16_384")] +pub const TICK_HZ: u64 = 16_384; +#[cfg(feature = "tick-hz-32_768")] +pub const TICK_HZ: u64 = 32_768; +#[cfg(feature = "tick-hz-65_536")] +pub const TICK_HZ: u64 = 65_536; +#[cfg(feature = "tick-hz-131_072")] +pub const TICK_HZ: u64 = 131_072; +#[cfg(feature = "tick-hz-262_144")] +pub const TICK_HZ: u64 = 262_144; +#[cfg(feature = "tick-hz-524_288")] +pub const TICK_HZ: u64 = 524_288; +#[cfg(feature = "tick-hz-1_048_576")] +pub const TICK_HZ: u64 = 1_048_576; +#[cfg(feature = "tick-hz-2_097_152")] +pub const TICK_HZ: u64 = 2_097_152; +#[cfg(feature = "tick-hz-4_194_304")] +pub const TICK_HZ: u64 = 4_194_304; +#[cfg(feature = "tick-hz-8_388_608")] +pub const TICK_HZ: u64 = 8_388_608; +#[cfg(feature = "tick-hz-16_777_216")] +pub const TICK_HZ: u64 = 16_777_216; +#[cfg(feature = "tick-hz-2_000")] +pub const TICK_HZ: u64 = 2_000; +#[cfg(feature = "tick-hz-4_000")] +pub const TICK_HZ: u64 = 4_000; +#[cfg(feature = "tick-hz-8_000")] +pub const TICK_HZ: u64 = 8_000; +#[cfg(feature = "tick-hz-16_000")] +pub const TICK_HZ: u64 = 16_000; +#[cfg(feature = "tick-hz-32_000")] +pub const TICK_HZ: u64 = 32_000; +#[cfg(feature = "tick-hz-64_000")] +pub const TICK_HZ: u64 = 64_000; +#[cfg(feature = "tick-hz-128_000")] +pub const TICK_HZ: u64 = 128_000; +#[cfg(feature = "tick-hz-256_000")] +pub const TICK_HZ: u64 = 256_000; +#[cfg(feature = "tick-hz-512_000")] +pub const TICK_HZ: u64 = 512_000; +#[cfg(feature = "tick-hz-1_024_000")] +pub const TICK_HZ: u64 = 1_024_000; +#[cfg(feature = "tick-hz-2_048_000")] +pub const TICK_HZ: u64 = 2_048_000; +#[cfg(feature = "tick-hz-4_096_000")] +pub const TICK_HZ: u64 = 4_096_000; +#[cfg(feature = "tick-hz-8_192_000")] +pub const TICK_HZ: u64 = 8_192_000; +#[cfg(feature = "tick-hz-16_384_000")] +pub const TICK_HZ: u64 = 16_384_000; +#[cfg(feature = "tick-hz-32_768_000")] +pub const TICK_HZ: u64 = 32_768_000; +#[cfg(feature = "tick-hz-65_536_000")] +pub const TICK_HZ: u64 = 65_536_000; +#[cfg(feature = "tick-hz-131_072_000")] +pub const TICK_HZ: u64 = 131_072_000; +#[cfg(feature = "tick-hz-262_144_000")] +pub const TICK_HZ: u64 = 262_144_000; +#[cfg(feature = "tick-hz-524_288_000")] +pub const TICK_HZ: u64 = 524_288_000; +#[cfg(feature = "tick-hz-2_000_000")] +pub const TICK_HZ: u64 = 2_000_000; +#[cfg(feature = "tick-hz-3_000_000")] +pub const TICK_HZ: u64 = 3_000_000; +#[cfg(feature = "tick-hz-4_000_000")] +pub const TICK_HZ: u64 = 4_000_000; +#[cfg(feature = "tick-hz-6_000_000")] +pub const TICK_HZ: u64 = 6_000_000; +#[cfg(feature = "tick-hz-8_000_000")] +pub const TICK_HZ: u64 = 8_000_000; +#[cfg(feature = "tick-hz-9_000_000")] +pub const TICK_HZ: u64 = 9_000_000; +#[cfg(feature = "tick-hz-12_000_000")] +pub const TICK_HZ: u64 = 12_000_000; +#[cfg(feature = "tick-hz-16_000_000")] +pub const TICK_HZ: u64 = 16_000_000; +#[cfg(feature = "tick-hz-18_000_000")] +pub const TICK_HZ: u64 = 18_000_000; +#[cfg(feature = "tick-hz-24_000_000")] +pub const TICK_HZ: u64 = 24_000_000; +#[cfg(feature = "tick-hz-32_000_000")] +pub const TICK_HZ: u64 = 32_000_000; +#[cfg(feature = "tick-hz-36_000_000")] +pub const TICK_HZ: u64 = 36_000_000; +#[cfg(feature = "tick-hz-48_000_000")] +pub const TICK_HZ: u64 = 48_000_000; +#[cfg(feature = "tick-hz-64_000_000")] +pub const TICK_HZ: u64 = 64_000_000; +#[cfg(feature = "tick-hz-72_000_000")] +pub const TICK_HZ: u64 = 72_000_000; +#[cfg(feature = "tick-hz-96_000_000")] +pub const TICK_HZ: u64 = 96_000_000; +#[cfg(feature = "tick-hz-128_000_000")] +pub const TICK_HZ: u64 = 128_000_000; +#[cfg(feature = "tick-hz-144_000_000")] +pub const TICK_HZ: u64 = 144_000_000; +#[cfg(feature = "tick-hz-192_000_000")] +pub const TICK_HZ: u64 = 192_000_000; +#[cfg(feature = "tick-hz-256_000_000")] +pub const TICK_HZ: u64 = 256_000_000; +#[cfg(feature = "tick-hz-288_000_000")] +pub const TICK_HZ: u64 = 288_000_000; +#[cfg(feature = "tick-hz-384_000_000")] +pub const TICK_HZ: u64 = 384_000_000; +#[cfg(feature = "tick-hz-512_000_000")] +pub const TICK_HZ: u64 = 512_000_000; +#[cfg(feature = "tick-hz-576_000_000")] +pub const TICK_HZ: u64 = 576_000_000; +#[cfg(feature = "tick-hz-768_000_000")] +pub const TICK_HZ: u64 = 768_000_000; +#[cfg(not(any( + feature = "tick-hz-1", + feature = "tick-hz-10", + feature = "tick-hz-100", + feature = "tick-hz-1_000", + feature = "tick-hz-10_000", + feature = "tick-hz-100_000", + feature = "tick-hz-1_000_000", + feature = "tick-hz-10_000_000", + feature = "tick-hz-100_000_000", + feature = "tick-hz-1_000_000_000", + feature = "tick-hz-2", + feature = "tick-hz-4", + feature = "tick-hz-8", + feature = "tick-hz-16", + feature = "tick-hz-32", + feature = "tick-hz-64", + feature = "tick-hz-128", + feature = "tick-hz-256", + feature = "tick-hz-512", + feature = "tick-hz-1_024", + feature = "tick-hz-2_048", + feature = "tick-hz-4_096", + feature = "tick-hz-8_192", + feature = "tick-hz-16_384", + feature = "tick-hz-32_768", + feature = "tick-hz-65_536", + feature = "tick-hz-131_072", + feature = "tick-hz-262_144", + feature = "tick-hz-524_288", + feature = "tick-hz-1_048_576", + feature = "tick-hz-2_097_152", + feature = "tick-hz-4_194_304", + feature = "tick-hz-8_388_608", + feature = "tick-hz-16_777_216", + feature = "tick-hz-2_000", + feature = "tick-hz-4_000", + feature = "tick-hz-8_000", + feature = "tick-hz-16_000", + feature = "tick-hz-32_000", + feature = "tick-hz-64_000", + feature = "tick-hz-128_000", + feature = "tick-hz-256_000", + feature = "tick-hz-512_000", + feature = "tick-hz-1_024_000", + feature = "tick-hz-2_048_000", + feature = "tick-hz-4_096_000", + feature = "tick-hz-8_192_000", + feature = "tick-hz-16_384_000", + feature = "tick-hz-32_768_000", + feature = "tick-hz-65_536_000", + feature = "tick-hz-131_072_000", + feature = "tick-hz-262_144_000", + feature = "tick-hz-524_288_000", + feature = "tick-hz-2_000_000", + feature = "tick-hz-3_000_000", + feature = "tick-hz-4_000_000", + feature = "tick-hz-6_000_000", + feature = "tick-hz-8_000_000", + feature = "tick-hz-9_000_000", + feature = "tick-hz-12_000_000", + feature = "tick-hz-16_000_000", + feature = "tick-hz-18_000_000", + feature = "tick-hz-24_000_000", + feature = "tick-hz-32_000_000", + feature = "tick-hz-36_000_000", + feature = "tick-hz-48_000_000", + feature = "tick-hz-64_000_000", + feature = "tick-hz-72_000_000", + feature = "tick-hz-96_000_000", + feature = "tick-hz-128_000_000", + feature = "tick-hz-144_000_000", + feature = "tick-hz-192_000_000", + feature = "tick-hz-256_000_000", + feature = "tick-hz-288_000_000", + feature = "tick-hz-384_000_000", + feature = "tick-hz-512_000_000", + feature = "tick-hz-576_000_000", + feature = "tick-hz-768_000_000", +)))] +pub const TICK_HZ: u64 = 1_000_000; diff --git a/embassy-time/src/timer.rs b/embassy-time/src/timer.rs index bd791b817..d3d1f9f5f 100644 --- a/embassy-time/src/timer.rs +++ b/embassy-time/src/timer.rs @@ -1,4 +1,4 @@ -use core::future::Future; +use core::future::{poll_fn, Future}; use core::pin::Pin; use core::task::{Context, Poll, Waker}; @@ -26,6 +26,7 @@ pub async fn with_timeout(timeout: Duration, fut: F) -> Result impl Future + '_ { + poll_fn(|cx| { + if self.expires_at <= Instant::now() { + let dur = self.duration; + self.expires_at += dur; + Poll::Ready(()) + } else { + schedule_wake(self.expires_at, cx.waker()); + Poll::Pending + } + }) + } } impl Unpin for Ticker {} diff --git a/embassy-usb-ncm/Cargo.toml b/embassy-usb-driver/Cargo.toml similarity index 55% rename from embassy-usb-ncm/Cargo.toml rename to embassy-usb-driver/Cargo.toml index 15d3db96f..d658f9ec7 100644 --- a/embassy-usb-ncm/Cargo.toml +++ b/embassy-usb-driver/Cargo.toml @@ -1,17 +1,16 @@ [package] -name = "embassy-usb-ncm" +name = "embassy-usb-driver" version = "0.1.0" edition = "2021" +license = "MIT OR Apache-2.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [package.metadata.embassy_docs] -src_base = "https://github.com/embassy-rs/embassy/blob/embassy-usb-ncm-v$VERSION/embassy-usb-ncm/src/" -src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-usb-ncm/src/" +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-usb-driver-v$VERSION/embassy-usb/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-usb-driver/src/" features = ["defmt"] target = "thumbv7em-none-eabi" [dependencies] -embassy-sync = { version = "0.1.0", path = "../embassy-sync" } -embassy-usb = { version = "0.1.0", path = "../embassy-usb" } - defmt = { version = "0.3", optional = true } -log = { version = "0.4.14", optional = true } diff --git a/embassy-usb-driver/README.md b/embassy-usb-driver/README.md new file mode 100644 index 000000000..012663c3f --- /dev/null +++ b/embassy-usb-driver/README.md @@ -0,0 +1,32 @@ +# embassy-usb-driver + +This crate contains the driver traits for [`embassy-usb`]. HAL/BSP crates can implement these +traits to add support for using `embassy-usb` for a given chip/platform. + +The traits are kept in a separate crate so that breaking changes in the higher-level [`embassy-usb`] +APIs don't cause a semver-major bump of thsi crate. This allows existing HALs/BSPs to be used +with the newer `embassy-usb` without needing updates. + +If you're writing an application using USB, you should depend on the main [`embassy-usb`] crate +instead of this one. + +[`embassy-usb`]: https://crates.io/crates/embassy-usb + +## Interoperability + +This crate can run on any executor. + +## Minimum supported Rust version (MSRV) + +This crate requires nightly Rust, due to using "async fn in trait" support. + +## License + +This work is licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. + diff --git a/embassy-usb-driver/src/lib.rs b/embassy-usb-driver/src/lib.rs new file mode 100644 index 000000000..86e37595b --- /dev/null +++ b/embassy-usb-driver/src/lib.rs @@ -0,0 +1,397 @@ +#![no_std] +#![feature(async_fn_in_trait)] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +/// Direction of USB traffic. Note that in the USB standard the direction is always indicated from +/// the perspective of the host, which is backward for devices, but the standard directions are used +/// for consistency. +/// +/// The values of the enum also match the direction bit used in endpoint addresses and control +/// request types. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Direction { + /// Host to device (OUT) + Out, + /// Device to host (IN) + In, +} + +/// USB endpoint transfer type. The values of this enum can be directly cast into `u8` to get the +/// transfer bmAttributes transfer type bits. +#[repr(u8)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum EndpointType { + /// Control endpoint. Used for device management. Only the host can initiate requests. Usually + /// used only endpoint 0. + Control = 0b00, + /// Isochronous endpoint. Used for time-critical unreliable data. Not implemented yet. + Isochronous = 0b01, + /// Bulk endpoint. Used for large amounts of best-effort reliable data. + Bulk = 0b10, + /// Interrupt endpoint. Used for small amounts of time-critical reliable data. + Interrupt = 0b11, +} + +/// Type-safe endpoint address. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct EndpointAddress(u8); + +impl From for EndpointAddress { + #[inline] + fn from(addr: u8) -> EndpointAddress { + EndpointAddress(addr) + } +} + +impl From for u8 { + #[inline] + fn from(addr: EndpointAddress) -> u8 { + addr.0 + } +} + +impl EndpointAddress { + const INBITS: u8 = 0x80; + + /// Constructs a new EndpointAddress with the given index and direction. + #[inline] + pub fn from_parts(index: usize, dir: Direction) -> Self { + let dir_u8 = match dir { + Direction::Out => 0x00, + Direction::In => Self::INBITS, + }; + EndpointAddress(index as u8 | dir_u8) + } + + /// Gets the direction part of the address. + #[inline] + pub fn direction(&self) -> Direction { + if (self.0 & Self::INBITS) != 0 { + Direction::In + } else { + Direction::Out + } + } + + /// Returns true if the direction is IN, otherwise false. + #[inline] + pub fn is_in(&self) -> bool { + (self.0 & Self::INBITS) != 0 + } + + /// Returns true if the direction is OUT, otherwise false. + #[inline] + pub fn is_out(&self) -> bool { + (self.0 & Self::INBITS) == 0 + } + + /// Gets the index part of the endpoint address. + #[inline] + pub fn index(&self) -> usize { + (self.0 & !Self::INBITS) as usize + } +} + +/// Information for an endpoint. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct EndpointInfo { + /// Endpoint's address. + pub addr: EndpointAddress, + /// Endpoint's type. + pub ep_type: EndpointType, + /// Max packet size, in bytes. + pub max_packet_size: u16, + /// Polling interval, in milliseconds. + pub interval_ms: u8, +} + +/// Main USB driver trait. +/// +/// Implement this to add support for a new hardware platform. +pub trait Driver<'a> { + /// Type of the OUT endpoints for this driver. + type EndpointOut: EndpointOut + 'a; + /// Type of the IN endpoints for this driver. + type EndpointIn: EndpointIn + 'a; + /// Type of the control pipe for this driver. + type ControlPipe: ControlPipe + 'a; + /// Type for bus control for this driver. + type Bus: Bus + 'a; + + /// Allocates an OUT endpoint. + /// + /// This method is called by the USB stack to allocate endpoints. + /// It can only be called before [`start`](Self::start) is called. + /// + /// # Arguments + /// + /// * `ep_type` - the endpoint's type. + /// * `max_packet_size` - Maximum packet size in bytes. + /// * `interval_ms` - Polling interval parameter for interrupt endpoints. + fn alloc_endpoint_out( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + ) -> Result; + + /// Allocates an IN endpoint. + /// + /// This method is called by the USB stack to allocate endpoints. + /// It can only be called before [`start`](Self::start) is called. + /// + /// # Arguments + /// + /// * `ep_type` - the endpoint's type. + /// * `max_packet_size` - Maximum packet size in bytes. + /// * `interval_ms` - Polling interval parameter for interrupt endpoints. + fn alloc_endpoint_in( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval_ms: u8, + ) -> Result; + + /// Start operation of the USB device. + /// + /// This returns the `Bus` and `ControlPipe` instances that are used to operate + /// the USB device. Additionally, this makes all the previously allocated endpoints + /// start operating. + /// + /// This consumes the `Driver` instance, so it's no longer possible to allocate more + /// endpoints. + fn start(self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe); +} + +/// USB bus trait. +/// +/// This trait provides methods that act on the whole bus. It is kept owned by +/// the main USB task, and used to manage the bus. +pub trait Bus { + /// Enable the USB peripheral. + async fn enable(&mut self); + + /// Disable and powers down the USB peripheral. + async fn disable(&mut self); + + /// Wait for a bus-related event. + /// + /// This method should asynchronously wait for an event to happen, then + /// return it. See [`Event`] for the list of events this method should return. + async fn poll(&mut self) -> Event; + + /// Enable or disable an endpoint. + fn endpoint_set_enabled(&mut self, ep_addr: EndpointAddress, enabled: bool); + + /// Set or clear the STALL condition for an endpoint. + /// + /// If the endpoint is an OUT endpoint, it should be prepared to receive data again. + fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool); + + /// Get whether the STALL condition is set for an endpoint. + fn endpoint_is_stalled(&mut self, ep_addr: EndpointAddress) -> bool; + + /// Simulate a disconnect from the USB bus, causing the host to reset and re-enumerate the + /// device. + /// + /// The default implementation just returns `Unsupported`. + /// + /// # Errors + /// + /// * [`Unsupported`](crate::Unsupported) - This UsbBus implementation doesn't support + /// simulating a disconnect or it has not been enabled at creation time. + fn force_reset(&mut self) -> Result<(), Unsupported> { + Err(Unsupported) + } + + /// Initiate a remote wakeup of the host by the device. + /// + /// # Errors + /// + /// * [`Unsupported`](crate::Unsupported) - This UsbBus implementation doesn't support + /// remote wakeup or it has not been enabled at creation time. + async fn remote_wakeup(&mut self) -> Result<(), Unsupported>; +} + +/// Endpoint trait, common for OUT and IN. +pub trait Endpoint { + /// Get the endpoint address + fn info(&self) -> &EndpointInfo; + + /// Wait for the endpoint to be enabled. + async fn wait_enabled(&mut self); +} + +/// OUT Endpoint trait. +pub trait EndpointOut: Endpoint { + /// Read a single packet of data from the endpoint, and return the actual length of + /// the packet. + /// + /// This should also clear any NAK flags and prepare the endpoint to receive the next packet. + async fn read(&mut self, buf: &mut [u8]) -> Result; +} + +/// USB control pipe trait. +/// +/// The USB control pipe owns both OUT endpoint 0 and IN endpoint 0 in a single +/// unit, and manages them together to implement the control pipe state machine. +/// +/// The reason this is a separate trait instead of using EndpointOut/EndpointIn is that +/// many USB peripherals treat the control pipe endpoints differently (different registers, +/// different procedures), usually to accelerate processing in hardware somehow. A separate +/// trait allows the driver to handle it specially. +/// +/// The call sequences made by the USB stack to the ControlPipe are the following: +/// +/// - control in/out with len=0: +/// +/// ```not_rust +/// setup() +/// (...processing...) +/// accept() or reject() +/// ``` +/// +/// - control out for setting the device address: +/// +/// ```not_rust +/// setup() +/// (...processing...) +/// accept_set_address(addr) or reject() +/// ``` +/// +/// - control out with len != 0: +/// +/// ```not_rust +/// setup() +/// data_out(first=true, last=false) +/// data_out(first=false, last=false) +/// ... +/// data_out(first=false, last=false) +/// data_out(first=false, last=true) +/// (...processing...) +/// accept() or reject() +/// ``` +/// +/// - control in with len != 0, accepted: +/// +/// ```not_rust +/// setup() +/// (...processing...) +/// data_in(first=true, last=false) +/// data_in(first=false, last=false) +/// ... +/// data_in(first=false, last=false) +/// data_in(first=false, last=true) +/// (NO `accept()`!!! This is because calling `data_in` already implies acceptance.) +/// ``` +/// +/// - control in with len != 0, rejected: +/// +/// ```not_rust +/// setup() +/// (...processing...) +/// reject() +/// ``` +/// +/// The driver is responsible for handling the status stage. The stack DOES NOT do zero-length +/// calls to `data_in` or `data_out` for the status zero-length packet. The status stage should +/// be triggered by either `accept()`, or `data_in` with `last = true`. +/// +/// Note that the host can abandon a control request and send a new SETUP packet any time. If +/// a SETUP packet arrives at any time during `data_out`, `data_in`, `accept` or `reject`, +/// the driver must immediately return (with `EndpointError::Disabled` from `data_in`, `data_out`) +/// to let the stack call `setup()` again to start handling the new control request. Not doing +/// so will cause things to get stuck, because the host will never read/send the packet we're +/// waiting for. +pub trait ControlPipe { + /// Maximum packet size for the control pipe + fn max_packet_size(&self) -> usize; + + /// Read a single setup packet from the endpoint. + async fn setup(&mut self) -> [u8; 8]; + + /// Read a DATA OUT packet into `buf` in response to a control write request. + /// + /// Must be called after `setup()` for requests with `direction` of `Out` + /// and `length` greater than zero. + async fn data_out(&mut self, buf: &mut [u8], first: bool, last: bool) -> Result; + + /// Send a DATA IN packet with `data` in response to a control read request. + /// + /// If `last_packet` is true, the STATUS packet will be ACKed following the transfer of `data`. + async fn data_in(&mut self, data: &[u8], first: bool, last: bool) -> Result<(), EndpointError>; + + /// Accept a control request. + /// + /// Causes the STATUS packet for the current request to be ACKed. + async fn accept(&mut self); + + /// Reject a control request. + /// + /// Sets a STALL condition on the pipe to indicate an error. + async fn reject(&mut self); + + /// Accept SET_ADDRESS control and change bus address. + /// + /// For most drivers this function should firstly call `accept()` and then change the bus address. + /// However, there are peripherals (Synopsys USB OTG) that have reverse order. + async fn accept_set_address(&mut self, addr: u8); +} + +/// IN Endpoint trait. +pub trait EndpointIn: Endpoint { + /// Write a single packet of data to the endpoint. + async fn write(&mut self, buf: &[u8]) -> Result<(), EndpointError>; +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Event returned by [`Bus::poll`]. +pub enum Event { + /// The USB reset condition has been detected. + Reset, + + /// A USB suspend request has been detected or, in the case of self-powered devices, the device + /// has been disconnected from the USB bus. + Suspend, + + /// A USB resume request has been detected after being suspended or, in the case of self-powered + /// devices, the device has been connected to the USB bus. + Resume, + + /// The USB power has been detected. + PowerDetected, + + /// The USB power has been removed. Not supported by all devices. + PowerRemoved, +} + +/// Allocating an endpoint failed. +/// +/// This can be due to running out of endpoints, or out of endpoint memory, +/// or because the hardware doesn't support the requested combination of features. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct EndpointAllocError; + +/// Operation is unsupported by the driver. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct Unsupported; + +/// Errors returned by [`EndpointIn::write`] and [`EndpointOut::read`] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum EndpointError { + /// Either the packet to be written is too long to fit in the transmission + /// buffer or the received packet is too long to fit in `buf`. + BufferOverflow, + + /// The endpoint is disabled. + Disabled, +} diff --git a/embassy-usb-hid/Cargo.toml b/embassy-usb-hid/Cargo.toml deleted file mode 100644 index 730351485..000000000 --- a/embassy-usb-hid/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "embassy-usb-hid" -version = "0.1.0" -edition = "2021" - -[package.metadata.embassy_docs] -src_base = "https://github.com/embassy-rs/embassy/blob/embassy-usb-hid-v$VERSION/embassy-usb-hid/src/" -src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-usb-hid/src/" -features = ["defmt"] -target = "thumbv7em-none-eabi" - -[features] -default = ["usbd-hid"] -usbd-hid = ["dep:usbd-hid", "ssmarshal"] - -[dependencies] -embassy-sync = { version = "0.1.0", path = "../embassy-sync" } -embassy-usb = { version = "0.1.0", path = "../embassy-usb" } - -defmt = { version = "0.3", optional = true } -log = { version = "0.4.14", optional = true } -usbd-hid = { version = "0.5.2", optional = true } -ssmarshal = { version = "1.0", default-features = false, optional = true } -futures-util = { version = "0.3.21", default-features = false } diff --git a/embassy-usb-logger/Cargo.toml b/embassy-usb-logger/Cargo.toml new file mode 100644 index 000000000..0f91cd36d --- /dev/null +++ b/embassy-usb-logger/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "embassy-usb-logger" +version = "0.1.0" +edition = "2021" + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-usb-logger-v$VERSION/embassy-usb/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-usb-logger/src/" +target = "thumbv7em-none-eabi" + +[dependencies] +embassy-usb = { version = "0.1.0", path = "../embassy-usb" } +embassy-sync = { version = "0.2.0", path = "../embassy-sync" } +embassy-futures = { version = "0.1.0", path = "../embassy-futures" } +futures = { version = "0.3", default-features = false } +static_cell = "1" +usbd-hid = "0.6.0" +log = "0.4" diff --git a/embassy-usb-logger/README.md b/embassy-usb-logger/README.md new file mode 100644 index 000000000..81b0dcd0e --- /dev/null +++ b/embassy-usb-logger/README.md @@ -0,0 +1,15 @@ +# embassy-usb-logger + +USB implementation of the `log` crate. This logger can be used by any device that implements `embassy-usb`. When running, +it will output all logging done through the `log` facade to the USB serial peripheral. + +## Usage + +Add the following embassy task to your application. The `Driver` type is different depending on which HAL you use. + + ```rust +#[embassy_executor::task] +async fn logger_task(driver: Driver<'static, USB>) { + embassy_usb_logger::run!(1024, log::LevelFilter::Info, driver); +} +``` diff --git a/embassy-usb-logger/src/lib.rs b/embassy-usb-logger/src/lib.rs new file mode 100644 index 000000000..6e50c40ba --- /dev/null +++ b/embassy-usb-logger/src/lib.rs @@ -0,0 +1,145 @@ +#![no_std] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] + +use core::fmt::Write as _; + +use embassy_futures::join::join; +use embassy_sync::pipe::Pipe; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::Driver; +use embassy_usb::{Builder, Config}; +use log::{Metadata, Record}; + +type CS = embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; + +/// The logger state containing buffers that must live as long as the USB peripheral. +pub struct LoggerState<'d> { + state: State<'d>, + device_descriptor: [u8; 32], + config_descriptor: [u8; 128], + bos_descriptor: [u8; 16], + control_buf: [u8; 64], +} + +impl<'d> LoggerState<'d> { + /// Create a new instance of the logger state. + pub fn new() -> Self { + Self { + state: State::new(), + device_descriptor: [0; 32], + config_descriptor: [0; 128], + bos_descriptor: [0; 16], + control_buf: [0; 64], + } + } +} + +/// The logger handle, which contains a pipe with configurable size for buffering log messages. +pub struct UsbLogger { + buffer: Pipe, +} + +impl UsbLogger { + /// Create a new logger instance. + pub const fn new() -> Self { + Self { buffer: Pipe::new() } + } + + /// Run the USB logger using the state and USB driver. Never returns. + pub async fn run<'d, D>(&'d self, state: &'d mut LoggerState<'d>, driver: D) -> ! + where + D: Driver<'d>, + Self: 'd, + { + const MAX_PACKET_SIZE: u8 = 64; + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial logger"); + config.serial_number = None; + config.max_power = 100; + config.max_packet_size_0 = MAX_PACKET_SIZE; + + // Required for windows compatiblity. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + let mut builder = Builder::new( + driver, + config, + &mut state.device_descriptor, + &mut state.config_descriptor, + &mut state.bos_descriptor, + &mut state.control_buf, + ); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state.state, MAX_PACKET_SIZE as u16); + + // Build the builder. + let mut device = builder.build(); + + loop { + let run_fut = device.run(); + let log_fut = async { + let mut rx: [u8; MAX_PACKET_SIZE as usize] = [0; MAX_PACKET_SIZE as usize]; + class.wait_connection().await; + loop { + let len = self.buffer.read(&mut rx[..]).await; + let _ = class.write_packet(&rx[..len]).await; + } + }; + join(run_fut, log_fut).await; + } + } +} + +impl log::Log for UsbLogger { + fn enabled(&self, _metadata: &Metadata) -> bool { + true + } + + fn log(&self, record: &Record) { + if self.enabled(record.metadata()) { + let _ = write!(Writer(&self.buffer), "{}\r\n", record.args()); + } + } + + fn flush(&self) {} +} + +struct Writer<'d, const N: usize>(&'d Pipe); + +impl<'d, const N: usize> core::fmt::Write for Writer<'d, N> { + fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> { + let _ = self.0.try_write(s.as_bytes()); + Ok(()) + } +} + +/// Initialize and run the USB serial logger, never returns. +/// +/// Arguments specify the buffer size, log level and the USB driver, respectively. +/// +/// # Usage +/// +/// ``` +/// embassy_usb_logger::run!(1024, log::LevelFilter::Info, driver); +/// ``` +/// +/// # Safety +/// +/// This macro should only be invoked only once since it is setting the global logging state of the application. +#[macro_export] +macro_rules! run { + ( $x:expr, $l:expr, $p:ident ) => { + static LOGGER: ::embassy_usb_logger::UsbLogger<$x> = ::embassy_usb_logger::UsbLogger::new(); + unsafe { + let _ = ::log::set_logger_racy(&LOGGER).map(|()| log::set_max_level_racy($l)); + } + let _ = LOGGER.run(&mut ::embassy_usb_logger::LoggerState::new(), $p).await; + }; +} diff --git a/embassy-usb-serial/Cargo.toml b/embassy-usb-serial/Cargo.toml deleted file mode 100644 index 9788588e9..000000000 --- a/embassy-usb-serial/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "embassy-usb-serial" -version = "0.1.0" -edition = "2021" - -[package.metadata.embassy_docs] -src_base = "https://github.com/embassy-rs/embassy/blob/embassy-usb-serial-v$VERSION/embassy-usb-serial/src/" -src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-usb-serial/src/" -features = ["defmt"] -target = "thumbv7em-none-eabi" - -[dependencies] -embassy-sync = { version = "0.1.0", path = "../embassy-sync" } -embassy-usb = { version = "0.1.0", path = "../embassy-usb" } - -defmt = { version = "0.3", optional = true } -log = { version = "0.4.14", optional = true } diff --git a/embassy-usb/Cargo.toml b/embassy-usb/Cargo.toml index 8cad4d314..03cf96a1d 100644 --- a/embassy-usb/Cargo.toml +++ b/embassy-usb/Cargo.toml @@ -2,16 +2,52 @@ name = "embassy-usb" version = "0.1.0" edition = "2021" +license = "MIT OR Apache-2.0" [package.metadata.embassy_docs] src_base = "https://github.com/embassy-rs/embassy/blob/embassy-usb-v$VERSION/embassy-usb/src/" src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-usb/src/" -features = ["defmt"] +features = ["defmt", "usbd-hid"] target = "thumbv7em-none-eabi" +[features] +defmt = ["dep:defmt", "embassy-usb-driver/defmt"] +usbd-hid = ["dep:usbd-hid", "dep:ssmarshal"] +msos-descriptor = [] +default = ["usbd-hid"] + +# BEGIN AUTOGENERATED CONFIG FEATURES +# Generated by gen_config.py. DO NOT EDIT. +max-interface-count-1 = [] +max-interface-count-2 = [] +max-interface-count-3 = [] +max-interface-count-4 = [] # Default +max-interface-count-5 = [] +max-interface-count-6 = [] +max-interface-count-7 = [] +max-interface-count-8 = [] + +max-handler-count-1 = [] +max-handler-count-2 = [] +max-handler-count-3 = [] +max-handler-count-4 = [] # Default +max-handler-count-5 = [] +max-handler-count-6 = [] +max-handler-count-7 = [] +max-handler-count-8 = [] + +# END AUTOGENERATED CONFIG FEATURES + [dependencies] embassy-futures = { version = "0.1.0", path = "../embassy-futures" } +embassy-usb-driver = { version = "0.1.0", path = "../embassy-usb-driver" } +embassy-sync = { version = "0.2.0", path = "../embassy-sync" } +embassy-net-driver-channel = { version = "0.1.0", path = "../embassy-net-driver-channel" } defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } -heapless = "0.7.10" \ No newline at end of file +heapless = "0.7.10" + +# for HID +usbd-hid = { version = "0.6.0", optional = true } +ssmarshal = { version = "1.0", default-features = false, optional = true } diff --git a/embassy-usb/README.md b/embassy-usb/README.md new file mode 100644 index 000000000..a3d45b561 --- /dev/null +++ b/embassy-usb/README.md @@ -0,0 +1,44 @@ +# embassy-usb + +TODO crate description + +## Configuration + +`embassy-usb` has some configuration settings that are set at compile time, affecting sizes +and counts of buffers. + +They can be set in two ways: + +- Via Cargo features: enable a feature like `-`. `name` must be in lowercase and +use dashes instead of underscores. For example. `max-interface-count-3`. Only a selection of values +is available, check `Cargo.toml` for the list. +- Via environment variables at build time: set the variable named `EMBASSY_USB_`. For example +`EMBASSY_USB_MAX_INTERFACE_COUNT=3 cargo build`. You can also set them in the `[env]` section of `.cargo/config.toml`. +Any value can be set, unlike with Cargo features. + +Environment variables take precedence over Cargo features. If two Cargo features are enabled for the same setting +with different values, compilation fails. + +### `MAX_INTERFACE_COUNT` + +Max amount of interfaces that can be created in one device. Default: 4. + + +## Interoperability + +This crate can run on any executor. + +## Minimum supported Rust version (MSRV) + +This crate requires nightly Rust, due to using "async fn in trait" support. + +## License + +This work is licensed under either of + +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or + ) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. + diff --git a/embassy-usb/build.rs b/embassy-usb/build.rs new file mode 100644 index 000000000..33d32f7d3 --- /dev/null +++ b/embassy-usb/build.rs @@ -0,0 +1,94 @@ +use std::collections::HashMap; +use std::fmt::Write; +use std::path::PathBuf; +use std::{env, fs}; + +static CONFIGS: &[(&str, usize)] = &[ + // BEGIN AUTOGENERATED CONFIG FEATURES + // Generated by gen_config.py. DO NOT EDIT. + ("MAX_INTERFACE_COUNT", 4), + ("MAX_HANDLER_COUNT", 4), + // END AUTOGENERATED CONFIG FEATURES +]; + +struct ConfigState { + value: usize, + seen_feature: bool, + seen_env: bool, +} + +fn main() { + let crate_name = env::var("CARGO_PKG_NAME") + .unwrap() + .to_ascii_uppercase() + .replace('-', "_"); + + // only rebuild if build.rs changed. Otherwise Cargo will rebuild if any + // other file changed. + println!("cargo:rerun-if-changed=build.rs"); + + // Rebuild if config envvar changed. + for (name, _) in CONFIGS { + println!("cargo:rerun-if-env-changed={crate_name}_{name}"); + } + + let mut configs = HashMap::new(); + for (name, default) in CONFIGS { + configs.insert( + *name, + ConfigState { + value: *default, + seen_env: false, + seen_feature: false, + }, + ); + } + + let prefix = format!("{crate_name}_"); + for (var, value) in env::vars() { + if let Some(name) = var.strip_prefix(&prefix) { + let Some(cfg) = configs.get_mut(name) else { + panic!("Unknown env var {name}") + }; + + let Ok(value) = value.parse::() else { + panic!("Invalid value for env var {name}: {value}") + }; + + cfg.value = value; + cfg.seen_env = true; + } + + if let Some(feature) = var.strip_prefix("CARGO_FEATURE_") { + if let Some(i) = feature.rfind('_') { + let name = &feature[..i]; + let value = &feature[i + 1..]; + if let Some(cfg) = configs.get_mut(name) { + let Ok(value) = value.parse::() else { + panic!("Invalid value for feature {name}: {value}") + }; + + // envvars take priority. + if !cfg.seen_env { + if cfg.seen_feature { + panic!("multiple values set for feature {}: {} and {}", name, cfg.value, value); + } + + cfg.value = value; + cfg.seen_feature = true; + } + } + } + } + } + + let mut data = String::new(); + + for (name, cfg) in &configs { + writeln!(&mut data, "pub const {}: usize = {};", name, cfg.value).unwrap(); + } + + let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); + let out_file = out_dir.join("config.rs").to_string_lossy().to_string(); + fs::write(out_file, data).unwrap(); +} diff --git a/embassy-usb/gen_config.py b/embassy-usb/gen_config.py new file mode 100644 index 000000000..67ce359cd --- /dev/null +++ b/embassy-usb/gen_config.py @@ -0,0 +1,74 @@ +import os + +abspath = os.path.abspath(__file__) +dname = os.path.dirname(abspath) +os.chdir(dname) + +features = [] + + +def feature(name, default, min, max, pow2=None): + vals = set() + val = min + while val <= max: + vals.add(val) + if pow2 == True or (isinstance(pow2, int) and val >= pow2): + val *= 2 + else: + val += 1 + vals.add(default) + + features.append( + { + "name": name, + "default": default, + "vals": sorted(list(vals)), + } + ) + + +feature("max_interface_count", default=4, min=1, max=8) +feature("max_handler_count", default=4, min=1, max=8) + +# ========= Update Cargo.toml + +things = "" +for f in features: + name = f["name"].replace("_", "-") + for val in f["vals"]: + things += f"{name}-{val} = []" + if val == f["default"]: + things += " # Default" + things += "\n" + things += "\n" + +SEPARATOR_START = "# BEGIN AUTOGENERATED CONFIG FEATURES\n" +SEPARATOR_END = "# END AUTOGENERATED CONFIG FEATURES\n" +HELP = "# Generated by gen_config.py. DO NOT EDIT.\n" +with open("Cargo.toml", "r") as f: + data = f.read() +before, data = data.split(SEPARATOR_START, maxsplit=1) +_, after = data.split(SEPARATOR_END, maxsplit=1) +data = before + SEPARATOR_START + HELP + things + SEPARATOR_END + after +with open("Cargo.toml", "w") as f: + f.write(data) + + +# ========= Update build.rs + +things = "" +for f in features: + name = f["name"].upper() + things += f' ("{name}", {f["default"]}),\n' + +SEPARATOR_START = "// BEGIN AUTOGENERATED CONFIG FEATURES\n" +SEPARATOR_END = "// END AUTOGENERATED CONFIG FEATURES\n" +HELP = " // Generated by gen_config.py. DO NOT EDIT.\n" +with open("build.rs", "r") as f: + data = f.read() +before, data = data.split(SEPARATOR_START, maxsplit=1) +_, after = data.split(SEPARATOR_END, maxsplit=1) +data = before + SEPARATOR_START + HELP + \ + things + " " + SEPARATOR_END + after +with open("build.rs", "w") as f: + f.write(data) diff --git a/embassy-usb/src/builder.rs b/embassy-usb/src/builder.rs index 6be88bc76..6b68bcd7b 100644 --- a/embassy-usb/src/builder.rs +++ b/embassy-usb/src/builder.rs @@ -1,11 +1,12 @@ use heapless::Vec; -use super::control::ControlHandler; -use super::descriptor::{BosWriter, DescriptorWriter}; -use super::driver::{Driver, Endpoint}; -use super::types::*; -use super::{DeviceStateHandler, UsbDevice, MAX_INTERFACE_COUNT}; -use crate::{Interface, STRING_INDEX_CUSTOM_START}; +use crate::config::*; +use crate::descriptor::{BosWriter, DescriptorWriter}; +use crate::driver::{Driver, Endpoint, EndpointType}; +#[cfg(feature = "msos-descriptor")] +use crate::msos::{DeviceLevelDescriptor, FunctionLevelDescriptor, MsOsDescriptorWriter}; +use crate::types::*; +use crate::{Handler, Interface, UsbDevice, MAX_INTERFACE_COUNT, STRING_INDEX_CUSTOM_START}; #[derive(Debug, Copy, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -121,8 +122,8 @@ impl<'a> Config<'a> { /// [`UsbDevice`] builder. pub struct Builder<'d, D: Driver<'d>> { config: Config<'d>, - handler: Option<&'d dyn DeviceStateHandler>, - interfaces: Vec, MAX_INTERFACE_COUNT>, + handlers: Vec<&'d mut dyn Handler, MAX_HANDLER_COUNT>, + interfaces: Vec, control_buf: &'d mut [u8], driver: D, @@ -131,6 +132,9 @@ pub struct Builder<'d, D: Driver<'d>> { device_descriptor: DescriptorWriter<'d>, config_descriptor: DescriptorWriter<'d>, bos_descriptor: BosWriter<'d>, + + #[cfg(feature = "msos-descriptor")] + msos_descriptor: MsOsDescriptorWriter<'d>, } impl<'d, D: Driver<'d>> Builder<'d, D> { @@ -145,8 +149,8 @@ impl<'d, D: Driver<'d>> Builder<'d, D> { device_descriptor_buf: &'d mut [u8], config_descriptor_buf: &'d mut [u8], bos_descriptor_buf: &'d mut [u8], + #[cfg(feature = "msos-descriptor")] msos_descriptor_buf: &'d mut [u8], control_buf: &'d mut [u8], - handler: Option<&'d dyn DeviceStateHandler>, ) -> Self { // Magic values specified in USB-IF ECN on IADs. if config.composite_with_iads @@ -174,32 +178,48 @@ impl<'d, D: Driver<'d>> Builder<'d, D> { Builder { driver, - handler, config, interfaces: Vec::new(), + handlers: Vec::new(), control_buf, next_string_index: STRING_INDEX_CUSTOM_START, device_descriptor, config_descriptor, bos_descriptor, + + #[cfg(feature = "msos-descriptor")] + msos_descriptor: MsOsDescriptorWriter::new(msos_descriptor_buf), } } /// Creates the [`UsbDevice`] instance with the configuration in this builder. pub fn build(mut self) -> UsbDevice<'d, D> { + #[cfg(feature = "msos-descriptor")] + let msos_descriptor = self.msos_descriptor.build(&mut self.bos_descriptor); + self.config_descriptor.end_configuration(); self.bos_descriptor.end_bos(); + // Log the number of allocator bytes actually used in descriptor buffers + info!("USB: device_descriptor used: {}", self.device_descriptor.position()); + info!("USB: config_descriptor used: {}", self.config_descriptor.position()); + info!("USB: bos_descriptor used: {}", self.bos_descriptor.writer.position()); + #[cfg(feature = "msos-descriptor")] + info!("USB: msos_descriptor used: {}", msos_descriptor.len()); + info!("USB: control_buf size: {}", self.control_buf.len()); + UsbDevice::build( self.driver, self.config, - self.handler, + self.handlers, self.device_descriptor.into_buf(), self.config_descriptor.into_buf(), self.bos_descriptor.writer.into_buf(), self.interfaces, self.control_buf, + #[cfg(feature = "msos-descriptor")] + msos_descriptor, ) } @@ -216,14 +236,10 @@ impl<'d, D: Driver<'d>> Builder<'d, D> { /// /// If it's not set, no IAD descriptor is added. pub fn function(&mut self, class: u8, subclass: u8, protocol: u8) -> FunctionBuilder<'_, 'd, D> { + let first_interface = InterfaceNumber::new(self.interfaces.len() as u8); let iface_count_index = if self.config.composite_with_iads { - self.config_descriptor.iad( - InterfaceNumber::new(self.interfaces.len() as _), - 0, - class, - subclass, - protocol, - ); + self.config_descriptor + .iad(first_interface, 0, class, subclass, protocol); Some(self.config_descriptor.position() - 5) } else { @@ -233,8 +249,52 @@ impl<'d, D: Driver<'d>> Builder<'d, D> { FunctionBuilder { builder: self, iface_count_index, + + #[cfg(feature = "msos-descriptor")] + first_interface, } } + + /// Add a Handler. + /// + /// The Handler is called on some USB bus events, and to handle all control requests not already + /// handled by the USB stack. + pub fn handler(&mut self, handler: &'d mut dyn Handler) { + if self.handlers.push(handler).is_err() { + panic!( + "embassy-usb: handler list full. Increase the `max_handler_count` compile-time setting. Current value: {}", + MAX_HANDLER_COUNT + ) + } + } + + /// Allocates a new string index. + pub fn string(&mut self) -> StringIndex { + let index = self.next_string_index; + self.next_string_index += 1; + StringIndex::new(index) + } + + #[cfg(feature = "msos-descriptor")] + /// Add an MS OS 2.0 Descriptor Set. + /// + /// Panics if called more than once. + pub fn msos_descriptor(&mut self, windows_version: u32, vendor_code: u8) { + self.msos_descriptor.header(windows_version, vendor_code); + } + + #[cfg(feature = "msos-descriptor")] + /// Add an MS OS 2.0 Device Level Feature Descriptor. + pub fn msos_feature(&mut self, desc: T) { + self.msos_descriptor.device_feature(desc); + } + + #[cfg(feature = "msos-descriptor")] + /// Gets the underlying [`MsOsDescriptorWriter`] to allow adding subsets and features for classes that + /// do not add their own. + pub fn msos_writer(&mut self) -> &mut MsOsDescriptorWriter<'d> { + &mut self.msos_descriptor + } } /// Function builder. @@ -245,6 +305,16 @@ impl<'d, D: Driver<'d>> Builder<'d, D> { pub struct FunctionBuilder<'a, 'd, D: Driver<'d>> { builder: &'a mut Builder<'d, D>, iface_count_index: Option, + + #[cfg(feature = "msos-descriptor")] + first_interface: InterfaceNumber, +} + +impl<'a, 'd, D: Driver<'d>> Drop for FunctionBuilder<'a, 'd, D> { + fn drop(&mut self) { + #[cfg(feature = "msos-descriptor")] + self.builder.msos_descriptor.end_function(); + } } impl<'a, 'd, D: Driver<'d>> FunctionBuilder<'a, 'd, D> { @@ -258,14 +328,15 @@ impl<'a, 'd, D: Driver<'d>> FunctionBuilder<'a, 'd, D> { let number = self.builder.interfaces.len() as _; let iface = Interface { - handler: None, current_alt_setting: 0, num_alt_settings: 0, - num_strings: 0, }; if self.builder.interfaces.push(iface).is_err() { - panic!("max interface count reached") + panic!( + "embassy-usb: interface list full. Increase the `max_interface_count` compile-time setting. Current value: {}", + MAX_INTERFACE_COUNT + ) } InterfaceBuilder { @@ -274,6 +345,21 @@ impl<'a, 'd, D: Driver<'d>> FunctionBuilder<'a, 'd, D> { next_alt_setting_number: 0, } } + + #[cfg(feature = "msos-descriptor")] + /// Add an MS OS 2.0 Function Level Feature Descriptor. + pub fn msos_feature(&mut self, desc: T) { + if !self.builder.msos_descriptor.is_in_config_subset() { + self.builder.msos_descriptor.configuration(0); + } + + if !self.builder.msos_descriptor.is_in_function_subset() { + self.builder.msos_descriptor.function(self.first_interface); + } + + #[cfg(feature = "msos-descriptor")] + self.builder.msos_descriptor.function_feature(desc); + } } /// Interface builder. @@ -289,17 +375,9 @@ impl<'a, 'd, D: Driver<'d>> InterfaceBuilder<'a, 'd, D> { self.interface_number } - pub fn handler(&mut self, handler: &'d mut dyn ControlHandler) { - self.builder.interfaces[self.interface_number.0 as usize].handler = Some(handler); - } - /// Allocates a new string index. pub fn string(&mut self) -> StringIndex { - let index = self.builder.next_string_index; - self.builder.next_string_index += 1; - self.builder.interfaces[self.interface_number.0 as usize].num_strings += 1; - - StringIndex::new(index) + self.builder.string() } /// Add an alternate setting to the interface and write its descriptor. @@ -307,14 +385,25 @@ impl<'a, 'd, D: Driver<'d>> InterfaceBuilder<'a, 'd, D> { /// Alternate setting numbers are guaranteed to be allocated consecutively, starting from 0. /// /// The first alternate setting, with number 0, is the default one. - pub fn alt_setting(&mut self, class: u8, subclass: u8, protocol: u8) -> InterfaceAltBuilder<'_, 'd, D> { + pub fn alt_setting( + &mut self, + class: u8, + subclass: u8, + protocol: u8, + interface_string: Option, + ) -> InterfaceAltBuilder<'_, 'd, D> { let number = self.next_alt_setting_number; self.next_alt_setting_number += 1; self.builder.interfaces[self.interface_number.0 as usize].num_alt_settings += 1; - self.builder - .config_descriptor - .interface_alt(self.interface_number, number, class, subclass, protocol, None); + self.builder.config_descriptor.interface_alt( + self.interface_number, + number, + class, + subclass, + protocol, + interface_string, + ); InterfaceAltBuilder { builder: self.builder, @@ -350,11 +439,11 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> { self.builder.config_descriptor.write(descriptor_type, descriptor) } - fn endpoint_in(&mut self, ep_type: EndpointType, max_packet_size: u16, interval: u8) -> D::EndpointIn { + fn endpoint_in(&mut self, ep_type: EndpointType, max_packet_size: u16, interval_ms: u8) -> D::EndpointIn { let ep = self .builder .driver - .alloc_endpoint_in(ep_type, max_packet_size, interval) + .alloc_endpoint_in(ep_type, max_packet_size, interval_ms) .expect("alloc_endpoint_in failed"); self.builder.config_descriptor.endpoint(ep.info()); @@ -362,11 +451,11 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> { ep } - fn endpoint_out(&mut self, ep_type: EndpointType, max_packet_size: u16, interval: u8) -> D::EndpointOut { + fn endpoint_out(&mut self, ep_type: EndpointType, max_packet_size: u16, interval_ms: u8) -> D::EndpointOut { let ep = self .builder .driver - .alloc_endpoint_out(ep_type, max_packet_size, interval) + .alloc_endpoint_out(ep_type, max_packet_size, interval_ms) .expect("alloc_endpoint_out failed"); self.builder.config_descriptor.endpoint(ep.info()); @@ -394,12 +483,25 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> { /// /// Descriptors are written in the order builder functions are called. Note that some /// classes care about the order. - pub fn endpoint_interrupt_in(&mut self, max_packet_size: u16, interval: u8) -> D::EndpointIn { - self.endpoint_in(EndpointType::Interrupt, max_packet_size, interval) + pub fn endpoint_interrupt_in(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointIn { + self.endpoint_in(EndpointType::Interrupt, max_packet_size, interval_ms) } /// Allocate a INTERRUPT OUT endpoint and write its descriptor. - pub fn endpoint_interrupt_out(&mut self, max_packet_size: u16, interval: u8) -> D::EndpointOut { - self.endpoint_out(EndpointType::Interrupt, max_packet_size, interval) + pub fn endpoint_interrupt_out(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointOut { + self.endpoint_out(EndpointType::Interrupt, max_packet_size, interval_ms) + } + + /// Allocate a ISOCHRONOUS IN endpoint and write its descriptor. + /// + /// Descriptors are written in the order builder functions are called. Note that some + /// classes care about the order. + pub fn endpoint_isochronous_in(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointIn { + self.endpoint_in(EndpointType::Isochronous, max_packet_size, interval_ms) + } + + /// Allocate a ISOCHRONOUS OUT endpoint and write its descriptor. + pub fn endpoint_isochronous_out(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointOut { + self.endpoint_out(EndpointType::Isochronous, max_packet_size, interval_ms) } } diff --git a/embassy-usb-serial/src/lib.rs b/embassy-usb/src/class/cdc_acm.rs similarity index 66% rename from embassy-usb-serial/src/lib.rs rename to embassy-usb/src/class/cdc_acm.rs index f3de2ec1b..a341e10da 100644 --- a/embassy-usb-serial/src/lib.rs +++ b/embassy-usb/src/class/cdc_acm.rs @@ -1,19 +1,15 @@ -#![no_std] -#![feature(generic_associated_types)] -#![feature(type_alias_impl_trait)] - -// This mod MUST go first, so that the others see its macros. -pub(crate) mod fmt; +//! CDC-ACM class implementation, aka Serial over USB. use core::cell::Cell; use core::mem::{self, MaybeUninit}; use core::sync::atomic::{AtomicBool, Ordering}; use embassy_sync::blocking_mutex::CriticalSectionMutex; -use embassy_usb::control::{self, ControlHandler, InResponse, OutResponse, Request}; -use embassy_usb::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; -use embassy_usb::types::*; -use embassy_usb::Builder; + +use crate::control::{self, InResponse, OutResponse, Recipient, Request, RequestType}; +use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; +use crate::types::*; +use crate::{Builder, Handler}; /// This should be used as `device_class` when building the `UsbDevice`. pub const USB_CLASS_CDC: u8 = 0x02; @@ -24,7 +20,6 @@ const CDC_PROTOCOL_NONE: u8 = 0x00; const CS_INTERFACE: u8 = 0x24; const CDC_TYPE_HEADER: u8 = 0x00; -const CDC_TYPE_CALL_MANAGEMENT: u8 = 0x01; const CDC_TYPE_ACM: u8 = 0x02; const CDC_TYPE_UNION: u8 = 0x06; @@ -35,12 +30,14 @@ const REQ_SET_LINE_CODING: u8 = 0x20; const REQ_GET_LINE_CODING: u8 = 0x21; const REQ_SET_CONTROL_LINE_STATE: u8 = 0x22; +/// Internal state for CDC-ACM pub struct State<'a> { control: MaybeUninit>, shared: ControlShared, } impl<'a> State<'a> { + /// Create a new `State`. pub fn new() -> Self { Self { control: MaybeUninit::uninit(), @@ -70,6 +67,7 @@ pub struct CdcAcmClass<'d, D: Driver<'d>> { } struct Control<'a> { + comm_if: InterfaceNumber, shared: &'a ControlShared, } @@ -101,7 +99,7 @@ impl<'a> Control<'a> { } } -impl<'d> ControlHandler for Control<'d> { +impl<'d> Handler for Control<'d> { fn reset(&mut self) { let shared = self.shared(); shared.line_coding.lock(|x| x.set(LineCoding::default())); @@ -109,12 +107,18 @@ impl<'d> ControlHandler for Control<'d> { shared.rts.store(false, Ordering::Relaxed); } - fn control_out(&mut self, req: control::Request, data: &[u8]) -> OutResponse { + fn control_out(&mut self, req: control::Request, data: &[u8]) -> Option { + if (req.request_type, req.recipient, req.index) + != (RequestType::Class, Recipient::Interface, self.comm_if.0 as u16) + { + return None; + } + match req.request { REQ_SEND_ENCAPSULATED_COMMAND => { // We don't actually support encapsulated commands but pretend we do for standards // compatibility. - OutResponse::Accepted + Some(OutResponse::Accepted) } REQ_SET_LINE_CODING if data.len() >= 7 => { let coding = LineCoding { @@ -126,7 +130,7 @@ impl<'d> ControlHandler for Control<'d> { self.shared().line_coding.lock(|x| x.set(coding)); debug!("Set line coding to: {:?}", coding); - OutResponse::Accepted + Some(OutResponse::Accepted) } REQ_SET_CONTROL_LINE_STATE => { let dtr = (req.value & 0x0001) != 0; @@ -137,13 +141,19 @@ impl<'d> ControlHandler for Control<'d> { shared.rts.store(rts, Ordering::Relaxed); debug!("Set dtr {}, rts {}", dtr, rts); - OutResponse::Accepted + Some(OutResponse::Accepted) } - _ => OutResponse::Rejected, + _ => Some(OutResponse::Rejected), } } - fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { + fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> Option> { + if (req.request_type, req.recipient, req.index) + != (RequestType::Class, Recipient::Interface, self.comm_if.0 as u16) + { + return None; + } + match req.request { // REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below. REQ_GET_LINE_CODING if req.length == 7 => { @@ -154,9 +164,9 @@ impl<'d> ControlHandler for Control<'d> { buf[4] = coding.stop_bits as u8; buf[5] = coding.parity_type as u8; buf[6] = coding.data_bits; - InResponse::Accepted(&buf[0..7]) + Some(InResponse::Accepted(&buf[0..7])) } - _ => InResponse::Rejected, + _ => Some(InResponse::Rejected), } } } @@ -165,20 +175,15 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { /// Creates a new CdcAcmClass with the provided UsbBus and max_packet_size in bytes. For /// full-speed devices, max_packet_size has to be one of 8, 16, 32 or 64. pub fn new(builder: &mut Builder<'d, D>, state: &'d mut State<'d>, max_packet_size: u16) -> Self { - let control = state.control.write(Control { shared: &state.shared }); - - let control_shared = &state.shared; - assert!(builder.control_buf_len() >= 7); let mut func = builder.function(USB_CLASS_CDC, CDC_SUBCLASS_ACM, CDC_PROTOCOL_NONE); // Control interface let mut iface = func.interface(); - iface.handler(control); let comm_if = iface.interface_number(); let data_if = u8::from(comm_if) + 1; - let mut alt = iface.alt_setting(USB_CLASS_CDC, CDC_SUBCLASS_ACM, CDC_PROTOCOL_NONE); + let mut alt = iface.alt_setting(USB_CLASS_CDC, CDC_SUBCLASS_ACM, CDC_PROTOCOL_NONE, None); alt.descriptor( CS_INTERFACE, @@ -192,7 +197,10 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { CS_INTERFACE, &[ CDC_TYPE_ACM, // bDescriptorSubtype - 0x00, // bmCapabilities + 0x02, // bmCapabilities: + // D1: Device supports the request combination of + // Set_Line_Coding, Set_Control_Line_State, Get_Line_Coding, + // and the Notification Serial_State. ], ); alt.descriptor( @@ -203,24 +211,26 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { data_if.into(), // bSubordinateInterface ], ); - alt.descriptor( - CS_INTERFACE, - &[ - CDC_TYPE_CALL_MANAGEMENT, // bDescriptorSubtype - 0x00, // bmCapabilities - data_if.into(), // bDataInterface - ], - ); let comm_ep = alt.endpoint_interrupt_in(8, 255); // Data interface let mut iface = func.interface(); let data_if = iface.interface_number(); - let mut alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NONE); + let mut alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NONE, None); let read_ep = alt.endpoint_bulk_out(max_packet_size); let write_ep = alt.endpoint_bulk_in(max_packet_size); + drop(func); + + let control = state.control.write(Control { + shared: &state.shared, + comm_if, + }); + builder.handler(control); + + let control_shared = &state.shared; + CdcAcmClass { _comm_ep: comm_ep, _data_if: data_if, @@ -266,10 +276,110 @@ impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { pub async fn wait_connection(&mut self) { self.read_ep.wait_enabled().await } + + /// Split the class into a sender and receiver. + /// + /// This allows concurrently sending and receiving packets from separate tasks. + pub fn split(self) -> (Sender<'d, D>, Receiver<'d, D>) { + ( + Sender { + write_ep: self.write_ep, + control: self.control, + }, + Receiver { + read_ep: self.read_ep, + control: self.control, + }, + ) + } +} + +/// CDC ACM class packet sender. +/// +/// You can obtain a `Sender` with [`CdcAcmClass::split`] +pub struct Sender<'d, D: Driver<'d>> { + write_ep: D::EndpointIn, + control: &'d ControlShared, +} + +impl<'d, D: Driver<'d>> Sender<'d, D> { + /// Gets the maximum packet size in bytes. + pub fn max_packet_size(&self) -> u16 { + // The size is the same for both endpoints. + self.write_ep.info().max_packet_size + } + + /// Gets the current line coding. The line coding contains information that's mainly relevant + /// for USB to UART serial port emulators, and can be ignored if not relevant. + pub fn line_coding(&self) -> LineCoding { + self.control.line_coding.lock(|x| x.get()) + } + + /// Gets the DTR (data terminal ready) state + pub fn dtr(&self) -> bool { + self.control.dtr.load(Ordering::Relaxed) + } + + /// Gets the RTS (request to send) state + pub fn rts(&self) -> bool { + self.control.rts.load(Ordering::Relaxed) + } + + /// Writes a single packet into the IN endpoint. + pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> { + self.write_ep.write(data).await + } + + /// Waits for the USB host to enable this interface + pub async fn wait_connection(&mut self) { + self.write_ep.wait_enabled().await + } +} + +/// CDC ACM class packet receiver. +/// +/// You can obtain a `Receiver` with [`CdcAcmClass::split`] +pub struct Receiver<'d, D: Driver<'d>> { + read_ep: D::EndpointOut, + control: &'d ControlShared, +} + +impl<'d, D: Driver<'d>> Receiver<'d, D> { + /// Gets the maximum packet size in bytes. + pub fn max_packet_size(&self) -> u16 { + // The size is the same for both endpoints. + self.read_ep.info().max_packet_size + } + + /// Gets the current line coding. The line coding contains information that's mainly relevant + /// for USB to UART serial port emulators, and can be ignored if not relevant. + pub fn line_coding(&self) -> LineCoding { + self.control.line_coding.lock(|x| x.get()) + } + + /// Gets the DTR (data terminal ready) state + pub fn dtr(&self) -> bool { + self.control.dtr.load(Ordering::Relaxed) + } + + /// Gets the RTS (request to send) state + pub fn rts(&self) -> bool { + self.control.rts.load(Ordering::Relaxed) + } + + /// Reads a single packet from the OUT endpoint. + pub async fn read_packet(&mut self, data: &mut [u8]) -> Result { + self.read_ep.read(data).await + } + + /// Waits for the USB host to enable this interface + pub async fn wait_connection(&mut self) { + self.read_ep.wait_enabled().await + } } /// Number of stop bits for LineCoding -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum StopBits { /// 1 stop bit @@ -293,13 +403,18 @@ impl From for StopBits { } /// Parity for LineCoding -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ParityType { + /// No parity bit. None = 0, + /// Parity bit is 1 if the amount of `1` bits in the data byte is odd. Odd = 1, + /// Parity bit is 1 if the amount of `1` bits in the data byte is even. Even = 2, + /// Parity bit is always 1 Mark = 3, + /// Parity bit is always 0 Space = 4, } @@ -317,7 +432,7 @@ impl From for ParityType { /// /// This is provided by the host for specifying the standard UART parameters such as baud rate. Can /// be ignored if you don't plan to interface with a physical UART. -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct LineCoding { stop_bits: StopBits, diff --git a/embassy-usb/src/class/cdc_ncm/embassy_net.rs b/embassy-usb/src/class/cdc_ncm/embassy_net.rs new file mode 100644 index 000000000..670709021 --- /dev/null +++ b/embassy-usb/src/class/cdc_ncm/embassy_net.rs @@ -0,0 +1,101 @@ +//! [`embassy-net`](https://crates.io/crates/embassy-net) driver for the CDC-NCM class. + +use embassy_futures::select::{select, Either}; +use embassy_net_driver_channel as ch; +use embassy_net_driver_channel::driver::LinkState; +use embassy_usb_driver::Driver; + +use super::{CdcNcmClass, Receiver, Sender}; + +/// Internal state for the embassy-net integration. +pub struct State { + ch_state: ch::State, +} + +impl State { + /// Create a new `State`. + pub const fn new() -> Self { + Self { + ch_state: ch::State::new(), + } + } +} + +/// Background runner for the CDC-NCM class. +/// +/// You must call `.run()` in a background task for the class to operate. +pub struct Runner<'d, D: Driver<'d>, const MTU: usize> { + tx_usb: Sender<'d, D>, + rx_usb: Receiver<'d, D>, + ch: ch::Runner<'d, MTU>, +} + +impl<'d, D: Driver<'d>, const MTU: usize> Runner<'d, D, MTU> { + /// Run the CDC-NCM class. + /// + /// You must call this in a background task for the class to operate. + pub async fn run(mut self) -> ! { + let (state_chan, mut rx_chan, mut tx_chan) = self.ch.split(); + let rx_fut = async move { + loop { + trace!("WAITING for connection"); + state_chan.set_link_state(LinkState::Down); + + self.rx_usb.wait_connection().await.unwrap(); + + trace!("Connected"); + state_chan.set_link_state(LinkState::Up); + + loop { + let p = rx_chan.rx_buf().await; + match self.rx_usb.read_packet(p).await { + Ok(n) => rx_chan.rx_done(n), + Err(e) => { + warn!("error reading packet: {:?}", e); + break; + } + }; + } + } + }; + let tx_fut = async move { + loop { + let p = tx_chan.tx_buf().await; + if let Err(e) = self.tx_usb.write_packet(p).await { + warn!("Failed to TX packet: {:?}", e); + } + tx_chan.tx_done(); + } + }; + match select(rx_fut, tx_fut).await { + Either::First(x) => x, + Either::Second(x) => x, + } + } +} + +// would be cool to use a TAIT here, but it gives a "may not live long enough". rustc bug? +//pub type Device<'d, const MTU: usize> = impl embassy_net_driver_channel::driver::Driver + 'd; +/// Type alias for the embassy-net driver for CDC-NCM. +pub type Device<'d, const MTU: usize> = embassy_net_driver_channel::Device<'d, MTU>; + +impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> { + /// Obtain a driver for using the CDC-NCM class with [`embassy-net`](https://crates.io/crates/embassy-net). + pub fn into_embassy_net_device( + self, + state: &'d mut State, + ethernet_address: [u8; 6], + ) -> (Runner<'d, D, MTU>, Device<'d, MTU>) { + let (tx_usb, rx_usb) = self.split(); + let (runner, device) = ch::new(&mut state.ch_state, ethernet_address); + + ( + Runner { + tx_usb, + rx_usb, + ch: runner, + }, + device, + ) + } +} diff --git a/embassy-usb-ncm/src/lib.rs b/embassy-usb/src/class/cdc_ncm/mod.rs similarity index 81% rename from embassy-usb-ncm/src/lib.rs rename to embassy-usb/src/class/cdc_ncm/mod.rs index e796af28f..fcfa0bfcd 100644 --- a/embassy-usb-ncm/src/lib.rs +++ b/embassy-usb/src/class/cdc_ncm/mod.rs @@ -1,15 +1,28 @@ -#![no_std] - -// This mod MUST go first, so that the others see its macros. -pub(crate) mod fmt; +//! CDC-NCM class implementation, aka Ethernet over USB. +//! +//! # Compatibility +//! +//! Windows: NOT supported in Windows 10 (though there's apparently a driver you can install?). Supported out of the box in Windows 11. +//! +//! Linux: Well-supported since forever. +//! +//! Android: Support for CDC-NCM is spotty and varies across manufacturers. +//! +//! - On Pixel 4a, it refused to work on Android 11, worked on Android 12. +//! - if the host's MAC address has the "locally-administered" bit set (bit 1 of first byte), +//! it doesn't work! The "Ethernet tethering" option in settings doesn't get enabled. +//! This is due to regex spaghetti: +//! and this nonsense in the linux kernel: use core::intrinsics::copy_nonoverlapping; use core::mem::{size_of, MaybeUninit}; -use embassy_usb::control::{self, ControlHandler, InResponse, OutResponse, Request}; -use embassy_usb::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; -use embassy_usb::types::*; -use embassy_usb::Builder; +use crate::control::{self, InResponse, OutResponse, Recipient, Request, RequestType}; +use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; +use crate::types::*; +use crate::{Builder, Handler}; + +pub mod embassy_net; /// This should be used as `device_class` when building the `UsbDevice`. pub const USB_CLASS_CDC: u8 = 0x02; @@ -102,17 +115,17 @@ fn byteify(buf: &mut [u8], data: T) -> &[u8] { &buf[..len] } +/// Internal state for the CDC-NCM class. pub struct State<'a> { - comm_control: MaybeUninit>, - data_control: MaybeUninit, + control: MaybeUninit>, shared: ControlShared, } impl<'a> State<'a> { + /// Create a new `State`. pub fn new() -> Self { Self { - comm_control: MaybeUninit::uninit(), - data_control: MaybeUninit::uninit(), + control: MaybeUninit::uninit(), shared: Default::default(), } } @@ -129,29 +142,55 @@ impl Default for ControlShared { } } -struct CommControl<'a> { +struct Control<'a> { mac_addr_string: StringIndex, shared: &'a ControlShared, mac_addr_str: [u8; 12], + comm_if: InterfaceNumber, + data_if: InterfaceNumber, } -impl<'d> ControlHandler for CommControl<'d> { - fn control_out(&mut self, req: control::Request, _data: &[u8]) -> OutResponse { +impl<'d> Handler for Control<'d> { + fn set_alternate_setting(&mut self, iface: InterfaceNumber, alternate_setting: u8) { + if iface != self.data_if { + return; + } + + match alternate_setting { + ALTERNATE_SETTING_ENABLED => info!("ncm: interface enabled"), + ALTERNATE_SETTING_DISABLED => info!("ncm: interface disabled"), + _ => unreachable!(), + } + } + + fn control_out(&mut self, req: control::Request, _data: &[u8]) -> Option { + if (req.request_type, req.recipient, req.index) + != (RequestType::Class, Recipient::Interface, self.comm_if.0 as u16) + { + return None; + } + match req.request { REQ_SEND_ENCAPSULATED_COMMAND => { // We don't actually support encapsulated commands but pretend we do for standards // compatibility. - OutResponse::Accepted + Some(OutResponse::Accepted) } REQ_SET_NTB_INPUT_SIZE => { // TODO - OutResponse::Accepted + Some(OutResponse::Accepted) } - _ => OutResponse::Rejected, + _ => Some(OutResponse::Rejected), } } - fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { + fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> Option> { + if (req.request_type, req.recipient, req.index) + != (RequestType::Class, Recipient::Interface, self.comm_if.0 as u16) + { + return None; + } + match req.request { REQ_GET_NTB_PARAMETERS => { let res = NtbParameters { @@ -172,9 +211,9 @@ impl<'d> ControlHandler for CommControl<'d> { max_datagram_count: 1, // We only decode 1 packet per NTB }, }; - InResponse::Accepted(byteify(buf, res)) + Some(InResponse::Accepted(byteify(buf, res))) } - _ => InResponse::Rejected, + _ => Some(InResponse::Rejected), } } @@ -199,18 +238,7 @@ impl<'d> ControlHandler for CommControl<'d> { } } -struct DataControl {} - -impl ControlHandler for DataControl { - fn set_alternate_setting(&mut self, alternate_setting: u8) { - match alternate_setting { - ALTERNATE_SETTING_ENABLED => info!("ncm: interface enabled"), - ALTERNATE_SETTING_DISABLED => info!("ncm: interface disabled"), - _ => unreachable!(), - } - } -} - +/// CDC-NCM class pub struct CdcNcmClass<'d, D: Driver<'d>> { _comm_if: InterfaceNumber, comm_ep: D::EndpointIn, @@ -223,6 +251,7 @@ pub struct CdcNcmClass<'d, D: Driver<'d>> { } impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> { + /// Create a new CDC NCM class. pub fn new( builder: &mut Builder<'d, D>, state: &'d mut State<'d>, @@ -236,13 +265,8 @@ impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> { // Control interface let mut iface = func.interface(); let mac_addr_string = iface.string(); - iface.handler(state.comm_control.write(CommControl { - mac_addr_string, - shared: &state.shared, - mac_addr_str: [0; 12], - })); let comm_if = iface.interface_number(); - let mut alt = iface.alt_setting(USB_CLASS_CDC, CDC_SUBCLASS_NCM, CDC_PROTOCOL_NONE); + let mut alt = iface.alt_setting(USB_CLASS_CDC, CDC_SUBCLASS_NCM, CDC_PROTOCOL_NONE, None); alt.descriptor( CS_INTERFACE, @@ -290,13 +314,23 @@ impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> { // Data interface let mut iface = func.interface(); - iface.handler(state.data_control.write(DataControl {})); let data_if = iface.interface_number(); - let _alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NTB); - let mut alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NTB); + let _alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NTB, None); + let mut alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NTB, None); let read_ep = alt.endpoint_bulk_out(max_packet_size); let write_ep = alt.endpoint_bulk_in(max_packet_size); + drop(func); + + let control = state.control.write(Control { + mac_addr_string, + shared: &state.shared, + mac_addr_str: [0; 12], + comm_if, + data_if, + }); + builder.handler(control); + CdcNcmClass { _comm_if: comm_if, comm_ep, @@ -307,6 +341,9 @@ impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> { } } + /// Split the class into a sender and receiver. + /// + /// This allows concurrently sending and receiving packets from separate tasks. pub fn split(self) -> (Sender<'d, D>, Receiver<'d, D>) { ( Sender { @@ -322,12 +359,18 @@ impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> { } } +/// CDC NCM class packet sender. +/// +/// You can obtain a `Sender` with [`CdcNcmClass::split`] pub struct Sender<'d, D: Driver<'d>> { write_ep: D::EndpointIn, seq: u16, } impl<'d, D: Driver<'d>> Sender<'d, D> { + /// Write a packet. + /// + /// This waits until the packet is successfully stored in the CDC-NCM endpoint buffers. pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> { let seq = self.seq; self.seq = self.seq.wrapping_add(1); @@ -381,6 +424,9 @@ impl<'d, D: Driver<'d>> Sender<'d, D> { } } +/// CDC NCM class packet receiver. +/// +/// You can obtain a `Receiver` with [`CdcNcmClass::split`] pub struct Receiver<'d, D: Driver<'d>> { data_if: InterfaceNumber, comm_ep: D::EndpointIn, @@ -388,7 +434,9 @@ pub struct Receiver<'d, D: Driver<'d>> { } impl<'d, D: Driver<'d>> Receiver<'d, D> { - /// Reads a single packet from the OUT endpoint. + /// Write a network packet. + /// + /// This waits until a packet is successfully received from the endpoint buffers. pub async fn read_packet(&mut self, buf: &mut [u8]) -> Result { // Retry loop loop { diff --git a/embassy-usb-hid/src/lib.rs b/embassy-usb/src/class/hid.rs similarity index 74% rename from embassy-usb-hid/src/lib.rs rename to embassy-usb/src/class/hid.rs index 5fee60bbc..889d66ec5 100644 --- a/embassy-usb-hid/src/lib.rs +++ b/embassy-usb/src/class/hid.rs @@ -1,24 +1,19 @@ -#![no_std] -#![feature(generic_associated_types)] -#![feature(type_alias_impl_trait)] - -//! Implements HID functionality for a usb-device device. - -// This mod MUST go first, so that the others see its macros. -pub(crate) mod fmt; +//! USB HID (Human Interface Device) class implementation. use core::mem::MaybeUninit; use core::ops::Range; use core::sync::atomic::{AtomicUsize, Ordering}; -use embassy_usb::control::{ControlHandler, InResponse, OutResponse, Request, RequestType}; -use embassy_usb::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; -use embassy_usb::Builder; #[cfg(feature = "usbd-hid")] use ssmarshal::serialize; #[cfg(feature = "usbd-hid")] use usbd_hid::descriptor::AsInputReport; +use crate::control::{InResponse, OutResponse, Recipient, Request, RequestType}; +use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; +use crate::types::InterfaceNumber; +use crate::{Builder, Handler}; + const USB_CLASS_HID: u8 = 0x03; const USB_SUBCLASS_NONE: u8 = 0x00; const USB_PROTOCOL_NONE: u8 = 0x00; @@ -36,6 +31,7 @@ const HID_REQ_SET_REPORT: u8 = 0x09; const HID_REQ_GET_PROTOCOL: u8 = 0x03; const HID_REQ_SET_PROTOCOL: u8 = 0x0b; +/// Configuration for the HID class. pub struct Config<'d> { /// HID report descriptor. pub report_descriptor: &'d [u8], @@ -54,11 +50,15 @@ pub struct Config<'d> { pub max_packet_size: u16, } +/// Report ID #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ReportId { + /// IN report In(u8), + /// OUT report Out(u8), + /// Feature report Feature(u8), } @@ -73,12 +73,14 @@ impl ReportId { } } +/// Internal state for USB HID. pub struct State<'d> { control: MaybeUninit>, out_report_offset: AtomicUsize, } impl<'d> State<'d> { + /// Create a new `State`. pub fn new() -> Self { State { control: MaybeUninit::uninit(), @@ -87,6 +89,7 @@ impl<'d> State<'d> { } } +/// USB HID reader/writer. pub struct HidReaderWriter<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize> { reader: HidReader<'d, D, READ_N>, writer: HidWriter<'d, D, WRITE_N>, @@ -98,18 +101,12 @@ fn build<'d, D: Driver<'d>>( config: Config<'d>, with_out_endpoint: bool, ) -> (Option, D::EndpointIn, &'d AtomicUsize) { - let control = state.control.write(Control::new( - config.report_descriptor, - config.request_handler, - &state.out_report_offset, - )); - let len = config.report_descriptor.len(); let mut func = builder.function(USB_CLASS_HID, USB_SUBCLASS_NONE, USB_PROTOCOL_NONE); let mut iface = func.interface(); - iface.handler(control); - let mut alt = iface.alt_setting(USB_CLASS_HID, USB_SUBCLASS_NONE, USB_PROTOCOL_NONE); + let if_num = iface.interface_number(); + let mut alt = iface.alt_setting(USB_CLASS_HID, USB_SUBCLASS_NONE, USB_PROTOCOL_NONE, None); // HID descriptor alt.descriptor( @@ -137,6 +134,16 @@ fn build<'d, D: Driver<'d>>( None }; + drop(func); + + let control = state.control.write(Control::new( + if_num, + config.report_descriptor, + config.request_handler, + &state.out_report_offset, + )); + builder.handler(control); + (ep_out, ep_in, &state.out_report_offset) } @@ -158,7 +165,7 @@ impl<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize> HidReaderWrit } } - /// Splits into seperate readers/writers for input and output reports. + /// Splits into separate readers/writers for input and output reports. pub fn split(self) -> (HidReader<'d, D, READ_N>, HidWriter<'d, D, WRITE_N>) { (self.reader, self.writer) } @@ -188,26 +195,36 @@ impl<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize> HidReaderWrit } } +/// USB HID writer. +/// +/// You can obtain a `HidWriter` using [`HidReaderWriter::split`]. pub struct HidWriter<'d, D: Driver<'d>, const N: usize> { ep_in: D::EndpointIn, } +/// USB HID reader. +/// +/// You can obtain a `HidReader` using [`HidReaderWriter::split`]. pub struct HidReader<'d, D: Driver<'d>, const N: usize> { ep_out: D::EndpointOut, offset: &'d AtomicUsize, } +/// Error when reading a HID report. #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ReadError { + /// The given buffer was too small to read the received report. BufferOverflow, + /// The endpoint is disabled. Disabled, + /// The report was only partially read. See [`HidReader::read`] for details. Sync(Range), } -impl From for ReadError { - fn from(val: embassy_usb::driver::EndpointError) -> Self { - use embassy_usb::driver::EndpointError::*; +impl From for ReadError { + fn from(val: EndpointError) -> Self { + use EndpointError::*; match val { BufferOverflow => ReadError::BufferOverflow, Disabled => ReadError::Disabled, @@ -307,7 +324,7 @@ impl<'d, D: Driver<'d>, const N: usize> HidReader<'d, D, N> { /// **Note:** If `N` > the maximum packet size of the endpoint (i.e. output /// reports may be split across multiple packets) and this method's future /// is dropped after some packets have been read, the next call to `read()` - /// will return a [`ReadError::SyncError()`]. The range in the sync error + /// will return a [`ReadError::Sync`]. The range in the sync error /// indicates the portion `buf` that was filled by the current call to /// `read()`. If the dropped future used the same `buf`, then `buf` will /// contain the full report. @@ -352,6 +369,7 @@ impl<'d, D: Driver<'d>, const N: usize> HidReader<'d, D, N> { } } +/// Handler for HID-related control requests. pub trait RequestHandler { /// Reads the value of report `id` into `buf` returning the size. /// @@ -387,6 +405,7 @@ pub trait RequestHandler { } struct Control<'d> { + if_num: InterfaceNumber, report_descriptor: &'d [u8], request_handler: Option<&'d dyn RequestHandler>, out_report_offset: &'d AtomicUsize, @@ -395,11 +414,13 @@ struct Control<'d> { impl<'d> Control<'d> { fn new( + if_num: InterfaceNumber, report_descriptor: &'d [u8], request_handler: Option<&'d dyn RequestHandler>, out_report_offset: &'d AtomicUsize, ) -> Self { Control { + if_num, report_descriptor, request_handler, out_report_offset, @@ -425,88 +446,103 @@ impl<'d> Control<'d> { } } -impl<'d> ControlHandler for Control<'d> { +impl<'d> Handler for Control<'d> { fn reset(&mut self) { self.out_report_offset.store(0, Ordering::Release); } - fn get_descriptor<'a>(&'a mut self, req: Request, _buf: &'a mut [u8]) -> InResponse<'a> { - match (req.value >> 8) as u8 { - HID_DESC_DESCTYPE_HID_REPORT => InResponse::Accepted(self.report_descriptor), - HID_DESC_DESCTYPE_HID => InResponse::Accepted(&self.hid_descriptor), - _ => InResponse::Rejected, + fn control_out(&mut self, req: Request, data: &[u8]) -> Option { + if (req.request_type, req.recipient, req.index) + != (RequestType::Class, Recipient::Interface, self.if_num.0 as u16) + { + return None; } - } - fn control_out(&mut self, req: embassy_usb::control::Request, data: &[u8]) -> OutResponse { + // This uses a defmt-specific formatter that causes use of the `log` + // feature to fail to build, so leave it defmt-specific for now. + #[cfg(feature = "defmt")] trace!("HID control_out {:?} {=[u8]:x}", req, data); - if let RequestType::Class = req.request_type { - match req.request { - HID_REQ_SET_IDLE => { - if let Some(handler) = self.request_handler { - let id = req.value as u8; - let id = (id != 0).then(|| ReportId::In(id)); - let dur = u32::from(req.value >> 8); - let dur = if dur == 0 { u32::MAX } else { 4 * dur }; - handler.set_idle_ms(id, dur); - } - OutResponse::Accepted - } - HID_REQ_SET_REPORT => match (ReportId::try_from(req.value), self.request_handler) { - (Ok(id), Some(handler)) => handler.set_report(id, data), - _ => OutResponse::Rejected, - }, - HID_REQ_SET_PROTOCOL => { - if req.value == 1 { - OutResponse::Accepted - } else { - warn!("HID Boot Protocol is unsupported."); - OutResponse::Rejected // UNSUPPORTED: Boot Protocol - } - } - _ => OutResponse::Rejected, - } - } else { - OutResponse::Rejected // UNSUPPORTED: SET_DESCRIPTOR - } - } - - fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { - trace!("HID control_in {:?}", req); match req.request { - HID_REQ_GET_REPORT => { - let size = match ReportId::try_from(req.value) { - Ok(id) => self.request_handler.and_then(|x| x.get_report(id, buf)), - Err(_) => None, - }; - - if let Some(size) = size { - InResponse::Accepted(&buf[0..size]) - } else { - InResponse::Rejected - } - } - HID_REQ_GET_IDLE => { + HID_REQ_SET_IDLE => { if let Some(handler) = self.request_handler { let id = req.value as u8; let id = (id != 0).then(|| ReportId::In(id)); - if let Some(dur) = handler.get_idle_ms(id) { - let dur = u8::try_from(dur / 4).unwrap_or(0); - buf[0] = dur; - InResponse::Accepted(&buf[0..1]) - } else { - InResponse::Rejected - } + let dur = u32::from(req.value >> 8); + let dur = if dur == 0 { u32::MAX } else { 4 * dur }; + handler.set_idle_ms(id, dur); + } + Some(OutResponse::Accepted) + } + HID_REQ_SET_REPORT => match (ReportId::try_from(req.value), self.request_handler) { + (Ok(id), Some(handler)) => Some(handler.set_report(id, data)), + _ => Some(OutResponse::Rejected), + }, + HID_REQ_SET_PROTOCOL => { + if req.value == 1 { + Some(OutResponse::Accepted) } else { - InResponse::Rejected + warn!("HID Boot Protocol is unsupported."); + Some(OutResponse::Rejected) // UNSUPPORTED: Boot Protocol } } - HID_REQ_GET_PROTOCOL => { - // UNSUPPORTED: Boot Protocol - buf[0] = 1; - InResponse::Accepted(&buf[0..1]) + _ => Some(OutResponse::Rejected), + } + } + + fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> Option> { + if req.index != self.if_num.0 as u16 { + return None; + } + + match (req.request_type, req.recipient) { + (RequestType::Standard, Recipient::Interface) => match req.request { + Request::GET_DESCRIPTOR => match (req.value >> 8) as u8 { + HID_DESC_DESCTYPE_HID_REPORT => Some(InResponse::Accepted(self.report_descriptor)), + HID_DESC_DESCTYPE_HID => Some(InResponse::Accepted(&self.hid_descriptor)), + _ => Some(InResponse::Rejected), + }, + + _ => Some(InResponse::Rejected), + }, + (RequestType::Class, Recipient::Interface) => { + trace!("HID control_in {:?}", req); + match req.request { + HID_REQ_GET_REPORT => { + let size = match ReportId::try_from(req.value) { + Ok(id) => self.request_handler.and_then(|x| x.get_report(id, buf)), + Err(_) => None, + }; + + if let Some(size) = size { + Some(InResponse::Accepted(&buf[0..size])) + } else { + Some(InResponse::Rejected) + } + } + HID_REQ_GET_IDLE => { + if let Some(handler) = self.request_handler { + let id = req.value as u8; + let id = (id != 0).then(|| ReportId::In(id)); + if let Some(dur) = handler.get_idle_ms(id) { + let dur = u8::try_from(dur / 4).unwrap_or(0); + buf[0] = dur; + Some(InResponse::Accepted(&buf[0..1])) + } else { + Some(InResponse::Rejected) + } + } else { + Some(InResponse::Rejected) + } + } + HID_REQ_GET_PROTOCOL => { + // UNSUPPORTED: Boot Protocol + buf[0] = 1; + Some(InResponse::Accepted(&buf[0..1])) + } + _ => Some(InResponse::Rejected), + } } - _ => InResponse::Rejected, + _ => None, } } } diff --git a/embassy-usb/src/class/mod.rs b/embassy-usb/src/class/mod.rs new file mode 100644 index 000000000..b23e03d40 --- /dev/null +++ b/embassy-usb/src/class/mod.rs @@ -0,0 +1,4 @@ +//! Implementations of well-known USB classes. +pub mod cdc_acm; +pub mod cdc_ncm; +pub mod hid; diff --git a/embassy-usb/src/control.rs b/embassy-usb/src/control.rs index 3e5749a01..ceccfd85b 100644 --- a/embassy-usb/src/control.rs +++ b/embassy-usb/src/control.rs @@ -1,7 +1,7 @@ //! USB control data types. use core::mem; -use super::types::*; +use crate::driver::Direction; /// Control request type. #[repr(u8)] @@ -42,7 +42,7 @@ pub enum Recipient { #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Request { /// Direction of the request. - pub direction: UsbDirection, + pub direction: Direction, /// Type of the request. pub request_type: RequestType, /// Recipient of the request. @@ -105,7 +105,7 @@ impl Request { let recipient = rt & 0b11111; Request { - direction: rt.into(), + direction: if rt & 0x80 == 0 { Direction::Out } else { Direction::In }, request_type: unsafe { mem::transmute((rt >> 5) & 0b11) }, recipient: if recipient <= 3 { unsafe { mem::transmute(recipient) } @@ -125,72 +125,22 @@ impl Request { } } +/// Response for a CONTROL OUT request. #[derive(Copy, Clone, Eq, PartialEq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum OutResponse { + /// The request was accepted. Accepted, + /// The request was rejected. Rejected, } +/// Response for a CONTROL IN request. #[derive(Copy, Clone, Eq, PartialEq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum InResponse<'a> { + /// The request was accepted. The buffer contains the response data. Accepted(&'a [u8]), + /// The request was rejected. Rejected, } - -/// Handler for control requests. -/// -/// All methods are optional callbacks that will be called by -/// [`UsbDevice::run()`](crate::UsbDevice::run) -pub trait ControlHandler { - /// Called after a USB reset after the bus reset sequence is complete. - fn reset(&mut self) {} - - fn set_alternate_setting(&mut self, alternate_setting: u8) { - let _ = alternate_setting; - } - - /// Called when a control request is received with direction HostToDevice. - /// - /// # Arguments - /// - /// * `req` - The request from the SETUP packet. - /// * `data` - The data from the request. - fn control_out(&mut self, req: Request, data: &[u8]) -> OutResponse { - let _ = (req, data); - OutResponse::Rejected - } - - /// Called when a control request is received with direction DeviceToHost. - /// - /// You should write the response somewhere (usually to `buf`, but you may use another buffer - /// owned by yourself, or a static buffer), then return `InResponse::Accepted(data)`. - /// - /// # Arguments - /// - /// * `req` - The request from the SETUP packet. - fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { - let _ = (req, buf); - InResponse::Rejected - } - - /// Called when a GET DESCRIPTOR control request is received on the interface. - /// - /// You should write the response somewhere (usually to `buf`, but you may use another buffer - /// owned by yourself, or a static buffer), then return `InResponse::Accepted(data)`. - /// - /// # Arguments - /// - /// * `req` - The request from the SETUP packet. - fn get_descriptor<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { - let _ = (req, buf); - InResponse::Rejected - } - - /// Called when a GET_DESCRIPTOR STRING control request is received. - fn get_string(&mut self, index: StringIndex, lang_id: u16) -> Option<&str> { - let _ = (index, lang_id); - None - } -} diff --git a/embassy-usb/src/descriptor.rs b/embassy-usb/src/descriptor.rs index b94a4b161..ae38e26ca 100644 --- a/embassy-usb/src/descriptor.rs +++ b/embassy-usb/src/descriptor.rs @@ -1,6 +1,9 @@ -use super::builder::Config; -use super::types::*; -use super::CONFIGURATION_VALUE; +//! Utilities for writing USB descriptors. + +use crate::builder::Config; +use crate::driver::EndpointInfo; +use crate::types::*; +use crate::CONFIGURATION_VALUE; /// Standard descriptor types #[allow(missing_docs)] @@ -235,7 +238,7 @@ impl<'a> DescriptorWriter<'a> { endpoint.ep_type as u8, // bmAttributes endpoint.max_packet_size as u8, (endpoint.max_packet_size >> 8) as u8, // wMaxPacketSize - endpoint.interval, // bInterval + endpoint.interval_ms, // bInterval ], ); } diff --git a/embassy-usb/src/descriptor_reader.rs b/embassy-usb/src/descriptor_reader.rs index 0a12b566c..05adcce60 100644 --- a/embassy-usb/src/descriptor_reader.rs +++ b/embassy-usb/src/descriptor_reader.rs @@ -1,5 +1,6 @@ use crate::descriptor::descriptor_type; -use crate::types::EndpointAddress; +use crate::driver::EndpointAddress; +use crate::types::InterfaceNumber; #[derive(Copy, Clone, PartialEq, Eq, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -75,7 +76,7 @@ impl<'a, 'b> Iterator for DescriptorIter<'a, 'b> { #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct EndpointInfo { pub configuration: u8, - pub interface: u8, + pub interface: InterfaceNumber, pub interface_alt: u8, pub ep_address: EndpointAddress, } @@ -83,7 +84,7 @@ pub struct EndpointInfo { pub fn foreach_endpoint(data: &[u8], mut f: impl FnMut(EndpointInfo)) -> Result<(), ReadError> { let mut ep = EndpointInfo { configuration: 0, - interface: 0, + interface: InterfaceNumber(0), interface_alt: 0, ep_address: EndpointAddress::from(0), }; @@ -96,7 +97,7 @@ pub fn foreach_endpoint(data: &[u8], mut f: impl FnMut(EndpointInfo)) -> Result< ep.configuration = r.read_u8()?; } descriptor_type::INTERFACE => { - ep.interface = r.read_u8()?; + ep.interface = InterfaceNumber(r.read_u8()?); ep.interface_alt = r.read_u8()?; } descriptor_type::ENDPOINT => { diff --git a/embassy-usb/src/driver.rs b/embassy-usb/src/driver.rs deleted file mode 100644 index 7888f1639..000000000 --- a/embassy-usb/src/driver.rs +++ /dev/null @@ -1,232 +0,0 @@ -use core::future::Future; - -use super::types::*; - -/// Driver for a specific USB peripheral. Implement this to add support for a new hardware -/// platform. -pub trait Driver<'a> { - type EndpointOut: EndpointOut + 'a; - type EndpointIn: EndpointIn + 'a; - type ControlPipe: ControlPipe + 'a; - type Bus: Bus + 'a; - - /// Allocates an endpoint and specified endpoint parameters. This method is called by the device - /// and class implementations to allocate endpoints, and can only be called before - /// [`start`](Self::start) is called. - /// - /// # Arguments - /// - /// * `ep_addr` - A static endpoint address to allocate. If Some, the implementation should - /// attempt to return an endpoint with the specified address. If None, the implementation - /// should return the next available one. - /// * `max_packet_size` - Maximum packet size in bytes. - /// * `interval` - Polling interval parameter for interrupt endpoints. - fn alloc_endpoint_out( - &mut self, - ep_type: EndpointType, - max_packet_size: u16, - interval: u8, - ) -> Result; - - fn alloc_endpoint_in( - &mut self, - ep_type: EndpointType, - max_packet_size: u16, - interval: u8, - ) -> Result; - - /// Start operation of the USB device. - /// - /// This returns the `Bus` and `ControlPipe` instances that are used to operate - /// the USB device. Additionally, this makes all the previously allocated endpoints - /// start operating. - /// - /// This consumes the `Driver` instance, so it's no longer possible to allocate more - /// endpoints. - fn start(self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe); - - /// Indicates that `set_device_address` must be called before accepting the corresponding - /// control transfer, not after. - /// - /// The default value for this constant is `false`, which corresponds to the USB 2.0 spec, 9.4.6 - const QUIRK_SET_ADDRESS_BEFORE_STATUS: bool = false; -} - -pub trait Bus { - type EnableFuture<'a>: Future + 'a - where - Self: 'a; - type DisableFuture<'a>: Future + 'a - where - Self: 'a; - type PollFuture<'a>: Future + 'a - where - Self: 'a; - type RemoteWakeupFuture<'a>: Future> + 'a - where - Self: 'a; - - /// Enables the USB peripheral. Soon after enabling the device will be reset, so - /// there is no need to perform a USB reset in this method. - fn enable(&mut self) -> Self::EnableFuture<'_>; - - /// Disables and powers down the USB peripheral. - fn disable(&mut self) -> Self::DisableFuture<'_>; - - fn poll<'a>(&'a mut self) -> Self::PollFuture<'a>; - - /// Sets the device USB address to `addr`. - fn set_address(&mut self, addr: u8); - - /// Enables or disables an endpoint. - fn endpoint_set_enabled(&mut self, ep_addr: EndpointAddress, enabled: bool); - - /// Sets or clears the STALL condition for an endpoint. If the endpoint is an OUT endpoint, it - /// should be prepared to receive data again. Only used during control transfers. - fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool); - - /// Gets whether the STALL condition is set for an endpoint. Only used during control transfers. - fn endpoint_is_stalled(&mut self, ep_addr: EndpointAddress) -> bool; - - /// Simulates a disconnect from the USB bus, causing the host to reset and re-enumerate the - /// device. - /// - /// The default implementation just returns `Unsupported`. - /// - /// # Errors - /// - /// * [`Unsupported`](crate::driver::Unsupported) - This UsbBus implementation doesn't support - /// simulating a disconnect or it has not been enabled at creation time. - fn force_reset(&mut self) -> Result<(), Unsupported> { - Err(Unsupported) - } - - /// Initiates a remote wakeup of the host by the device. - /// - /// # Errors - /// - /// * [`Unsupported`](crate::driver::Unsupported) - This UsbBus implementation doesn't support - /// remote wakeup or it has not been enabled at creation time. - fn remote_wakeup(&mut self) -> Self::RemoteWakeupFuture<'_>; -} - -pub trait Endpoint { - type WaitEnabledFuture<'a>: Future + 'a - where - Self: 'a; - - /// Get the endpoint address - fn info(&self) -> &EndpointInfo; - - /// Waits for the endpoint to be enabled. - fn wait_enabled(&mut self) -> Self::WaitEnabledFuture<'_>; -} - -pub trait EndpointOut: Endpoint { - type ReadFuture<'a>: Future> + 'a - where - Self: 'a; - - /// Reads a single packet of data from the endpoint, and returns the actual length of - /// the packet. - /// - /// This should also clear any NAK flags and prepare the endpoint to receive the next packet. - fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a>; -} - -pub trait ControlPipe { - type SetupFuture<'a>: Future + 'a - where - Self: 'a; - type DataOutFuture<'a>: Future> + 'a - where - Self: 'a; - type DataInFuture<'a>: Future> + 'a - where - Self: 'a; - type AcceptFuture<'a>: Future + 'a - where - Self: 'a; - type RejectFuture<'a>: Future + 'a - where - Self: 'a; - - /// Maximum packet size for the control pipe - fn max_packet_size(&self) -> usize; - - /// Reads a single setup packet from the endpoint. - fn setup<'a>(&'a mut self) -> Self::SetupFuture<'a>; - - /// Reads a DATA OUT packet into `buf` in response to a control write request. - /// - /// Must be called after `setup()` for requests with `direction` of `Out` - /// and `length` greater than zero. - fn data_out<'a>(&'a mut self, buf: &'a mut [u8], first: bool, last: bool) -> Self::DataOutFuture<'a>; - - /// Sends a DATA IN packet with `data` in response to a control read request. - /// - /// If `last_packet` is true, the STATUS packet will be ACKed following the transfer of `data`. - fn data_in<'a>(&'a mut self, data: &'a [u8], first: bool, last: bool) -> Self::DataInFuture<'a>; - - /// Accepts a control request. - /// - /// Causes the STATUS packet for the current request to be ACKed. - fn accept<'a>(&'a mut self) -> Self::AcceptFuture<'a>; - - /// Rejects a control request. - /// - /// Sets a STALL condition on the pipe to indicate an error. - fn reject<'a>(&'a mut self) -> Self::RejectFuture<'a>; -} - -pub trait EndpointIn: Endpoint { - type WriteFuture<'a>: Future> + 'a - where - Self: 'a; - - /// Writes a single packet of data to the endpoint. - fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a>; -} - -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -/// Event returned by [`Bus::poll`]. -pub enum Event { - /// The USB reset condition has been detected. - Reset, - - /// A USB suspend request has been detected or, in the case of self-powered devices, the device - /// has been disconnected from the USB bus. - Suspend, - - /// A USB resume request has been detected after being suspended or, in the case of self-powered - /// devices, the device has been connected to the USB bus. - Resume, - - /// The USB power has been detected. - PowerDetected, - - /// The USB power has been removed. Not supported by all devices. - PowerRemoved, -} - -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct EndpointAllocError; - -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -/// Operation is unsupported by the driver. -pub struct Unsupported; - -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -/// Errors returned by [`EndpointIn::write`] and [`EndpointOut::read`] -pub enum EndpointError { - /// Either the packet to be written is too long to fit in the transmission - /// buffer or the received packet is too long to fit in `buf`. - BufferOverflow, - - /// The endpoint is disabled. - Disabled, -} diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs index 5a3f8ba88..1180b9b66 100644 --- a/embassy-usb/src/lib.rs +++ b/embassy-usb/src/lib.rs @@ -1,27 +1,35 @@ #![no_std] -#![feature(generic_associated_types)] -#![feature(type_alias_impl_trait)] +#![doc = include_str!("../README.md")] +#![warn(missing_docs)] // This mod MUST go first, so that the others see its macros. pub(crate) mod fmt; +pub use embassy_usb_driver as driver; + mod builder; +pub mod class; pub mod control; pub mod descriptor; mod descriptor_reader; -pub mod driver; +pub mod msos; pub mod types; -use embassy_futures::{select, Either}; +mod config { + #![allow(unused)] + include!(concat!(env!("OUT_DIR"), "/config.rs")); +} + +use embassy_futures::select::{select, Either}; use heapless::Vec; -pub use self::builder::{Builder, Config}; -use self::control::*; -use self::descriptor::*; -use self::driver::{Bus, Driver, Event}; -use self::types::*; +pub use crate::builder::{Builder, Config, FunctionBuilder, InterfaceAltBuilder, InterfaceBuilder}; +use crate::config::*; +use crate::control::*; +use crate::descriptor::*; use crate::descriptor_reader::foreach_endpoint; -use crate::driver::ControlPipe; +use crate::driver::{Bus, ControlPipe, Direction, Driver, EndpointAddress, Event}; +use crate::types::*; /// The global state of the USB device. /// @@ -46,10 +54,13 @@ pub enum UsbDeviceState { Configured, } +/// Error returned by [`UsbDevice::remote_wakeup`]. #[derive(PartialEq, Eq, Copy, Clone, Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum RemoteWakeupError { + /// The USB device is not suspended, or remote wakeup was not enabled. InvalidState, + /// The underlying driver doesn't support remote wakeup. Unsupported, } @@ -65,41 +76,114 @@ pub const CONFIGURATION_NONE: u8 = 0; /// The bConfiguration value for the single configuration supported by this device. pub const CONFIGURATION_VALUE: u8 = 1; -pub const MAX_INTERFACE_COUNT: usize = 4; - const STRING_INDEX_MANUFACTURER: u8 = 1; const STRING_INDEX_PRODUCT: u8 = 2; const STRING_INDEX_SERIAL_NUMBER: u8 = 3; const STRING_INDEX_CUSTOM_START: u8 = 4; -/// A handler trait for changes in the device state of the [UsbDevice]. -pub trait DeviceStateHandler { +/// Handler for device events and control requests. +/// +/// All methods are optional callbacks that will be called by +/// [`UsbDevice::run()`](crate::UsbDevice::run) +pub trait Handler { /// Called when the USB device has been enabled or disabled. - fn enabled(&self, _enabled: bool) {} + fn enabled(&mut self, _enabled: bool) {} - /// Called when the host resets the device. - fn reset(&self) {} + /// Called after a USB reset after the bus reset sequence is complete. + fn reset(&mut self) {} /// Called when the host has set the address of the device to `addr`. - fn addressed(&self, _addr: u8) {} + fn addressed(&mut self, _addr: u8) {} /// Called when the host has enabled or disabled the configuration of the device. - fn configured(&self, _configured: bool) {} + fn configured(&mut self, _configured: bool) {} /// Called when the bus has entered or exited the suspend state. - fn suspended(&self, _suspended: bool) {} + fn suspended(&mut self, _suspended: bool) {} /// Called when remote wakeup feature is enabled or disabled. - fn remote_wakeup_enabled(&self, _enabled: bool) {} + fn remote_wakeup_enabled(&mut self, _enabled: bool) {} + + /// Called when a "set alternate setting" control request is done on the interface. + fn set_alternate_setting(&mut self, iface: InterfaceNumber, alternate_setting: u8) { + let _ = iface; + let _ = alternate_setting; + } + + /// Called when a control request is received with direction HostToDevice. + /// + /// # Arguments + /// + /// * `req` - The request from the SETUP packet. + /// * `data` - The data from the request. + /// + /// # Returns + /// + /// If you didn't handle this request (for example if it's for the wrong interface), return + /// `None`. In this case, the the USB stack will continue calling the other handlers, to see + /// if another handles it. + /// + /// If you did, return `Some` with either `Accepted` or `Rejected`. This will make the USB stack + /// respond to the control request, and stop calling other handlers. + fn control_out(&mut self, req: Request, data: &[u8]) -> Option { + let _ = (req, data); + None + } + + /// Called when a control request is received with direction DeviceToHost. + /// + /// You should write the response somewhere (usually to `buf`, but you may use another buffer + /// owned by yourself, or a static buffer), then return `InResponse::Accepted(data)`. + /// + /// # Arguments + /// + /// * `req` - The request from the SETUP packet. + /// + /// # Returns + /// + /// If you didn't handle this request (for example if it's for the wrong interface), return + /// `None`. In this case, the the USB stack will continue calling the other handlers, to see + /// if another handles it. + /// + /// If you did, return `Some` with either `Accepted` or `Rejected`. This will make the USB stack + /// respond to the control request, and stop calling other handlers. + fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> Option> { + let _ = (req, buf); + None + } + + /// Called when a GET_DESCRIPTOR STRING control request is received. + fn get_string(&mut self, index: StringIndex, lang_id: u16) -> Option<&str> { + let _ = (index, lang_id); + None + } } -struct Interface<'d> { - handler: Option<&'d mut dyn ControlHandler>, +struct Interface { current_alt_setting: u8, num_alt_settings: u8, - num_strings: u8, } +/// A report of the used size of the runtime allocated buffers +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct UsbBufferReport { + /// Number of device descriptor bytes used + pub device_descriptor_used: usize, + /// Number of config descriptor bytes used + pub config_descriptor_used: usize, + /// Number of bos descriptor bytes used + pub bos_descriptor_used: usize, + /// Number of msos descriptor bytes used + /// + /// Will be `None` if the "msos-descriptor" feature is not active. + /// Otherwise will return Some(bytes). + pub msos_descriptor_used: Option, + /// Size of the control buffer + pub control_buffer_size: usize, +} + +/// Main struct for the USB device stack. pub struct UsbDevice<'d, D: Driver<'d>> { control_buf: &'d mut [u8], control: D::ControlPipe, @@ -108,7 +192,6 @@ pub struct UsbDevice<'d, D: Driver<'d>> { struct Inner<'d, D: Driver<'d>> { bus: D::Bus, - handler: Option<&'d dyn DeviceStateHandler>, config: Config<'d>, device_descriptor: &'d [u8], @@ -122,25 +205,29 @@ struct Inner<'d, D: Driver<'d>> { /// Our device address, or 0 if none. address: u8, - /// When receiving a set addr control request, we have to apply it AFTER we've - /// finished handling the control request, as the status stage still has to be - /// handled with addr 0. - /// If true, do a set_addr after finishing the current control req. + /// SET_ADDRESS requests have special handling depending on the driver. + /// This flag indicates that requests must be handled by `ControlPipe::accept_set_address()` + /// instead of regular `accept()`. set_address_pending: bool, - interfaces: Vec, MAX_INTERFACE_COUNT>, + interfaces: Vec, + handlers: Vec<&'d mut dyn Handler, MAX_HANDLER_COUNT>, + + #[cfg(feature = "msos-descriptor")] + msos_descriptor: crate::msos::MsOsDescriptorSet<'d>, } impl<'d, D: Driver<'d>> UsbDevice<'d, D> { pub(crate) fn build( driver: D, config: Config<'d>, - handler: Option<&'d dyn DeviceStateHandler>, + handlers: Vec<&'d mut dyn Handler, MAX_HANDLER_COUNT>, device_descriptor: &'d [u8], config_descriptor: &'d [u8], bos_descriptor: &'d [u8], - interfaces: Vec, MAX_INTERFACE_COUNT>, + interfaces: Vec, control_buf: &'d mut [u8], + #[cfg(feature = "msos-descriptor")] msos_descriptor: crate::msos::MsOsDescriptorSet<'d>, ) -> UsbDevice<'d, D> { // Start the USB bus. // This prevent further allocation by consuming the driver. @@ -152,7 +239,6 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { inner: Inner { bus, config, - handler, device_descriptor, config_descriptor, bos_descriptor, @@ -164,10 +250,31 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { address: 0, set_address_pending: false, interfaces, + handlers, + #[cfg(feature = "msos-descriptor")] + msos_descriptor, }, } } + /// Returns a report of the consumed buffers + /// + /// Useful for tuning buffer sizes for actual usage + pub fn buffer_usage(&self) -> UsbBufferReport { + #[cfg(not(feature = "msos-descriptor"))] + let mdu = None; + #[cfg(feature = "msos-descriptor")] + let mdu = Some(self.inner.msos_descriptor.len()); + + UsbBufferReport { + device_descriptor_used: self.inner.device_descriptor.len(), + config_descriptor_used: self.inner.config_descriptor.len(), + bos_descriptor_used: self.inner.bos_descriptor.len(), + msos_descriptor_used: mdu, + control_buffer_size: self.control_buf.len(), + } + } + /// Runs the `UsbDevice` forever. /// /// This future may leave the bus in an invalid state if it is dropped. @@ -206,7 +313,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { self.inner.suspended = false; self.inner.remote_wakeup_enabled = false; - if let Some(h) = &self.inner.handler { + for h in &mut self.inner.handlers { h.enabled(false); } } @@ -235,7 +342,7 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { self.inner.bus.remote_wakeup().await?; self.inner.suspended = false; - if let Some(h) = &self.inner.handler { + for h in &mut self.inner.handlers { h.suspended(false); } @@ -248,16 +355,11 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { async fn handle_control(&mut self, req: [u8; 8]) { let req = Request::parse(&req); - trace!("control request: {:02x}", req); + trace!("control request: {:?}", req); match req.direction { - UsbDirection::In => self.handle_control_in(req).await, - UsbDirection::Out => self.handle_control_out(req).await, - } - - if self.inner.set_address_pending { - self.inner.bus.set_address(self.inner.address); - self.inner.set_address_pending = false; + Direction::In => self.handle_control_in(req).await, + Direction::Out => self.handle_control_out(req).await, } } @@ -328,7 +430,14 @@ impl<'d, D: Driver<'d>> UsbDevice<'d, D> { trace!(" control out data: {:02x?}", data); match self.inner.handle_control_out(req, data) { - OutResponse::Accepted => self.control.accept().await, + OutResponse::Accepted => { + if self.inner.set_address_pending { + self.control.accept_set_address(self.inner.address).await; + self.inner.set_address_pending = false; + } else { + self.control.accept().await + } + } OutResponse::Rejected => self.control.reject().await, } } @@ -344,29 +453,29 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { self.remote_wakeup_enabled = false; self.address = 0; - for iface in self.interfaces.iter_mut() { - iface.current_alt_setting = 0; - if let Some(h) = &mut iface.handler { - h.reset(); - h.set_alternate_setting(0); - } + for h in &mut self.handlers { + h.reset(); } - if let Some(h) = &self.handler { - h.reset(); + for (i, iface) in self.interfaces.iter_mut().enumerate() { + iface.current_alt_setting = 0; + + for h in &mut self.handlers { + h.set_alternate_setting(InterfaceNumber::new(i as _), 0); + } } } Event::Resume => { trace!("usb: resume"); self.suspended = false; - if let Some(h) = &self.handler { + for h in &mut self.handlers { h.suspended(false); } } Event::Suspend => { trace!("usb: suspend"); self.suspended = true; - if let Some(h) = &self.handler { + for h in &mut self.handlers { h.suspended(true); } } @@ -375,7 +484,7 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { self.bus.enable().await; self.device_state = UsbDeviceState::Default; - if let Some(h) = &self.handler { + for h in &mut self.handlers { h.enabled(true); } } @@ -384,7 +493,7 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { self.bus.disable().await; self.device_state = UsbDeviceState::Unpowered; - if let Some(h) = &self.handler { + for h in &mut self.handlers { h.enabled(false); } } @@ -399,14 +508,14 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { (RequestType::Standard, Recipient::Device) => match (req.request, req.value) { (Request::CLEAR_FEATURE, Request::FEATURE_DEVICE_REMOTE_WAKEUP) => { self.remote_wakeup_enabled = false; - if let Some(h) = &self.handler { + for h in &mut self.handlers { h.remote_wakeup_enabled(false); } OutResponse::Accepted } (Request::SET_FEATURE, Request::FEATURE_DEVICE_REMOTE_WAKEUP) => { self.remote_wakeup_enabled = true; - if let Some(h) = &self.handler { + for h in &mut self.handlers { h.remote_wakeup_enabled(true); } OutResponse::Accepted @@ -415,7 +524,7 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { self.address = addr as u8; self.set_address_pending = true; self.device_state = UsbDeviceState::Addressed; - if let Some(h) = &self.handler { + for h in &mut self.handlers { h.addressed(self.address); } OutResponse::Accepted @@ -426,14 +535,14 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { // Enable all endpoints of selected alt settings. foreach_endpoint(self.config_descriptor, |ep| { - let iface = &self.interfaces[ep.interface as usize]; + let iface = &self.interfaces[ep.interface.0 as usize]; self.bus .endpoint_set_enabled(ep.ep_address, iface.current_alt_setting == ep.interface_alt); }) .unwrap(); - // Notify handler. - if let Some(h) = &self.handler { + // Notify handlers. + for h in &mut self.handlers { h.configured(true); } @@ -451,8 +560,8 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { }) .unwrap(); - // Notify handler. - if let Some(h) = &self.handler { + // Notify handlers. + for h in &mut self.handlers { h.configured(false); } @@ -462,7 +571,8 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { _ => OutResponse::Rejected, }, (RequestType::Standard, Recipient::Interface) => { - let iface = match self.interfaces.get_mut(req.index as usize) { + let iface_num = InterfaceNumber::new(req.index as _); + let iface = match self.interfaces.get_mut(iface_num.0 as usize) { Some(iface) => iface, None => return OutResponse::Rejected, }; @@ -480,7 +590,7 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { // Enable/disable EPs of this interface as needed. foreach_endpoint(self.config_descriptor, |ep| { - if ep.interface == req.index as u8 { + if ep.interface == iface_num { self.bus .endpoint_set_enabled(ep.ep_address, iface.current_alt_setting == ep.interface_alt); } @@ -488,10 +598,9 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { .unwrap(); // TODO check it is valid (not out of range) - // TODO actually enable/disable endpoints. - if let Some(handler) = &mut iface.handler { - handler.set_alternate_setting(new_altsetting); + for h in &mut self.handlers { + h.set_alternate_setting(iface_num, new_altsetting); } OutResponse::Accepted } @@ -511,17 +620,7 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { } _ => OutResponse::Rejected, }, - (RequestType::Class, Recipient::Interface) => { - let iface = match self.interfaces.get_mut(req.index as usize) { - Some(iface) => iface, - None => return OutResponse::Rejected, - }; - match &mut iface.handler { - Some(handler) => handler.control_out(req, data), - None => OutResponse::Rejected, - } - } - _ => OutResponse::Rejected, + _ => self.handle_control_out_delegated(req, data), } } @@ -566,11 +665,7 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { buf[0] = iface.current_alt_setting; InResponse::Accepted(&buf[..1]) } - Request::GET_DESCRIPTOR => match &mut iface.handler { - Some(handler) => handler.get_descriptor(req, buf), - None => InResponse::Rejected, - }, - _ => InResponse::Rejected, + _ => self.handle_control_in_delegated(req, buf), } } (RequestType::Standard, Recipient::Endpoint) => match req.request { @@ -585,21 +680,48 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { } _ => InResponse::Rejected, }, - (RequestType::Class, Recipient::Interface) => { - let iface = match self.interfaces.get_mut(req.index as usize) { - Some(iface) => iface, - None => return InResponse::Rejected, - }; - - match &mut iface.handler { - Some(handler) => handler.control_in(req, buf), - None => InResponse::Rejected, + #[cfg(feature = "msos-descriptor")] + (RequestType::Vendor, Recipient::Device) => { + if !self.msos_descriptor.is_empty() + && req.request == self.msos_descriptor.vendor_code() + && req.index == 7 + { + // Index 7 retrieves the MS OS Descriptor Set + InResponse::Accepted(self.msos_descriptor.descriptor()) + } else { + self.handle_control_in_delegated(req, buf) } } - _ => InResponse::Rejected, + _ => self.handle_control_in_delegated(req, buf), } } + fn handle_control_out_delegated(&mut self, req: Request, data: &[u8]) -> OutResponse { + for h in &mut self.handlers { + if let Some(res) = h.control_out(req, data) { + return res; + } + } + OutResponse::Rejected + } + + fn handle_control_in_delegated<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { + unsafe fn extend_lifetime<'x, 'y>(r: InResponse<'x>) -> InResponse<'y> { + core::mem::transmute(r) + } + + for h in &mut self.handlers { + if let Some(res) = h.control_in(req, buf) { + // safety: the borrow checker isn't smart enough to know this pattern (returning a + // borrowed value from inside the loop) is sound. Workaround by unsafely extending lifetime. + // Also, Polonius (the WIP new borrow checker) does accept it. + + return unsafe { extend_lifetime(res) }; + } + } + InResponse::Rejected + } + fn handle_get_descriptor<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { let (dtype, index) = req.descriptor_type_index(); @@ -620,30 +742,16 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { STRING_INDEX_PRODUCT => self.config.product, STRING_INDEX_SERIAL_NUMBER => self.config.serial_number, _ => { - // Find out which iface owns this string index. - let mut index_left = index - STRING_INDEX_CUSTOM_START; - let mut the_iface = None; - for iface in &mut self.interfaces { - if index_left < iface.num_strings { - the_iface = Some(iface); + let mut s = None; + for handler in &mut self.handlers { + let index = StringIndex::new(index); + let lang_id = req.index; + if let Some(res) = handler.get_string(index, lang_id) { + s = Some(res); break; } - index_left -= iface.num_strings; - } - - if let Some(iface) = the_iface { - if let Some(handler) = &mut iface.handler { - let index = StringIndex::new(index); - let lang_id = req.index; - handler.get_string(index, lang_id) - } else { - warn!("String requested to an interface with no handler."); - None - } - } else { - warn!("String requested but didn't match to an interface."); - None } + s } }; @@ -655,7 +763,7 @@ impl<'d, D: Driver<'d>> Inner<'d, D> { buf[1] = descriptor_type::STRING; let mut pos = 2; for c in s.encode_utf16() { - if pos >= buf.len() { + if pos + 2 >= buf.len() { panic!("control buffer too small"); } diff --git a/embassy-usb/src/msos.rs b/embassy-usb/src/msos.rs new file mode 100644 index 000000000..187b2ff8e --- /dev/null +++ b/embassy-usb/src/msos.rs @@ -0,0 +1,732 @@ +#![cfg(feature = "msos-descriptor")] + +//! Microsoft OS Descriptors +//! +//! + +use core::mem::size_of; + +use super::{capability_type, BosWriter}; +use crate::types::InterfaceNumber; + +/// A serialized Microsoft OS 2.0 Descriptor set. +/// +/// Create with [`DeviceDescriptorSetBuilder`]. +pub struct MsOsDescriptorSet<'d> { + descriptor: &'d [u8], + vendor_code: u8, +} + +impl<'d> MsOsDescriptorSet<'d> { + /// Gets the raw bytes of the MS OS descriptor + pub fn descriptor(&self) -> &[u8] { + self.descriptor + } + + /// Gets the vendor code used by the host to retrieve the MS OS descriptor + pub fn vendor_code(&self) -> u8 { + self.vendor_code + } + + /// Returns `true` if no MS OS descriptor data is available + pub fn is_empty(&self) -> bool { + self.descriptor.is_empty() + } + + /// Returns the length of the descriptor field + pub fn len(&self) -> usize { + self.descriptor.len() + } +} + +/// Writes a Microsoft OS 2.0 Descriptor set into a buffer. +pub struct MsOsDescriptorWriter<'d> { + buf: &'d mut [u8], + + position: usize, + config_mark: Option, + function_mark: Option, + vendor_code: u8, +} + +impl<'d> MsOsDescriptorWriter<'d> { + pub(crate) fn new(buf: &'d mut [u8]) -> Self { + MsOsDescriptorWriter { + buf, + position: 0, + config_mark: None, + function_mark: None, + vendor_code: 0, + } + } + + pub(crate) fn build(mut self, bos: &mut BosWriter) -> MsOsDescriptorSet<'d> { + self.end(); + + if self.is_empty() { + MsOsDescriptorSet { + descriptor: &[], + vendor_code: 0, + } + } else { + self.write_bos(bos); + MsOsDescriptorSet { + descriptor: &self.buf[..self.position], + vendor_code: self.vendor_code, + } + } + } + + /// Returns `true` if the MS OS descriptor header has not yet been written + pub fn is_empty(&self) -> bool { + self.position == 0 + } + + /// Returns `true` if a configuration subset header has been started + pub fn is_in_config_subset(&self) -> bool { + self.config_mark.is_some() + } + + /// Returns `true` if a function subset header has been started and not yet ended + pub fn is_in_function_subset(&self) -> bool { + self.function_mark.is_some() + } + + /// Write the MS OS descriptor set header. + /// + /// - `windows_version` is an NTDDI version constant that describes a windows version. See the [`windows_version`] + /// module. + /// - `vendor_code` is the vendor request code used to read the MS OS descriptor set. + pub fn header(&mut self, windows_version: u32, vendor_code: u8) { + assert!(self.is_empty(), "You can only call MsOsDescriptorWriter::header once"); + self.write(DescriptorSetHeader::new(windows_version)); + self.vendor_code = vendor_code; + } + + /// Add a device level feature descriptor. + /// + /// Note that some feature descriptors may only be used at the device level in non-composite devices. + /// Those features must be written before the first call to [`Self::configuration`]. + pub fn device_feature(&mut self, desc: T) { + assert!( + !self.is_empty(), + "device features may only be added after the header is written" + ); + assert!( + self.config_mark.is_none(), + "device features must be added before the first configuration subset" + ); + self.write(desc); + } + + /// Add a configuration subset. + pub fn configuration(&mut self, config: u8) { + assert!( + !self.is_empty(), + "MsOsDescriptorWriter: configuration must be called after header" + ); + Self::end_subset::(self.buf, self.position, &mut self.config_mark); + self.config_mark = Some(self.position); + self.write(ConfigurationSubsetHeader::new(config)); + } + + /// Add a function subset. + pub fn function(&mut self, first_interface: InterfaceNumber) { + assert!( + self.config_mark.is_some(), + "MsOsDescriptorWriter: function subset requires a configuration subset" + ); + self.end_function(); + self.function_mark = Some(self.position); + self.write(FunctionSubsetHeader::new(first_interface)); + } + + /// Add a function level feature descriptor. + /// + /// Note that some features may only be used at the function level. Those features must be written after a call + /// to [`Self::function`]. + pub fn function_feature(&mut self, desc: T) { + assert!( + self.function_mark.is_some(), + "function features may only be added to a function subset" + ); + self.write(desc); + } + + /// Ends the current function subset (if any) + pub fn end_function(&mut self) { + Self::end_subset::(self.buf, self.position, &mut self.function_mark); + } + + fn write(&mut self, desc: T) { + desc.write_to(&mut self.buf[self.position..]); + self.position += desc.size(); + } + + fn end_subset(buf: &mut [u8], position: usize, mark: &mut Option) { + if let Some(mark) = mark.take() { + let len = position - mark; + let p = mark + T::LENGTH_OFFSET; + buf[p..(p + 2)].copy_from_slice(&(len as u16).to_le_bytes()); + } + } + + fn end(&mut self) { + if self.position > 0 { + Self::end_subset::(self.buf, self.position, &mut self.function_mark); + Self::end_subset::(self.buf, self.position, &mut self.config_mark); + Self::end_subset::(self.buf, self.position, &mut Some(0)); + } + } + + fn write_bos(&mut self, bos: &mut BosWriter) { + let windows_version = &self.buf[4..8]; + let len = (self.position as u16).to_le_bytes(); + bos.capability( + capability_type::PLATFORM, + &[ + 0, // reserved + // platform capability UUID, Microsoft OS 2.0 platform compatibility + 0xdf, + 0x60, + 0xdd, + 0xd8, + 0x89, + 0x45, + 0xc7, + 0x4c, + 0x9c, + 0xd2, + 0x65, + 0x9d, + 0x9e, + 0x64, + 0x8a, + 0x9f, + // Minimum compatible Windows version + windows_version[0], + windows_version[1], + windows_version[2], + windows_version[3], + // Descriptor set length + len[0], + len[1], + self.vendor_code, + 0x0, // Device does not support alternate enumeration + ], + ); + } +} + +/// Microsoft Windows version codes +/// +/// Windows 8.1 is the minimum version allowed for MS OS 2.0 descriptors. +pub mod windows_version { + /// Windows 8.1 (aka `NTDDI_WINBLUE`) + pub const WIN8_1: u32 = 0x06030000; + /// Windows 10 + pub const WIN10: u32 = 0x0A000000; +} + +mod sealed { + use core::mem::size_of; + + /// A trait for descriptors + pub trait Descriptor: Sized { + const TYPE: super::DescriptorType; + + /// The size of the descriptor's header. + fn size(&self) -> usize { + size_of::() + } + + fn write_to(&self, buf: &mut [u8]); + } + + pub trait DescriptorSet: Descriptor { + const LENGTH_OFFSET: usize; + } +} + +use sealed::*; + +/// Copies the data of `t` into `buf`. +/// +/// # Safety +/// The type `T` must be able to be safely cast to `&[u8]`. (e.g. it is a `#[repr(packed)]` struct) +unsafe fn transmute_write_to(t: &T, buf: &mut [u8]) { + let bytes = core::slice::from_raw_parts((t as *const T) as *const u8, size_of::()); + assert!(buf.len() >= bytes.len(), "MS OS descriptor buffer full"); + (&mut buf[..bytes.len()]).copy_from_slice(bytes); +} + +/// Table 9. Microsoft OS 2.0 descriptor wDescriptorType values. +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(u16)] +pub enum DescriptorType { + /// MS OS descriptor set header + SetHeaderDescriptor = 0, + /// Configuration subset header + SubsetHeaderConfiguration = 1, + /// Function subset header + SubsetHeaderFunction = 2, + /// Compatible device ID feature descriptor + FeatureCompatibleId = 3, + /// Registry property feature descriptor + FeatureRegProperty = 4, + /// Minimum USB resume time feature descriptor + FeatureMinResumeTime = 5, + /// Vendor revision feature descriptor + FeatureModelId = 6, + /// CCGP device descriptor feature descriptor + FeatureCcgpDevice = 7, + /// Vendor revision feature descriptor + FeatureVendorRevision = 8, +} + +/// Table 5. Descriptor set information structure. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct DescriptorSetInformation { + dwWindowsVersion: u32, + wMSOSDescriptorSetTotalLength: u16, + bMS_VendorCode: u8, + bAltEnumCode: u8, +} + +/// Table 4. Microsoft OS 2.0 platform capability descriptor header. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct PlatformDescriptor { + bLength: u8, + bDescriptorType: u8, + bDevCapabilityType: u8, + bReserved: u8, + platformCapabilityUUID: [u8; 16], + descriptor_set_information: DescriptorSetInformation, +} + +/// Table 10. Microsoft OS 2.0 descriptor set header. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct DescriptorSetHeader { + wLength: u16, + wDescriptorType: u16, + dwWindowsVersion: u32, + wTotalLength: u16, +} + +impl DescriptorSetHeader { + /// Creates a MS OS descriptor set header. + /// + /// `windows_version` is the minimum Windows version the descriptor set can apply to. + pub fn new(windows_version: u32) -> Self { + DescriptorSetHeader { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (Self::TYPE as u16).to_le(), + dwWindowsVersion: windows_version.to_le(), + wTotalLength: 0, + } + } +} + +impl Descriptor for DescriptorSetHeader { + const TYPE: DescriptorType = DescriptorType::SetHeaderDescriptor; + fn write_to(&self, buf: &mut [u8]) { + unsafe { transmute_write_to(self, buf) } + } +} + +impl DescriptorSet for DescriptorSetHeader { + const LENGTH_OFFSET: usize = 8; +} + +/// Table 11. Configuration subset header. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct ConfigurationSubsetHeader { + wLength: u16, + wDescriptorType: u16, + bConfigurationValue: u8, + bReserved: u8, + wTotalLength: u16, +} + +impl ConfigurationSubsetHeader { + /// Creates a configuration subset header + pub fn new(config: u8) -> Self { + ConfigurationSubsetHeader { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (Self::TYPE as u16).to_le(), + bConfigurationValue: config, + bReserved: 0, + wTotalLength: 0, + } + } +} + +impl Descriptor for ConfigurationSubsetHeader { + const TYPE: DescriptorType = DescriptorType::SubsetHeaderConfiguration; + fn write_to(&self, buf: &mut [u8]) { + unsafe { transmute_write_to(self, buf) } + } +} + +impl DescriptorSet for ConfigurationSubsetHeader { + const LENGTH_OFFSET: usize = 6; +} + +/// Table 12. Function subset header. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct FunctionSubsetHeader { + wLength: u16, + wDescriptorType: u16, + bFirstInterface: InterfaceNumber, + bReserved: u8, + wSubsetLength: u16, +} + +impl FunctionSubsetHeader { + /// Creates a function subset header + pub fn new(first_interface: InterfaceNumber) -> Self { + FunctionSubsetHeader { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (Self::TYPE as u16).to_le(), + bFirstInterface: first_interface, + bReserved: 0, + wSubsetLength: 0, + } + } +} + +impl Descriptor for FunctionSubsetHeader { + const TYPE: DescriptorType = DescriptorType::SubsetHeaderFunction; + fn write_to(&self, buf: &mut [u8]) { + unsafe { transmute_write_to(self, buf) } + } +} + +impl DescriptorSet for FunctionSubsetHeader { + const LENGTH_OFFSET: usize = 6; +} + +// Feature Descriptors + +/// A marker trait for feature descriptors that are valid at the device level. +pub trait DeviceLevelDescriptor: Descriptor {} + +/// A marker trait for feature descriptors that are valid at the function level. +pub trait FunctionLevelDescriptor: Descriptor {} + +/// Table 13. Microsoft OS 2.0 compatible ID descriptor. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct CompatibleIdFeatureDescriptor { + wLength: u16, + wDescriptorType: u16, + compatibleId: [u8; 8], + subCompatibleId: [u8; 8], +} + +impl DeviceLevelDescriptor for CompatibleIdFeatureDescriptor {} +impl FunctionLevelDescriptor for CompatibleIdFeatureDescriptor {} + +impl Descriptor for CompatibleIdFeatureDescriptor { + const TYPE: DescriptorType = DescriptorType::FeatureCompatibleId; + fn write_to(&self, buf: &mut [u8]) { + unsafe { transmute_write_to(self, buf) } + } +} + +impl CompatibleIdFeatureDescriptor { + /// Creates a compatible ID feature descriptor + /// + /// The ids must be 8 ASCII bytes or fewer. + pub fn new(compatible_id: &str, sub_compatible_id: &str) -> Self { + assert!(compatible_id.len() <= 8 && sub_compatible_id.len() <= 8); + let mut cid = [0u8; 8]; + (&mut cid[..compatible_id.len()]).copy_from_slice(compatible_id.as_bytes()); + let mut scid = [0u8; 8]; + (&mut scid[..sub_compatible_id.len()]).copy_from_slice(sub_compatible_id.as_bytes()); + Self::new_raw(cid, scid) + } + + fn new_raw(compatible_id: [u8; 8], sub_compatible_id: [u8; 8]) -> Self { + Self { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (Self::TYPE as u16).to_le(), + compatibleId: compatible_id, + subCompatibleId: sub_compatible_id, + } + } +} + +/// Table 14. Microsoft OS 2.0 registry property descriptor +#[allow(non_snake_case)] +pub struct RegistryPropertyFeatureDescriptor<'a> { + name: &'a str, + data: PropertyData<'a>, +} + +/// Data values that can be encoded into a registry property descriptor +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum PropertyData<'a> { + /// A registry property containing a string. + Sz(&'a str), + /// A registry property containing a string that expands environment variables. + ExpandSz(&'a str), + /// A registry property containing binary data. + Binary(&'a [u8]), + /// A registry property containing a little-endian 32-bit integer. + DwordLittleEndian(u32), + /// A registry property containing a big-endian 32-bit integer. + DwordBigEndian(u32), + /// A registry property containing a string that contains a symbolic link. + Link(&'a str), + /// A registry property containing multiple strings. + RegMultiSz(&'a [&'a str]), +} + +fn write_bytes(val: &[u8], buf: &mut [u8]) -> usize { + assert!(buf.len() >= val.len()); + buf[..val.len()].copy_from_slice(val); + val.len() +} + +fn write_utf16(val: &str, buf: &mut [u8]) -> usize { + let mut pos = 0; + for c in val.encode_utf16() { + pos += write_bytes(&c.to_le_bytes(), &mut buf[pos..]); + } + pos + write_bytes(&0u16.to_le_bytes(), &mut buf[pos..]) +} + +impl<'a> PropertyData<'a> { + /// Gets the `PropertyDataType` for this property value + pub fn kind(&self) -> PropertyDataType { + match self { + PropertyData::Sz(_) => PropertyDataType::Sz, + PropertyData::ExpandSz(_) => PropertyDataType::ExpandSz, + PropertyData::Binary(_) => PropertyDataType::Binary, + PropertyData::DwordLittleEndian(_) => PropertyDataType::DwordLittleEndian, + PropertyData::DwordBigEndian(_) => PropertyDataType::DwordBigEndian, + PropertyData::Link(_) => PropertyDataType::Link, + PropertyData::RegMultiSz(_) => PropertyDataType::RegMultiSz, + } + } + + /// Gets the size (in bytes) of this property value when encoded. + pub fn size(&self) -> usize { + match self { + PropertyData::Sz(val) | PropertyData::ExpandSz(val) | PropertyData::Link(val) => { + core::mem::size_of::() * (val.encode_utf16().count() + 1) + } + PropertyData::Binary(val) => val.len(), + PropertyData::DwordLittleEndian(val) | PropertyData::DwordBigEndian(val) => core::mem::size_of_val(val), + PropertyData::RegMultiSz(val) => { + core::mem::size_of::() * val.iter().map(|x| x.encode_utf16().count() + 1).sum::() + 1 + } + } + } + + /// Encodes the data for this property value and writes it to `buf`. + pub fn write(&self, buf: &mut [u8]) -> usize { + match self { + PropertyData::Sz(val) | PropertyData::ExpandSz(val) | PropertyData::Link(val) => write_utf16(val, buf), + PropertyData::Binary(val) => write_bytes(val, buf), + PropertyData::DwordLittleEndian(val) => write_bytes(&val.to_le_bytes(), buf), + PropertyData::DwordBigEndian(val) => write_bytes(&val.to_be_bytes(), buf), + PropertyData::RegMultiSz(val) => { + let mut pos = 0; + for s in *val { + pos += write_utf16(s, &mut buf[pos..]); + } + pos + write_bytes(&0u16.to_le_bytes(), &mut buf[pos..]) + } + } + } +} + +/// Table 15. wPropertyDataType values for the Microsoft OS 2.0 registry property descriptor. +#[derive(Clone, Copy, PartialEq, Eq)] +#[repr(u16)] +pub enum PropertyDataType { + /// A registry property containing a string. + Sz = 1, + /// A registry property containing a string that expands environment variables. + ExpandSz = 2, + /// A registry property containing binary data. + Binary = 3, + /// A registry property containing a little-endian 32-bit integer. + DwordLittleEndian = 4, + /// A registry property containing a big-endian 32-bit integer. + DwordBigEndian = 5, + /// A registry property containing a string that contains a symbolic link. + Link = 6, + /// A registry property containing multiple strings. + RegMultiSz = 7, +} + +impl<'a> DeviceLevelDescriptor for RegistryPropertyFeatureDescriptor<'a> {} +impl<'a> FunctionLevelDescriptor for RegistryPropertyFeatureDescriptor<'a> {} + +impl<'a> Descriptor for RegistryPropertyFeatureDescriptor<'a> { + const TYPE: DescriptorType = DescriptorType::FeatureRegProperty; + + fn size(&self) -> usize { + 10 + self.name_size() + self.data.size() + } + + fn write_to(&self, buf: &mut [u8]) { + assert!(buf.len() >= self.size(), "MS OS descriptor buffer full"); + + let mut pos = 0; + pos += write_bytes(&(self.size() as u16).to_le_bytes(), &mut buf[pos..]); + pos += write_bytes(&(Self::TYPE as u16).to_le_bytes(), &mut buf[pos..]); + pos += write_bytes(&(self.data.kind() as u16).to_le_bytes(), &mut buf[pos..]); + pos += write_bytes(&(self.name_size() as u16).to_le_bytes(), &mut buf[pos..]); + pos += write_utf16(self.name, &mut buf[pos..]); + pos += write_bytes(&(self.data.size() as u16).to_le_bytes(), &mut buf[pos..]); + self.data.write(&mut buf[pos..]); + } +} + +impl<'a> RegistryPropertyFeatureDescriptor<'a> { + /// A registry property. + pub fn new(name: &'a str, data: PropertyData<'a>) -> Self { + Self { name, data } + } + + fn name_size(&self) -> usize { + core::mem::size_of::() * (self.name.encode_utf16().count() + 1) + } +} + +/// Table 16. Microsoft OS 2.0 minimum USB recovery time descriptor. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct MinimumRecoveryTimeDescriptor { + wLength: u16, + wDescriptorType: u16, + bResumeRecoveryTime: u8, + bResumeSignalingTime: u8, +} + +impl DeviceLevelDescriptor for MinimumRecoveryTimeDescriptor {} + +impl Descriptor for MinimumRecoveryTimeDescriptor { + const TYPE: DescriptorType = DescriptorType::FeatureMinResumeTime; + fn write_to(&self, buf: &mut [u8]) { + unsafe { transmute_write_to(self, buf) } + } +} + +impl MinimumRecoveryTimeDescriptor { + /// Times are in milliseconds. + /// + /// `resume_recovery_time` must be >= 0 and <= 10. + /// `resume_signaling_time` must be >= 1 and <= 20. + pub fn new(resume_recovery_time: u8, resume_signaling_time: u8) -> Self { + assert!(resume_recovery_time <= 10); + assert!(resume_signaling_time >= 1 && resume_signaling_time <= 20); + Self { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (Self::TYPE as u16).to_le(), + bResumeRecoveryTime: resume_recovery_time, + bResumeSignalingTime: resume_signaling_time, + } + } +} + +/// Table 17. Microsoft OS 2.0 model ID descriptor. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct ModelIdDescriptor { + wLength: u16, + wDescriptorType: u16, + modelId: [u8; 16], +} + +impl DeviceLevelDescriptor for ModelIdDescriptor {} + +impl Descriptor for ModelIdDescriptor { + const TYPE: DescriptorType = DescriptorType::FeatureModelId; + fn write_to(&self, buf: &mut [u8]) { + unsafe { transmute_write_to(self, buf) } + } +} + +impl ModelIdDescriptor { + /// Creates a new model ID descriptor + /// + /// `model_id` should be a uuid that uniquely identifies a physical device. + pub fn new(model_id: u128) -> Self { + Self { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (Self::TYPE as u16).to_le(), + modelId: model_id.to_le_bytes(), + } + } +} + +/// Table 18. Microsoft OS 2.0 CCGP device descriptor. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct CcgpDeviceDescriptor { + wLength: u16, + wDescriptorType: u16, +} + +impl DeviceLevelDescriptor for CcgpDeviceDescriptor {} + +impl Descriptor for CcgpDeviceDescriptor { + const TYPE: DescriptorType = DescriptorType::FeatureCcgpDevice; + fn write_to(&self, buf: &mut [u8]) { + unsafe { transmute_write_to(self, buf) } + } +} + +impl CcgpDeviceDescriptor { + /// Creates a new CCGP device descriptor + pub fn new() -> Self { + Self { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (Self::TYPE as u16).to_le(), + } + } +} + +/// Table 19. Microsoft OS 2.0 vendor revision descriptor. +#[allow(non_snake_case)] +#[repr(C, packed(1))] +pub struct VendorRevisionDescriptor { + wLength: u16, + wDescriptorType: u16, + /// Revision number associated with the descriptor set. Modify it every time you add/modify a registry property or + /// other MS OS descriptor. Shell set to greater than or equal to 1. + VendorRevision: u16, +} + +impl DeviceLevelDescriptor for VendorRevisionDescriptor {} +impl FunctionLevelDescriptor for VendorRevisionDescriptor {} + +impl Descriptor for VendorRevisionDescriptor { + const TYPE: DescriptorType = DescriptorType::FeatureVendorRevision; + fn write_to(&self, buf: &mut [u8]) { + unsafe { transmute_write_to(self, buf) } + } +} + +impl VendorRevisionDescriptor { + /// Creates a new vendor revision descriptor + pub fn new(revision: u16) -> Self { + assert!(revision >= 1); + Self { + wLength: (size_of::() as u16).to_le(), + wDescriptorType: (Self::TYPE as u16).to_le(), + VendorRevision: revision.to_le(), + } + } +} diff --git a/embassy-usb/src/types.rs b/embassy-usb/src/types.rs index b8717ffa9..c7a47f7e4 100644 --- a/embassy-usb/src/types.rs +++ b/embassy-usb/src/types.rs @@ -1,112 +1,10 @@ -/// Direction of USB traffic. Note that in the USB standard the direction is always indicated from -/// the perspective of the host, which is backward for devices, but the standard directions are used -/// for consistency. -/// -/// The values of the enum also match the direction bit used in endpoint addresses and control -/// request types. -#[repr(u8)] -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum UsbDirection { - /// Host to device (OUT) - Out = 0x00, - /// Device to host (IN) - In = 0x80, -} - -impl From for UsbDirection { - fn from(value: u8) -> Self { - unsafe { core::mem::transmute(value & 0x80) } - } -} - -/// USB endpoint transfer type. The values of this enum can be directly cast into `u8` to get the -/// transfer bmAttributes transfer type bits. -#[repr(u8)] -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum EndpointType { - /// Control endpoint. Used for device management. Only the host can initiate requests. Usually - /// used only endpoint 0. - Control = 0b00, - /// Isochronous endpoint. Used for time-critical unreliable data. Not implemented yet. - Isochronous = 0b01, - /// Bulk endpoint. Used for large amounts of best-effort reliable data. - Bulk = 0b10, - /// Interrupt endpoint. Used for small amounts of time-critical reliable data. - Interrupt = 0b11, -} - -/// Type-safe endpoint address. -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct EndpointAddress(u8); - -impl From for EndpointAddress { - #[inline] - fn from(addr: u8) -> EndpointAddress { - EndpointAddress(addr) - } -} - -impl From for u8 { - #[inline] - fn from(addr: EndpointAddress) -> u8 { - addr.0 - } -} - -impl EndpointAddress { - const INBITS: u8 = UsbDirection::In as u8; - - /// Constructs a new EndpointAddress with the given index and direction. - #[inline] - pub fn from_parts(index: usize, dir: UsbDirection) -> Self { - EndpointAddress(index as u8 | dir as u8) - } - - /// Gets the direction part of the address. - #[inline] - pub fn direction(&self) -> UsbDirection { - if (self.0 & Self::INBITS) != 0 { - UsbDirection::In - } else { - UsbDirection::Out - } - } - - /// Returns true if the direction is IN, otherwise false. - #[inline] - pub fn is_in(&self) -> bool { - (self.0 & Self::INBITS) != 0 - } - - /// Returns true if the direction is OUT, otherwise false. - #[inline] - pub fn is_out(&self) -> bool { - (self.0 & Self::INBITS) == 0 - } - - /// Gets the index part of the endpoint address. - #[inline] - pub fn index(&self) -> usize { - (self.0 & !Self::INBITS) as usize - } -} - -#[derive(Copy, Clone, Eq, PartialEq, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct EndpointInfo { - pub addr: EndpointAddress, - pub ep_type: EndpointType, - pub max_packet_size: u16, - pub interval: u8, -} +//! USB types. /// A handle for a USB interface that contains its number. -#[derive(Copy, Clone, Eq, PartialEq)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct InterfaceNumber(pub(crate) u8); +#[repr(transparent)] +pub struct InterfaceNumber(pub u8); impl InterfaceNumber { pub(crate) fn new(index: u8) -> InterfaceNumber { @@ -123,7 +21,8 @@ impl From for u8 { /// A handle for a USB string descriptor that contains its index. #[derive(Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct StringIndex(u8); +#[repr(transparent)] +pub struct StringIndex(pub u8); impl StringIndex { pub(crate) fn new(index: u8) -> StringIndex { diff --git a/examples/.cargo/config.toml b/examples/.cargo/config.toml new file mode 100644 index 000000000..84d266320 --- /dev/null +++ b/examples/.cargo/config.toml @@ -0,0 +1,3 @@ +[profile.release] +# Allows defmt to display log locations even in release +debug = true \ No newline at end of file diff --git a/examples/boot/application/nrf/.cargo/config.toml b/examples/boot/application/nrf/.cargo/config.toml index 8ca28df39..17616a054 100644 --- a/examples/boot/application/nrf/.cargo/config.toml +++ b/examples/boot/application/nrf/.cargo/config.toml @@ -1,6 +1,6 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -# replace nRF82840_xxAA with your chip as listed in `probe-run --list-chips` -runner = "probe-run --chip nRF52840_xxAA" +# replace nRF82840_xxAA with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip nRF52840_xxAA" [build] target = "thumbv7em-none-eabi" diff --git a/examples/boot/application/nrf/Cargo.toml b/examples/boot/application/nrf/Cargo.toml index b9ff92578..2a0cf7818 100644 --- a/examples/boot/application/nrf/Cargo.toml +++ b/examples/boot/application/nrf/Cargo.toml @@ -2,19 +2,26 @@ edition = "2021" name = "embassy-boot-nrf-examples" version = "0.1.0" +license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.1.0", path = "../../../../embassy-sync" } -embassy-executor = { version = "0.1.0", path = "../../../../embassy-executor", features = ["nightly", "integrated-timers"] } -embassy-time = { version = "0.1.0", path = "../../../../embassy-time", features = ["nightly"] } -embassy-nrf = { version = "0.1.0", path = "../../../../embassy-nrf", features = ["time-driver-rtc1", "gpiote", "nightly", "nrf52840"] } -embassy-boot-nrf = { version = "0.1.0", path = "../../../../embassy-boot/nrf" } +embassy-sync = { version = "0.2.0", path = "../../../../embassy-sync" } +embassy-executor = { version = "0.2.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers", "arch-cortex-m", "executor-thread"] } +embassy-time = { version = "0.1.2", path = "../../../../embassy-time", features = ["nightly"] } +embassy-nrf = { version = "0.1.0", path = "../../../../embassy-nrf", features = ["time-driver-rtc1", "gpiote", "nightly"] } +embassy-boot = { version = "0.1.0", path = "../../../../embassy-boot/boot", features = ["nightly"] } +embassy-boot-nrf = { version = "0.1.0", path = "../../../../embassy-boot/nrf", features = ["nightly"] } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } panic-reset = { version = "0.1.1" } embedded-hal = { version = "0.2.6" } -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" + +[features] +ed25519-dalek = ["embassy-boot/ed25519-dalek"] +ed25519-salty = ["embassy-boot/ed25519-salty"] +skip-include = [] diff --git a/examples/boot/application/nrf/README.md b/examples/boot/application/nrf/README.md index 703377a20..9d6d20336 100644 --- a/examples/boot/application/nrf/README.md +++ b/examples/boot/application/nrf/README.md @@ -1,6 +1,6 @@ # Examples using bootloader -Example for nRF52 demonstrating the bootloader. The example consists of application binaries, 'a' +Example for nRF demonstrating the bootloader. The example consists of application binaries, 'a' which allows you to press a button to start the DFU process, and 'b' which is the updated application. @@ -20,15 +20,19 @@ application. cp memory-bl.x ../../bootloader/nrf/memory.x # Flash bootloader -cargo flash --manifest-path ../../bootloader/nrf/Cargo.toml --features embassy-nrf/nrf52840 --release --chip nRF52840_xxAA +cargo flash --manifest-path ../../bootloader/nrf/Cargo.toml --features embassy-nrf/nrf52840 --target thumbv7em-none-eabi --release --chip nRF52840_xxAA # Build 'b' -cargo build --release --bin b +cargo build --release --bin b --features embassy-nrf/nrf52840 # Generate binary for 'b' -cargo objcopy --release --bin b -- -O binary b.bin +cargo objcopy --release --bin b --features embassy-nrf/nrf52840 --target thumbv7em-none-eabi -- -O binary b.bin ``` # Flash `a` (which includes b.bin) ``` -cargo flash --release --bin a --chip nRF52840_xxAA +cargo flash --release --bin a --features embassy-nrf/nrf52840 --target thumbv7em-none-eabi --chip nRF52840_xxAA ``` + +You should then see a solid LED. Pressing button 1 will cause the DFU to be loaded by the bootloader. Upon +successfully loading, you'll see the LED flash. After 5 seconds, because there is no petting of the watchdog, +you'll see the LED go solid again. This indicates that the bootloader has reverted the update. diff --git a/examples/boot/application/nrf/memory-bl-nrf91.x b/examples/boot/application/nrf/memory-bl-nrf91.x new file mode 100644 index 000000000..14ceffa73 --- /dev/null +++ b/examples/boot/application/nrf/memory-bl-nrf91.x @@ -0,0 +1,19 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + /* Assumes Secure Partition Manager (SPM) flashed at the start */ + FLASH : ORIGIN = 0x00050000, LENGTH = 24K + BOOTLOADER_STATE : ORIGIN = 0x00056000, LENGTH = 4K + ACTIVE : ORIGIN = 0x00057000, LENGTH = 64K + DFU : ORIGIN = 0x00067000, LENGTH = 68K + RAM (rwx) : ORIGIN = 0x20018000, LENGTH = 32K +} + +__bootloader_state_start = ORIGIN(BOOTLOADER_STATE); +__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE); + +__bootloader_active_start = ORIGIN(ACTIVE); +__bootloader_active_end = ORIGIN(ACTIVE) + LENGTH(ACTIVE); + +__bootloader_dfu_start = ORIGIN(DFU); +__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU); diff --git a/examples/boot/application/nrf/memory-bl.x b/examples/boot/application/nrf/memory-bl.x index 8a32b905f..257d65644 100644 --- a/examples/boot/application/nrf/memory-bl.x +++ b/examples/boot/application/nrf/memory-bl.x @@ -5,7 +5,7 @@ MEMORY BOOTLOADER_STATE : ORIGIN = 0x00006000, LENGTH = 4K ACTIVE : ORIGIN = 0x00007000, LENGTH = 64K DFU : ORIGIN = 0x00017000, LENGTH = 68K - RAM (rwx) : ORIGIN = 0x20000008, LENGTH = 32K + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32K } __bootloader_state_start = ORIGIN(BOOTLOADER_STATE); diff --git a/examples/boot/application/nrf/memory-nrf91.x b/examples/boot/application/nrf/memory-nrf91.x new file mode 100644 index 000000000..2bc13c0d6 --- /dev/null +++ b/examples/boot/application/nrf/memory-nrf91.x @@ -0,0 +1,16 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + /* Assumes Secure Partition Manager (SPM) flashed at the start */ + BOOTLOADER : ORIGIN = 0x00050000, LENGTH = 24K + BOOTLOADER_STATE : ORIGIN = 0x00056000, LENGTH = 4K + FLASH : ORIGIN = 0x00057000, LENGTH = 64K + DFU : ORIGIN = 0x00067000, LENGTH = 68K + RAM (rwx) : ORIGIN = 0x20018000, LENGTH = 32K +} + +__bootloader_state_start = ORIGIN(BOOTLOADER_STATE); +__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE); + +__bootloader_dfu_start = ORIGIN(DFU); +__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU); diff --git a/examples/boot/application/nrf/memory.x b/examples/boot/application/nrf/memory.x index 3a54ca460..c6926e422 100644 --- a/examples/boot/application/nrf/memory.x +++ b/examples/boot/application/nrf/memory.x @@ -5,7 +5,7 @@ MEMORY BOOTLOADER_STATE : ORIGIN = 0x00006000, LENGTH = 4K FLASH : ORIGIN = 0x00007000, LENGTH = 64K DFU : ORIGIN = 0x00017000, LENGTH = 68K - RAM (rwx) : ORIGIN = 0x20000008, LENGTH = 32K + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32K } __bootloader_state_start = ORIGIN(BOOTLOADER_STATE); diff --git a/examples/boot/application/nrf/src/bin/a.rs b/examples/boot/application/nrf/src/bin/a.rs index bd8fa3246..021d77f3b 100644 --- a/examples/boot/application/nrf/src/bin/a.rs +++ b/examples/boot/application/nrf/src/bin/a.rs @@ -1,42 +1,71 @@ #![no_std] #![no_main] #![macro_use] -#![feature(generic_associated_types)] #![feature(type_alias_impl_trait)] -use embassy_boot_nrf::FirmwareUpdater; +use embassy_boot_nrf::{FirmwareUpdater, FirmwareUpdaterConfig}; use embassy_embedded_hal::adapter::BlockingAsync; use embassy_executor::Spawner; use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pull}; use embassy_nrf::nvmc::Nvmc; +use embassy_nrf::wdt::{self, Watchdog}; +use embassy_sync::mutex::Mutex; use panic_reset as _; +#[cfg(feature = "skip-include")] +static APP_B: &[u8] = &[0, 1, 2, 3]; +#[cfg(not(feature = "skip-include"))] static APP_B: &[u8] = include_bytes!("../../b.bin"); #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_nrf::init(Default::default()); + let mut button = Input::new(p.P0_11, Pull::Up); let mut led = Output::new(p.P0_13, Level::Low, OutputDrive::Standard); + //let mut led = Output::new(p.P1_10, Level::Low, OutputDrive::Standard); //let mut button = Input::new(p.P1_02, Pull::Up); - let nvmc = Nvmc::new(p.NVMC); - let mut nvmc = BlockingAsync::new(nvmc); + // nRF91 DK + // let mut led = Output::new(p.P0_02, Level::Low, OutputDrive::Standard); + // let mut button = Input::new(p.P0_06, Pull::Up); - let mut updater = FirmwareUpdater::default(); + // The following code block illustrates how to obtain a watchdog that is configured + // as per the existing watchdog. Ordinarily, we'd use the handle returned to "pet" the + // watchdog periodically. If we don't, and we're not going to for this example, then + // the watchdog will cause the device to reset as per its configured timeout in the bootloader. + // This helps is avoid a situation where new firmware might be bad and block our executor. + // If firmware is bad in this way then the bootloader will revert to any previous version. + let wdt_config = wdt::Config::try_new(&p.WDT).unwrap(); + let (_wdt, [_wdt_handle]) = match Watchdog::try_new(p.WDT, wdt_config) { + Ok(x) => x, + Err(_) => { + // Watchdog already active with the wrong number of handles, waiting for it to timeout... + loop { + cortex_m::asm::wfe(); + } + } + }; + + let nvmc = Nvmc::new(p.NVMC); + let nvmc = Mutex::new(BlockingAsync::new(nvmc)); + + let config = FirmwareUpdaterConfig::from_linkerfile(&nvmc); + let mut updater = FirmwareUpdater::new(config); loop { led.set_low(); button.wait_for_any_edge().await; if button.is_low() { let mut offset = 0; + let mut magic = [0; 4]; for chunk in APP_B.chunks(4096) { let mut buf: [u8; 4096] = [0; 4096]; buf[..chunk.len()].copy_from_slice(chunk); - updater.write_firmware(offset, &buf, &mut nvmc, 4096).await.unwrap(); + updater.write_firmware(&mut magic, offset, &buf).await.unwrap(); offset += chunk.len(); } - updater.update(&mut nvmc).await.unwrap(); + updater.mark_updated(&mut magic).await.unwrap(); led.set_high(); cortex_m::peripheral::SCB::sys_reset(); } diff --git a/examples/boot/application/nrf/src/bin/b.rs b/examples/boot/application/nrf/src/bin/b.rs index 5394bf0c7..15ebce5fa 100644 --- a/examples/boot/application/nrf/src/bin/b.rs +++ b/examples/boot/application/nrf/src/bin/b.rs @@ -1,7 +1,6 @@ #![no_std] #![no_main] #![macro_use] -#![feature(generic_associated_types)] #![feature(type_alias_impl_trait)] use embassy_executor::Spawner; @@ -13,7 +12,10 @@ use panic_reset as _; async fn main(_spawner: Spawner) { let p = embassy_nrf::init(Default::default()); let mut led = Output::new(p.P0_13, Level::Low, OutputDrive::Standard); - //let mut led = Output::new(p.P1_10, Level::Low, OutputDrive::Standard); + // let mut led = Output::new(p.P1_10, Level::Low, OutputDrive::Standard); + + // nRF91 DK + // let mut led = Output::new(p.P0_02, Level::Low, OutputDrive::Standard); loop { led.set_high(); diff --git a/examples/boot/application/rp/.cargo/config.toml b/examples/boot/application/rp/.cargo/config.toml new file mode 100644 index 000000000..cd8d1ef02 --- /dev/null +++ b/examples/boot/application/rp/.cargo/config.toml @@ -0,0 +1,12 @@ +[unstable] +build-std = ["core"] +build-std-features = ["panic_immediate_abort"] + +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +runner = "probe-rs run --chip RP2040" + +[build] +target = "thumbv6m-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/examples/boot/application/rp/Cargo.toml b/examples/boot/application/rp/Cargo.toml new file mode 100644 index 000000000..95b2da954 --- /dev/null +++ b/examples/boot/application/rp/Cargo.toml @@ -0,0 +1,35 @@ +[package] +edition = "2021" +name = "embassy-boot-rp-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-sync = { version = "0.2.0", path = "../../../../embassy-sync" } +embassy-executor = { version = "0.2.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers", "arch-cortex-m", "executor-thread"] } +embassy-time = { version = "0.1.2", path = "../../../../embassy-time", features = ["nightly"] } +embassy-rp = { version = "0.1.0", path = "../../../../embassy-rp", features = ["time-driver", "unstable-traits", "nightly"] } +embassy-boot-rp = { version = "0.1.0", path = "../../../../embassy-boot/rp", features = ["nightly"] } +embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } + +defmt = "0.3" +defmt-rtt = "0.4" +panic-probe = { version = "0.3", features = ["print-defmt"], optional = true } +panic-reset = { version = "0.1.1", optional = true } +embedded-hal = { version = "0.2.6" } + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-storage = "0.3.0" + +[features] +default = ["panic-reset"] +debug = [ + "embassy-rp/defmt", + "embassy-boot-rp/defmt", + "panic-probe" +] +skip-include = [] + +[profile.release] +debug = true diff --git a/examples/boot/application/rp/README.md b/examples/boot/application/rp/README.md new file mode 100644 index 000000000..41304c526 --- /dev/null +++ b/examples/boot/application/rp/README.md @@ -0,0 +1,28 @@ +# Examples using bootloader + +Example for Raspberry Pi Pico demonstrating the bootloader. The example consists of application binaries, 'a' +which waits for 5 seconds before flashing the 'b' binary, which blinks the LED. + +NOTE: The 'b' binary does not mark the new binary as active, so if you reset the device, it will roll back to the 'a' binary before automatically updating it again. + +## Prerequisites + +* `cargo-binutils` +* `cargo-flash` +* `embassy-boot-rp` + +## Usage + +``` +# Flash bootloader +cargo flash --manifest-path ../../bootloader/rp/Cargo.toml --release --chip RP2040 + +# Build 'b' +cargo build --release --bin b + +# Generate binary for 'b' +cargo objcopy --release --bin b -- -O binary b.bin + +# Flash `a` (which includes b.bin) +cargo flash --release --bin a --chip RP2040 +``` diff --git a/examples/boot/application/rp/build.rs b/examples/boot/application/rp/build.rs new file mode 100644 index 000000000..30691aa97 --- /dev/null +++ b/examples/boot/application/rp/build.rs @@ -0,0 +1,35 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/examples/boot/application/rp/memory.x b/examples/boot/application/rp/memory.x new file mode 100644 index 000000000..c19473114 --- /dev/null +++ b/examples/boot/application/rp/memory.x @@ -0,0 +1,15 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 + BOOTLOADER_STATE : ORIGIN = 0x10006000, LENGTH = 4K + FLASH : ORIGIN = 0x10007000, LENGTH = 512K + DFU : ORIGIN = 0x10087000, LENGTH = 516K + RAM : ORIGIN = 0x20000000, LENGTH = 256K +} + +__bootloader_state_start = ORIGIN(BOOTLOADER_STATE) - ORIGIN(BOOT2); +__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE) - ORIGIN(BOOT2); + +__bootloader_dfu_start = ORIGIN(DFU) - ORIGIN(BOOT2); +__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU) - ORIGIN(BOOT2); diff --git a/examples/boot/application/rp/src/bin/a.rs b/examples/boot/application/rp/src/bin/a.rs new file mode 100644 index 000000000..c8497494c --- /dev/null +++ b/examples/boot/application/rp/src/bin/a.rs @@ -0,0 +1,67 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use core::cell::RefCell; + +use defmt_rtt as _; +use embassy_boot_rp::*; +use embassy_executor::Spawner; +use embassy_rp::flash::Flash; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::watchdog::Watchdog; +use embassy_sync::blocking_mutex::Mutex; +use embassy_time::{Duration, Timer}; +use embedded_storage::nor_flash::NorFlash; +#[cfg(feature = "panic-probe")] +use panic_probe as _; +#[cfg(feature = "panic-reset")] +use panic_reset as _; + +#[cfg(feature = "skip-include")] +static APP_B: &[u8] = &[0, 1, 2, 3]; +#[cfg(not(feature = "skip-include"))] +static APP_B: &[u8] = include_bytes!("../../b.bin"); + +const FLASH_SIZE: usize = 2 * 1024 * 1024; + +#[embassy_executor::main] +async fn main(_s: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut led = Output::new(p.PIN_25, Level::Low); + + // Override bootloader watchdog + let mut watchdog = Watchdog::new(p.WATCHDOG); + watchdog.start(Duration::from_secs(8)); + + let flash: Flash<_, FLASH_SIZE> = Flash::new(p.FLASH); + let flash = Mutex::new(RefCell::new(flash)); + + let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash); + let mut updater = BlockingFirmwareUpdater::new(config); + + Timer::after(Duration::from_secs(5)).await; + watchdog.feed(); + led.set_high(); + let mut offset = 0; + let mut buf: AlignedBuffer<4096> = AlignedBuffer([0; 4096]); + defmt::info!("preparing update"); + let writer = updater + .prepare_update(&mut buf.0[..1]) + .map_err(|e| defmt::warn!("E: {:?}", defmt::Debug2Format(&e))) + .unwrap(); + defmt::info!("writer created, starting write"); + for chunk in APP_B.chunks(4096) { + buf.0[..chunk.len()].copy_from_slice(chunk); + defmt::info!("writing block at offset {}", offset); + writer.write(offset, &buf.0[..]).unwrap(); + offset += chunk.len() as u32; + } + watchdog.feed(); + defmt::info!("firmware written, marking update"); + updater.mark_updated(&mut buf.0[..1]).unwrap(); + Timer::after(Duration::from_secs(2)).await; + led.set_low(); + defmt::info!("update marked, resetting"); + cortex_m::peripheral::SCB::sys_reset(); +} diff --git a/examples/boot/application/rp/src/bin/b.rs b/examples/boot/application/rp/src/bin/b.rs new file mode 100644 index 000000000..47dec329c --- /dev/null +++ b/examples/boot/application/rp/src/bin/b.rs @@ -0,0 +1,23 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use embassy_executor::Spawner; +use embassy_rp::gpio; +use embassy_time::{Duration, Timer}; +use gpio::{Level, Output}; +use {defmt_rtt as _, panic_reset as _}; + +#[embassy_executor::main] +async fn main(_s: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut led = Output::new(p.PIN_25, Level::Low); + + loop { + led.set_high(); + Timer::after(Duration::from_millis(100)).await; + + led.set_low(); + Timer::after(Duration::from_millis(100)).await; + } +} diff --git a/examples/boot/application/stm32f3/.cargo/config.toml b/examples/boot/application/stm32f3/.cargo/config.toml index a76d6cab4..4a7ec0a5b 100644 --- a/examples/boot/application/stm32f3/.cargo/config.toml +++ b/examples/boot/application/stm32f3/.cargo/config.toml @@ -1,6 +1,6 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -# replace STM32F429ZITx with your chip as listed in `probe-run --list-chips` -runner = "probe-run --chip STM32F303VCTx" +# replace STM32F429ZITx with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32F303VCTx" [build] target = "thumbv7em-none-eabihf" diff --git a/examples/boot/application/stm32f3/Cargo.toml b/examples/boot/application/stm32f3/Cargo.toml index f143d1e8d..3b0fc4d9d 100644 --- a/examples/boot/application/stm32f3/Cargo.toml +++ b/examples/boot/application/stm32f3/Cargo.toml @@ -2,21 +2,22 @@ edition = "2021" name = "embassy-boot-stm32f3-examples" version = "0.1.0" +license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.1.0", path = "../../../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.1.0", path = "../../../../embassy-executor", features = ["nightly", "integrated-timers"] } -embassy-time = { version = "0.1.0", path = "../../../../embassy-time", features = ["nightly", "tick-32768hz"] } +embassy-sync = { version = "0.2.0", path = "../../../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.2.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers"] } +embassy-time = { version = "0.1.2", path = "../../../../embassy-time", features = ["nightly", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["unstable-traits", "nightly", "stm32f303re", "time-driver-any", "exti"] } -embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32" } +embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = ["nightly"] } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } panic-reset = { version = "0.1.1" } embedded-hal = { version = "0.2.6" } -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" [features] @@ -25,3 +26,4 @@ defmt = [ "embassy-stm32/defmt", "embassy-boot-stm32/defmt", ] +skip-include = [] diff --git a/examples/boot/application/stm32f3/src/bin/a.rs b/examples/boot/application/stm32f3/src/bin/a.rs index 11eecc5e2..c0a11d699 100644 --- a/examples/boot/application/stm32f3/src/bin/a.rs +++ b/examples/boot/application/stm32f3/src/bin/a.rs @@ -4,21 +4,25 @@ #[cfg(feature = "defmt-rtt")] use defmt_rtt::*; -use embassy_boot_stm32::FirmwareUpdater; +use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater, FirmwareUpdaterConfig}; use embassy_embedded_hal::adapter::BlockingAsync; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; -use embassy_stm32::flash::Flash; +use embassy_stm32::flash::{Flash, WRITE_SIZE}; use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; +use embassy_sync::mutex::Mutex; use panic_reset as _; +#[cfg(feature = "skip-include")] +static APP_B: &[u8] = &[0, 1, 2, 3]; +#[cfg(not(feature = "skip-include"))] static APP_B: &[u8] = include_bytes!("../../b.bin"); #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); - let flash = Flash::unlock(p.FLASH); - let mut flash = BlockingAsync::new(flash); + let flash = Flash::new_blocking(p.FLASH); + let flash = Mutex::new(BlockingAsync::new(flash)); let button = Input::new(p.PC13, Pull::Up); let mut button = ExtiInput::new(button, p.EXTI13); @@ -26,16 +30,18 @@ async fn main(_spawner: Spawner) { let mut led = Output::new(p.PA5, Level::Low, Speed::Low); led.set_high(); - let mut updater = FirmwareUpdater::default(); + let config = FirmwareUpdaterConfig::from_linkerfile(&flash); + let mut updater = FirmwareUpdater::new(config); button.wait_for_falling_edge().await; let mut offset = 0; + let mut magic = AlignedBuffer([0; WRITE_SIZE]); for chunk in APP_B.chunks(2048) { let mut buf: [u8; 2048] = [0; 2048]; buf[..chunk.len()].copy_from_slice(chunk); - updater.write_firmware(offset, &buf, &mut flash, 2048).await.unwrap(); + updater.write_firmware(magic.as_mut(), offset, &buf).await.unwrap(); offset += chunk.len(); } - updater.update(&mut flash).await.unwrap(); + updater.mark_updated(magic.as_mut()).await.unwrap(); led.set_low(); cortex_m::peripheral::SCB::sys_reset(); } diff --git a/examples/boot/application/stm32f7/.cargo/config.toml b/examples/boot/application/stm32f7/.cargo/config.toml index a90e1ccbb..9088eea6e 100644 --- a/examples/boot/application/stm32f7/.cargo/config.toml +++ b/examples/boot/application/stm32f7/.cargo/config.toml @@ -1,6 +1,6 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -# replace STM32F429ZITx with your chip as listed in `probe-run --list-chips` -runner = "probe-run --chip STM32F767ZITx -v" +# replace STM32F429ZITx with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32F767ZITx" [build] target = "thumbv7em-none-eabihf" diff --git a/examples/boot/application/stm32f7/Cargo.toml b/examples/boot/application/stm32f7/Cargo.toml index 29c87eee1..323b4ab2c 100644 --- a/examples/boot/application/stm32f7/Cargo.toml +++ b/examples/boot/application/stm32f7/Cargo.toml @@ -2,21 +2,23 @@ edition = "2021" name = "embassy-boot-stm32f7-examples" version = "0.1.0" +license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.1.0", path = "../../../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.1.0", path = "../../../../embassy-executor", features = ["nightly", "integrated-timers"] } -embassy-time = { version = "0.1.0", path = "../../../../embassy-time", features = ["nightly", "tick-32768hz"] } +embassy-sync = { version = "0.2.0", path = "../../../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.2.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers"] } +embassy-time = { version = "0.1.2", path = "../../../../embassy-time", features = ["nightly", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["unstable-traits", "nightly", "stm32f767zi", "time-driver-any", "exti"] } -embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32" } +embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = ["nightly"] } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } panic-reset = { version = "0.1.1" } embedded-hal = { version = "0.2.6" } +embedded-storage = "0.3.0" -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" [features] @@ -25,3 +27,4 @@ defmt = [ "embassy-stm32/defmt", "embassy-boot-stm32/defmt", ] +skip-include = [] diff --git a/examples/boot/application/stm32f7/src/bin/a.rs b/examples/boot/application/stm32f7/src/bin/a.rs index a3b66e7c9..dea682a96 100644 --- a/examples/boot/application/stm32f7/src/bin/a.rs +++ b/examples/boot/application/stm32f7/src/bin/a.rs @@ -2,23 +2,29 @@ #![no_main] #![feature(type_alias_impl_trait)] +use core::cell::RefCell; + #[cfg(feature = "defmt-rtt")] use defmt_rtt::*; -use embassy_boot_stm32::FirmwareUpdater; -use embassy_embedded_hal::adapter::BlockingAsync; +use embassy_boot_stm32::{AlignedBuffer, BlockingFirmwareUpdater, FirmwareUpdaterConfig}; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; -use embassy_stm32::flash::Flash; +use embassy_stm32::flash::{Flash, WRITE_SIZE}; use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; +use embassy_sync::blocking_mutex::Mutex; +use embedded_storage::nor_flash::NorFlash; use panic_reset as _; +#[cfg(feature = "skip-include")] +static APP_B: &[u8] = &[0, 1, 2, 3]; +#[cfg(not(feature = "skip-include"))] static APP_B: &[u8] = include_bytes!("../../b.bin"); #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); - let flash = Flash::unlock(p.FLASH); - let mut flash = BlockingAsync::new(flash); + let flash = Flash::new_blocking(p.FLASH); + let flash = Mutex::new(RefCell::new(flash)); let button = Input::new(p.PC13, Pull::Down); let mut button = ExtiInput::new(button, p.EXTI13); @@ -26,16 +32,19 @@ async fn main(_spawner: Spawner) { let mut led = Output::new(p.PB7, Level::Low, Speed::Low); led.set_high(); - let mut updater = FirmwareUpdater::default(); + let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash); + let mut updater = BlockingFirmwareUpdater::new(config); + let mut magic = AlignedBuffer([0; WRITE_SIZE]); + let writer = updater.prepare_update(magic.as_mut()).unwrap(); button.wait_for_rising_edge().await; let mut offset = 0; - let mut buf: [u8; 256 * 1024] = [0; 256 * 1024]; - for chunk in APP_B.chunks(256 * 1024) { - buf[..chunk.len()].copy_from_slice(chunk); - updater.write_firmware(offset, &buf, &mut flash, 2048).await.unwrap(); - offset += chunk.len(); + let mut buf = AlignedBuffer([0; 4096]); + for chunk in APP_B.chunks(4096) { + buf.as_mut()[..chunk.len()].copy_from_slice(chunk); + writer.write(offset, buf.as_ref()).unwrap(); + offset += chunk.len() as u32; } - updater.update(&mut flash).await.unwrap(); + updater.mark_updated(magic.as_mut()).unwrap(); led.set_low(); cortex_m::peripheral::SCB::sys_reset(); } diff --git a/examples/boot/application/stm32h7/.cargo/config.toml b/examples/boot/application/stm32h7/.cargo/config.toml index fefdd370e..caa0d3a93 100644 --- a/examples/boot/application/stm32h7/.cargo/config.toml +++ b/examples/boot/application/stm32h7/.cargo/config.toml @@ -1,6 +1,6 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -# replace STM32F429ZITx with your chip as listed in `probe-run --list-chips` -runner = "probe-run --chip STM32H743ZITx" +# replace STM32F429ZITx with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32H743ZITx" [build] target = "thumbv7em-none-eabihf" diff --git a/examples/boot/application/stm32h7/Cargo.toml b/examples/boot/application/stm32h7/Cargo.toml index 5669527fe..b2abdc891 100644 --- a/examples/boot/application/stm32h7/Cargo.toml +++ b/examples/boot/application/stm32h7/Cargo.toml @@ -2,21 +2,23 @@ edition = "2021" name = "embassy-boot-stm32h7-examples" version = "0.1.0" +license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.1.0", path = "../../../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.1.0", path = "../../../../embassy-executor", features = ["nightly", "integrated-timers"] } -embassy-time = { version = "0.1.0", path = "../../../../embassy-time", features = ["nightly", "tick-32768hz"] } +embassy-sync = { version = "0.2.0", path = "../../../../embassy-sync" } +embassy-executor = { version = "0.2.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers"] } +embassy-time = { version = "0.1.2", path = "../../../../embassy-time", features = ["nightly", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["unstable-traits", "nightly", "stm32h743zi", "time-driver-any", "exti"] } -embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32" } +embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = ["nightly"] } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } panic-reset = { version = "0.1.1" } embedded-hal = { version = "0.2.6" } +embedded-storage = "0.3.0" -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" [features] @@ -25,3 +27,4 @@ defmt = [ "embassy-stm32/defmt", "embassy-boot-stm32/defmt", ] +skip-include = [] diff --git a/examples/boot/application/stm32h7/flash-boot.sh b/examples/boot/application/stm32h7/flash-boot.sh index debdb17a7..4912a50b7 100755 --- a/examples/boot/application/stm32h7/flash-boot.sh +++ b/examples/boot/application/stm32h7/flash-boot.sh @@ -1,8 +1,9 @@ #!/bin/bash +probe-rs erase --chip STM32H743ZITx mv ../../bootloader/stm32/memory.x ../../bootloader/stm32/memory-old.x cp memory-bl.x ../../bootloader/stm32/memory.x -cargo flash --manifest-path ../../bootloader/stm32/Cargo.toml --release --features embassy-stm32/stm32f767zi --chip STM32F767ZITx --target thumbv7em-none-eabihf +cargo flash --manifest-path ../../bootloader/stm32/Cargo.toml --release --features embassy-stm32/stm32h743zi --chip STM32H743ZITx --target thumbv7em-none-eabihf rm ../../bootloader/stm32/memory.x mv ../../bootloader/stm32/memory-old.x ../../bootloader/stm32/memory.x diff --git a/examples/boot/application/stm32h7/src/bin/a.rs b/examples/boot/application/stm32h7/src/bin/a.rs index 0ecf60348..719176692 100644 --- a/examples/boot/application/stm32h7/src/bin/a.rs +++ b/examples/boot/application/stm32h7/src/bin/a.rs @@ -2,23 +2,29 @@ #![no_main] #![feature(type_alias_impl_trait)] +use core::cell::RefCell; + #[cfg(feature = "defmt-rtt")] use defmt_rtt::*; -use embassy_boot_stm32::FirmwareUpdater; -use embassy_embedded_hal::adapter::BlockingAsync; +use embassy_boot_stm32::{AlignedBuffer, BlockingFirmwareUpdater, FirmwareUpdaterConfig}; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; -use embassy_stm32::flash::Flash; +use embassy_stm32::flash::{Flash, WRITE_SIZE}; use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; +use embassy_sync::blocking_mutex::Mutex; +use embedded_storage::nor_flash::NorFlash; use panic_reset as _; +#[cfg(feature = "skip-include")] +static APP_B: &[u8] = &[0, 1, 2, 3]; +#[cfg(not(feature = "skip-include"))] static APP_B: &[u8] = include_bytes!("../../b.bin"); #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); - let flash = Flash::unlock(p.FLASH); - let mut flash = BlockingAsync::new(flash); + let flash = Flash::new_blocking(p.FLASH); + let flash = Mutex::new(RefCell::new(flash)); let button = Input::new(p.PC13, Pull::Down); let mut button = ExtiInput::new(button, p.EXTI13); @@ -26,16 +32,19 @@ async fn main(_spawner: Spawner) { let mut led = Output::new(p.PB14, Level::Low, Speed::Low); led.set_high(); - let mut updater = FirmwareUpdater::default(); + let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash); + let mut magic = AlignedBuffer([0; WRITE_SIZE]); + let mut updater = BlockingFirmwareUpdater::new(config); + let writer = updater.prepare_update(magic.as_mut()).unwrap(); button.wait_for_rising_edge().await; let mut offset = 0; - let mut buf: [u8; 128 * 1024] = [0; 128 * 1024]; - for chunk in APP_B.chunks(128 * 1024) { - buf[..chunk.len()].copy_from_slice(chunk); - updater.write_firmware(offset, &buf, &mut flash, 2048).await.unwrap(); - offset += chunk.len(); + let mut buf = AlignedBuffer([0; 4096]); + for chunk in APP_B.chunks(4096) { + buf.as_mut()[..chunk.len()].copy_from_slice(chunk); + writer.write(offset, buf.as_ref()).unwrap(); + offset += chunk.len() as u32; } - updater.update(&mut flash).await.unwrap(); + updater.mark_updated(magic.as_mut()).unwrap(); led.set_low(); cortex_m::peripheral::SCB::sys_reset(); } diff --git a/examples/boot/application/stm32l0/.cargo/config.toml b/examples/boot/application/stm32l0/.cargo/config.toml index 2627967ab..6099f015c 100644 --- a/examples/boot/application/stm32l0/.cargo/config.toml +++ b/examples/boot/application/stm32l0/.cargo/config.toml @@ -1,6 +1,6 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -# replace your chip as listed in `probe-run --list-chips` -runner = "probe-run --chip STM32L072CZTx" +# replace your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32L072CZTx" [build] target = "thumbv6m-none-eabi" diff --git a/examples/boot/application/stm32l0/Cargo.toml b/examples/boot/application/stm32l0/Cargo.toml index 48624d5ec..0b7e72d5e 100644 --- a/examples/boot/application/stm32l0/Cargo.toml +++ b/examples/boot/application/stm32l0/Cargo.toml @@ -2,21 +2,22 @@ edition = "2021" name = "embassy-boot-stm32l0-examples" version = "0.1.0" +license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.1.0", path = "../../../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.1.0", path = "../../../../embassy-executor", features = ["nightly", "integrated-timers"] } -embassy-time = { version = "0.1.0", path = "../../../../embassy-time", features = ["nightly", "tick-32768hz"] } +embassy-sync = { version = "0.2.0", path = "../../../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.2.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers"] } +embassy-time = { version = "0.1.2", path = "../../../../embassy-time", features = ["nightly", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["unstable-traits", "nightly", "stm32l072cz", "time-driver-any", "exti", "memory-x"] } -embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32" } +embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = ["nightly"] } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } panic-reset = { version = "0.1.1" } embedded-hal = { version = "0.2.6" } -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" [features] @@ -25,3 +26,4 @@ defmt = [ "embassy-stm32/defmt", "embassy-boot-stm32/defmt", ] +skip-include = [] diff --git a/examples/boot/application/stm32l0/src/bin/a.rs b/examples/boot/application/stm32l0/src/bin/a.rs index f4f1d7119..ce80056e6 100644 --- a/examples/boot/application/stm32l0/src/bin/a.rs +++ b/examples/boot/application/stm32l0/src/bin/a.rs @@ -4,22 +4,26 @@ #[cfg(feature = "defmt-rtt")] use defmt_rtt::*; -use embassy_boot_stm32::FirmwareUpdater; +use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater, FirmwareUpdaterConfig}; use embassy_embedded_hal::adapter::BlockingAsync; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; -use embassy_stm32::flash::Flash; +use embassy_stm32::flash::{Flash, WRITE_SIZE}; use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; +use embassy_sync::mutex::Mutex; use embassy_time::{Duration, Timer}; use panic_reset as _; +#[cfg(feature = "skip-include")] +static APP_B: &[u8] = &[0, 1, 2, 3]; +#[cfg(not(feature = "skip-include"))] static APP_B: &[u8] = include_bytes!("../../b.bin"); #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); - let flash = Flash::unlock(p.FLASH); - let mut flash = BlockingAsync::new(flash); + let flash = Flash::new_blocking(p.FLASH); + let flash = Mutex::new(BlockingAsync::new(flash)); let button = Input::new(p.PB2, Pull::Up); let mut button = ExtiInput::new(button, p.EXTI2); @@ -28,17 +32,19 @@ async fn main(_spawner: Spawner) { led.set_high(); - let mut updater = FirmwareUpdater::default(); + let config = FirmwareUpdaterConfig::from_linkerfile(&flash); + let mut updater = FirmwareUpdater::new(config); button.wait_for_falling_edge().await; let mut offset = 0; + let mut magic = AlignedBuffer([0; WRITE_SIZE]); for chunk in APP_B.chunks(128) { let mut buf: [u8; 128] = [0; 128]; buf[..chunk.len()].copy_from_slice(chunk); - updater.write_firmware(offset, &buf, &mut flash, 128).await.unwrap(); + updater.write_firmware(magic.as_mut(), offset, &buf).await.unwrap(); offset += chunk.len(); } - updater.update(&mut flash).await.unwrap(); + updater.mark_updated(magic.as_mut()).await.unwrap(); led.set_low(); Timer::after(Duration::from_secs(1)).await; cortex_m::peripheral::SCB::sys_reset(); diff --git a/examples/boot/application/stm32l1/.cargo/config.toml b/examples/boot/application/stm32l1/.cargo/config.toml index 404b6b55c..9cabd14ba 100644 --- a/examples/boot/application/stm32l1/.cargo/config.toml +++ b/examples/boot/application/stm32l1/.cargo/config.toml @@ -1,6 +1,6 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -# replace your chip as listed in `probe-run --list-chips` -runner = "probe-run --chip STM32L151CBxxA" +# replace your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32L151CBxxA" [build] target = "thumbv7m-none-eabi" diff --git a/examples/boot/application/stm32l1/Cargo.toml b/examples/boot/application/stm32l1/Cargo.toml index 00b638ca5..5f3f365c1 100644 --- a/examples/boot/application/stm32l1/Cargo.toml +++ b/examples/boot/application/stm32l1/Cargo.toml @@ -2,21 +2,22 @@ edition = "2021" name = "embassy-boot-stm32l1-examples" version = "0.1.0" +license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.1.0", path = "../../../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.1.0", path = "../../../../embassy-executor", features = ["nightly", "integrated-timers"] } -embassy-time = { version = "0.1.0", path = "../../../../embassy-time", features = ["nightly", "tick-32768hz"] } +embassy-sync = { version = "0.2.0", path = "../../../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.2.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers"] } +embassy-time = { version = "0.1.2", path = "../../../../embassy-time", features = ["nightly", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["unstable-traits", "nightly", "stm32l151cb-a", "time-driver-any", "exti"] } -embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32" } +embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = ["nightly"] } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } panic-reset = { version = "0.1.1" } embedded-hal = { version = "0.2.6" } -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" [features] @@ -25,3 +26,4 @@ defmt = [ "embassy-stm32/defmt", "embassy-boot-stm32/defmt", ] +skip-include = [] diff --git a/examples/boot/application/stm32l1/src/bin/a.rs b/examples/boot/application/stm32l1/src/bin/a.rs index f4f1d7119..1e9bf3cb9 100644 --- a/examples/boot/application/stm32l1/src/bin/a.rs +++ b/examples/boot/application/stm32l1/src/bin/a.rs @@ -4,22 +4,26 @@ #[cfg(feature = "defmt-rtt")] use defmt_rtt::*; -use embassy_boot_stm32::FirmwareUpdater; +use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater, FirmwareUpdaterConfig}; use embassy_embedded_hal::adapter::BlockingAsync; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; -use embassy_stm32::flash::Flash; +use embassy_stm32::flash::{Flash, WRITE_SIZE}; use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; +use embassy_sync::mutex::Mutex; use embassy_time::{Duration, Timer}; use panic_reset as _; +#[cfg(feature = "skip-include")] +static APP_B: &[u8] = &[0, 1, 2, 3]; +#[cfg(not(feature = "skip-include"))] static APP_B: &[u8] = include_bytes!("../../b.bin"); #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); - let flash = Flash::unlock(p.FLASH); - let mut flash = BlockingAsync::new(flash); + let flash = Flash::new_blocking(p.FLASH); + let flash = Mutex::new(BlockingAsync::new(flash)); let button = Input::new(p.PB2, Pull::Up); let mut button = ExtiInput::new(button, p.EXTI2); @@ -28,17 +32,19 @@ async fn main(_spawner: Spawner) { led.set_high(); - let mut updater = FirmwareUpdater::default(); + let config = FirmwareUpdaterConfig::from_linkerfile(&flash); + let mut updater = FirmwareUpdater::new(config); button.wait_for_falling_edge().await; + let mut magic = AlignedBuffer([0; WRITE_SIZE]); let mut offset = 0; for chunk in APP_B.chunks(128) { let mut buf: [u8; 128] = [0; 128]; buf[..chunk.len()].copy_from_slice(chunk); - updater.write_firmware(offset, &buf, &mut flash, 128).await.unwrap(); + updater.write_firmware(magic.as_mut(), offset, &buf).await.unwrap(); offset += chunk.len(); } - updater.update(&mut flash).await.unwrap(); + updater.mark_updated(magic.as_mut()).await.unwrap(); led.set_low(); Timer::after(Duration::from_secs(1)).await; cortex_m::peripheral::SCB::sys_reset(); diff --git a/examples/boot/application/stm32l4/.cargo/config.toml b/examples/boot/application/stm32l4/.cargo/config.toml index 43520e323..c803215f6 100644 --- a/examples/boot/application/stm32l4/.cargo/config.toml +++ b/examples/boot/application/stm32l4/.cargo/config.toml @@ -1,6 +1,6 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -# replace your chip as listed in `probe-run --list-chips` -runner = "probe-run --chip STM32L475VG" +# replace your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32L475VG" [build] target = "thumbv7em-none-eabihf" diff --git a/examples/boot/application/stm32l4/Cargo.toml b/examples/boot/application/stm32l4/Cargo.toml index 51ba730d5..44eb5aba8 100644 --- a/examples/boot/application/stm32l4/Cargo.toml +++ b/examples/boot/application/stm32l4/Cargo.toml @@ -2,21 +2,22 @@ edition = "2021" name = "embassy-boot-stm32l4-examples" version = "0.1.0" +license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.1.0", path = "../../../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.1.0", path = "../../../../embassy-executor", features = ["nightly", "integrated-timers"] } -embassy-time = { version = "0.1.0", path = "../../../../embassy-time", features = ["nightly", "tick-32768hz"] } +embassy-sync = { version = "0.2.0", path = "../../../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.2.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers"] } +embassy-time = { version = "0.1.2", path = "../../../../embassy-time", features = ["nightly", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["unstable-traits", "nightly", "stm32l475vg", "time-driver-any", "exti"] } -embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32" } +embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = ["nightly"] } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } panic-reset = { version = "0.1.1" } embedded-hal = { version = "0.2.6" } -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" [features] @@ -25,3 +26,4 @@ defmt = [ "embassy-stm32/defmt", "embassy-boot-stm32/defmt", ] +skip-include = [] diff --git a/examples/boot/application/stm32l4/src/bin/a.rs b/examples/boot/application/stm32l4/src/bin/a.rs index 178b2e04a..a514ab5be 100644 --- a/examples/boot/application/stm32l4/src/bin/a.rs +++ b/examples/boot/application/stm32l4/src/bin/a.rs @@ -4,21 +4,25 @@ #[cfg(feature = "defmt-rtt")] use defmt_rtt::*; -use embassy_boot_stm32::FirmwareUpdater; +use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater, FirmwareUpdaterConfig}; use embassy_embedded_hal::adapter::BlockingAsync; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; -use embassy_stm32::flash::Flash; +use embassy_stm32::flash::{Flash, WRITE_SIZE}; use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; +use embassy_sync::mutex::Mutex; use panic_reset as _; +#[cfg(feature = "skip-include")] +static APP_B: &[u8] = &[0, 1, 2, 3]; +#[cfg(not(feature = "skip-include"))] static APP_B: &[u8] = include_bytes!("../../b.bin"); #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); - let flash = Flash::unlock(p.FLASH); - let mut flash = BlockingAsync::new(flash); + let flash = Flash::new_blocking(p.FLASH); + let flash = Mutex::new(BlockingAsync::new(flash)); let button = Input::new(p.PC13, Pull::Up); let mut button = ExtiInput::new(button, p.EXTI13); @@ -26,16 +30,18 @@ async fn main(_spawner: Spawner) { let mut led = Output::new(p.PB14, Level::Low, Speed::Low); led.set_high(); - let mut updater = FirmwareUpdater::default(); + let config = FirmwareUpdaterConfig::from_linkerfile(&flash); + let mut updater = FirmwareUpdater::new(config); button.wait_for_falling_edge().await; + let mut magic = AlignedBuffer([0; WRITE_SIZE]); let mut offset = 0; for chunk in APP_B.chunks(2048) { let mut buf: [u8; 2048] = [0; 2048]; buf[..chunk.len()].copy_from_slice(chunk); - updater.write_firmware(offset, &buf, &mut flash, 2048).await.unwrap(); + updater.write_firmware(magic.as_mut(), offset, &buf).await.unwrap(); offset += chunk.len(); } - updater.update(&mut flash).await.unwrap(); + updater.mark_updated(magic.as_mut()).await.unwrap(); led.set_low(); cortex_m::peripheral::SCB::sys_reset(); } diff --git a/examples/boot/application/stm32wl/.cargo/config.toml b/examples/boot/application/stm32wl/.cargo/config.toml index e395d75b4..4f8094ff2 100644 --- a/examples/boot/application/stm32wl/.cargo/config.toml +++ b/examples/boot/application/stm32wl/.cargo/config.toml @@ -1,6 +1,6 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -# replace your chip as listed in `probe-run --list-chips` -runner = "probe-run --chip STM32WLE5JCIx" +# replace your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32WLE5JCIx" [build] target = "thumbv7em-none-eabihf" diff --git a/examples/boot/application/stm32wl/Cargo.toml b/examples/boot/application/stm32wl/Cargo.toml index 182acf694..fdad55060 100644 --- a/examples/boot/application/stm32wl/Cargo.toml +++ b/examples/boot/application/stm32wl/Cargo.toml @@ -2,21 +2,22 @@ edition = "2021" name = "embassy-boot-stm32wl-examples" version = "0.1.0" +license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.1.0", path = "../../../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.1.0", path = "../../../../embassy-executor", features = ["nightly", "integrated-timers"] } -embassy-time = { version = "0.1.0", path = "../../../../embassy-time", features = ["nightly", "tick-32768hz"] } +embassy-sync = { version = "0.2.0", path = "../../../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.2.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers"] } +embassy-time = { version = "0.1.2", path = "../../../../embassy-time", features = ["nightly", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["unstable-traits", "nightly", "stm32wl55jc-cm4", "time-driver-any", "exti"] } -embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32" } +embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = ["nightly"] } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } panic-reset = { version = "0.1.1" } embedded-hal = { version = "0.2.6" } -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" [features] @@ -25,3 +26,4 @@ defmt = [ "embassy-stm32/defmt", "embassy-boot-stm32/defmt", ] +skip-include = [] diff --git a/examples/boot/application/stm32wl/src/bin/a.rs b/examples/boot/application/stm32wl/src/bin/a.rs index c71a42654..52a197a5c 100644 --- a/examples/boot/application/stm32wl/src/bin/a.rs +++ b/examples/boot/application/stm32wl/src/bin/a.rs @@ -4,21 +4,25 @@ #[cfg(feature = "defmt-rtt")] use defmt_rtt::*; -use embassy_boot_stm32::FirmwareUpdater; +use embassy_boot_stm32::{AlignedBuffer, FirmwareUpdater, FirmwareUpdaterConfig}; use embassy_embedded_hal::adapter::BlockingAsync; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; -use embassy_stm32::flash::Flash; +use embassy_stm32::flash::{Flash, WRITE_SIZE}; use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; +use embassy_sync::mutex::Mutex; use panic_reset as _; +#[cfg(feature = "skip-include")] +static APP_B: &[u8] = &[0, 1, 2, 3]; +#[cfg(not(feature = "skip-include"))] static APP_B: &[u8] = include_bytes!("../../b.bin"); #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); - let flash = Flash::unlock(p.FLASH); - let mut flash = BlockingAsync::new(flash); + let flash = Flash::new_blocking(p.FLASH); + let flash = Mutex::new(BlockingAsync::new(flash)); let button = Input::new(p.PA0, Pull::Up); let mut button = ExtiInput::new(button, p.EXTI0); @@ -26,18 +30,20 @@ async fn main(_spawner: Spawner) { let mut led = Output::new(p.PB9, Level::Low, Speed::Low); led.set_high(); - let mut updater = FirmwareUpdater::default(); + let config = FirmwareUpdaterConfig::from_linkerfile(&flash); + let mut updater = FirmwareUpdater::new(config); button.wait_for_falling_edge().await; //defmt::info!("Starting update"); + let mut magic = AlignedBuffer([0; WRITE_SIZE]); let mut offset = 0; for chunk in APP_B.chunks(2048) { let mut buf: [u8; 2048] = [0; 2048]; buf[..chunk.len()].copy_from_slice(chunk); // defmt::info!("Writing chunk at 0x{:x}", offset); - updater.write_firmware(offset, &buf, &mut flash, 2048).await.unwrap(); + updater.write_firmware(magic.as_mut(), offset, &buf).await.unwrap(); offset += chunk.len(); } - updater.update(&mut flash).await.unwrap(); + updater.mark_updated(magic.as_mut()).await.unwrap(); //defmt::info!("Marked as updated"); led.set_low(); cortex_m::peripheral::SCB::sys_reset(); diff --git a/examples/boot/bootloader/nrf/.cargo/config.toml b/examples/boot/bootloader/nrf/.cargo/config.toml index 1060800a3..c292846aa 100644 --- a/examples/boot/bootloader/nrf/.cargo/config.toml +++ b/examples/boot/bootloader/nrf/.cargo/config.toml @@ -4,7 +4,7 @@ build-std-features = ["panic_immediate_abort"] [target.'cfg(all(target_arch = "arm", target_os = "none"))'] #runner = "./fruitrunner" -runner = "probe-run --chip nrf52840_xxAA" +runner = "probe-rs run --chip nrf52840_xxAA" rustflags = [ # Code-size optimizations. diff --git a/examples/boot/bootloader/nrf/Cargo.toml b/examples/boot/bootloader/nrf/Cargo.toml index aa2a13ecb..40656f359 100644 --- a/examples/boot/bootloader/nrf/Cargo.toml +++ b/examples/boot/bootloader/nrf/Cargo.toml @@ -3,14 +3,16 @@ edition = "2021" name = "nrf-bootloader-example" version = "0.1.0" description = "Bootloader for nRF chips" +license = "MIT OR Apache-2.0" [dependencies] defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } -embassy-nrf = { path = "../../../../embassy-nrf", default-features = false, features = ["nightly"] } -embassy-boot-nrf = { path = "../../../../embassy-boot/nrf", default-features = false } -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +embassy-nrf = { path = "../../../../embassy-nrf", features = ["nightly"] } +embassy-boot-nrf = { path = "../../../../embassy-boot/nrf" } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +embassy-sync = { path = "../../../../embassy-sync" } cortex-m-rt = { version = "0.7" } cfg-if = "1.0.0" diff --git a/examples/boot/bootloader/nrf/memory-bm.x b/examples/boot/bootloader/nrf/memory-bm.x index 8a32b905f..257d65644 100644 --- a/examples/boot/bootloader/nrf/memory-bm.x +++ b/examples/boot/bootloader/nrf/memory-bm.x @@ -5,7 +5,7 @@ MEMORY BOOTLOADER_STATE : ORIGIN = 0x00006000, LENGTH = 4K ACTIVE : ORIGIN = 0x00007000, LENGTH = 64K DFU : ORIGIN = 0x00017000, LENGTH = 68K - RAM (rwx) : ORIGIN = 0x20000008, LENGTH = 32K + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32K } __bootloader_state_start = ORIGIN(BOOTLOADER_STATE); diff --git a/examples/boot/bootloader/nrf/memory.x b/examples/boot/bootloader/nrf/memory.x index 8a32b905f..257d65644 100644 --- a/examples/boot/bootloader/nrf/memory.x +++ b/examples/boot/bootloader/nrf/memory.x @@ -5,7 +5,7 @@ MEMORY BOOTLOADER_STATE : ORIGIN = 0x00006000, LENGTH = 4K ACTIVE : ORIGIN = 0x00007000, LENGTH = 64K DFU : ORIGIN = 0x00017000, LENGTH = 68K - RAM (rwx) : ORIGIN = 0x20000008, LENGTH = 32K + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32K } __bootloader_state_start = ORIGIN(BOOTLOADER_STATE); diff --git a/examples/boot/bootloader/nrf/src/main.rs b/examples/boot/bootloader/nrf/src/main.rs index bc7e0755f..72c95c02a 100644 --- a/examples/boot/bootloader/nrf/src/main.rs +++ b/examples/boot/bootloader/nrf/src/main.rs @@ -1,11 +1,15 @@ #![no_std] #![no_main] +use core::cell::RefCell; + use cortex_m_rt::{entry, exception}; #[cfg(feature = "defmt")] use defmt_rtt as _; use embassy_boot_nrf::*; use embassy_nrf::nvmc::Nvmc; +use embassy_nrf::wdt; +use embassy_sync::blocking_mutex::Mutex; #[entry] fn main() -> ! { @@ -19,13 +23,21 @@ fn main() -> ! { } */ - let mut bl = BootLoader::default(); - let start = bl.prepare(&mut SingleFlashProvider::new(&mut WatchdogFlash::start( - Nvmc::new(p.NVMC), - p.WDT, - 5, - ))); - unsafe { bl.load(start) } + let mut wdt_config = wdt::Config::default(); + wdt_config.timeout_ticks = 32768 * 5; // timeout seconds + wdt_config.run_during_sleep = true; + wdt_config.run_during_debug_halt = false; + + let flash = WatchdogFlash::start(Nvmc::new(p.NVMC), p.WDT, wdt_config); + let flash = Mutex::new(RefCell::new(flash)); + + let config = BootLoaderConfig::from_linkerfile_blocking(&flash); + let active_offset = config.active.offset(); + let mut bl: BootLoader<_, _, _> = BootLoader::new(config); + + bl.prepare(); + + unsafe { bl.load(active_offset) } } #[no_mangle] diff --git a/examples/boot/bootloader/rp/.cargo/config.toml b/examples/boot/bootloader/rp/.cargo/config.toml new file mode 100644 index 000000000..9d48ecdc9 --- /dev/null +++ b/examples/boot/bootloader/rp/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +runner = "probe-rs run --chip RP2040" + +[build] +target = "thumbv6m-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/examples/boot/bootloader/rp/Cargo.toml b/examples/boot/bootloader/rp/Cargo.toml new file mode 100644 index 000000000..c1dc99eec --- /dev/null +++ b/examples/boot/bootloader/rp/Cargo.toml @@ -0,0 +1,33 @@ +[package] +edition = "2021" +name = "rp-bootloader-example" +version = "0.1.0" +description = "Example bootloader for RP2040 chips" +license = "MIT OR Apache-2.0" + +[dependencies] +defmt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } + +embassy-rp = { path = "../../../../embassy-rp", features = ["nightly"] } +embassy-boot-rp = { path = "../../../../embassy-boot/rp" } +embassy-sync = { path = "../../../../embassy-sync" } +embassy-time = { path = "../../../../embassy-time", features = ["nightly"] } + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = { version = "0.7" } +embedded-storage = "0.3.0" +embedded-storage-async = "0.4.0" +cfg-if = "1.0.0" + +[features] +defmt = [ + "dep:defmt", + "embassy-boot-rp/defmt", + "embassy-rp/defmt", +] +debug = ["defmt-rtt", "defmt"] + +[profile.release] +debug = true +opt-level = 's' diff --git a/examples/boot/bootloader/rp/README.md b/examples/boot/bootloader/rp/README.md new file mode 100644 index 000000000..064e87273 --- /dev/null +++ b/examples/boot/bootloader/rp/README.md @@ -0,0 +1,17 @@ +# Bootloader for RP2040 + +The bootloader uses `embassy-boot` to interact with the flash. + +# Usage + +Flashing the bootloader + +``` +cargo flash --release --chip RP2040 +``` + +To debug, use `cargo run` and enable the debug feature flag + +``` rust +cargo run --release --features debug +``` diff --git a/examples/boot/bootloader/rp/build.rs b/examples/boot/bootloader/rp/build.rs new file mode 100644 index 000000000..c201704ad --- /dev/null +++ b/examples/boot/bootloader/rp/build.rs @@ -0,0 +1,28 @@ +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tlink-rp.x"); + if env::var("CARGO_FEATURE_DEFMT").is_ok() { + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + } +} diff --git a/examples/boot/bootloader/rp/memory.x b/examples/boot/bootloader/rp/memory.x new file mode 100644 index 000000000..d6ef38469 --- /dev/null +++ b/examples/boot/bootloader/rp/memory.x @@ -0,0 +1,19 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + BOOT2 : ORIGIN = 0x10000000, LENGTH = 0x100 + FLASH : ORIGIN = 0x10000100, LENGTH = 24K + BOOTLOADER_STATE : ORIGIN = 0x10006000, LENGTH = 4K + ACTIVE : ORIGIN = 0x10007000, LENGTH = 512K + DFU : ORIGIN = 0x10087000, LENGTH = 516K + RAM : ORIGIN = 0x20000000, LENGTH = 256K +} + +__bootloader_state_start = ORIGIN(BOOTLOADER_STATE) - ORIGIN(BOOT2); +__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE) - ORIGIN(BOOT2); + +__bootloader_active_start = ORIGIN(ACTIVE) - ORIGIN(BOOT2); +__bootloader_active_end = ORIGIN(ACTIVE) + LENGTH(ACTIVE) - ORIGIN(BOOT2); + +__bootloader_dfu_start = ORIGIN(DFU) - ORIGIN(BOOT2); +__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU) - ORIGIN(BOOT2); diff --git a/examples/boot/bootloader/rp/src/main.rs b/examples/boot/bootloader/rp/src/main.rs new file mode 100644 index 000000000..6a81db804 --- /dev/null +++ b/examples/boot/bootloader/rp/src/main.rs @@ -0,0 +1,56 @@ +#![no_std] +#![no_main] + +use core::cell::RefCell; + +use cortex_m_rt::{entry, exception}; +#[cfg(feature = "defmt")] +use defmt_rtt as _; +use embassy_boot_rp::*; +use embassy_sync::blocking_mutex::Mutex; +use embassy_time::Duration; + +const FLASH_SIZE: usize = 2 * 1024 * 1024; + +#[entry] +fn main() -> ! { + let p = embassy_rp::init(Default::default()); + + // Uncomment this if you are debugging the bootloader with debugger/RTT attached, + // as it prevents a hard fault when accessing flash 'too early' after boot. + /* + for i in 0..10000000 { + cortex_m::asm::nop(); + } + */ + + let flash = WatchdogFlash::::start(p.FLASH, p.WATCHDOG, Duration::from_secs(8)); + let flash = Mutex::new(RefCell::new(flash)); + + let config = BootLoaderConfig::from_linkerfile_blocking(&flash); + let active_offset = config.active.offset(); + let mut bl: BootLoader<_, _, _> = BootLoader::new(config); + + bl.prepare(); + + unsafe { bl.load(embassy_rp::flash::FLASH_BASE as u32 + active_offset) } +} + +#[no_mangle] +#[cfg_attr(target_os = "none", link_section = ".HardFault.user")] +unsafe extern "C" fn HardFault() { + cortex_m::peripheral::SCB::sys_reset(); +} + +#[exception] +unsafe fn DefaultHandler(_: i16) -> ! { + const SCB_ICSR: *const u32 = 0xE000_ED04 as *const u32; + let irqn = core::ptr::read_volatile(SCB_ICSR) as u8 as i16 - 16; + + panic!("DefaultHandler #{:?}", irqn); +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + cortex_m::asm::udf(); +} diff --git a/examples/boot/bootloader/stm32/Cargo.toml b/examples/boot/bootloader/stm32/Cargo.toml index 491777103..6436f2fee 100644 --- a/examples/boot/bootloader/stm32/Cargo.toml +++ b/examples/boot/bootloader/stm32/Cargo.toml @@ -3,17 +3,19 @@ edition = "2021" name = "stm32-bootloader-example" version = "0.1.0" description = "Example bootloader for STM32 chips" +license = "MIT OR Apache-2.0" [dependencies] defmt = { version = "0.3", optional = true } -defmt-rtt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } -embassy-stm32 = { path = "../../../../embassy-stm32", default-features = false, features = ["nightly"] } -embassy-boot-stm32 = { path = "../../../../embassy-boot/stm32", default-features = false } -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +embassy-stm32 = { path = "../../../../embassy-stm32", features = ["nightly"] } +embassy-boot-stm32 = { path = "../../../../embassy-boot/stm32" } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +embassy-sync = { path = "../../../../embassy-sync" } cortex-m-rt = { version = "0.7" } embedded-storage = "0.3.0" -embedded-storage-async = "0.3.0" +embedded-storage-async = "0.4.0" cfg-if = "1.0.0" [features] diff --git a/examples/boot/bootloader/stm32/src/main.rs b/examples/boot/bootloader/stm32/src/main.rs index 45c511ced..262eed200 100644 --- a/examples/boot/bootloader/stm32/src/main.rs +++ b/examples/boot/bootloader/stm32/src/main.rs @@ -1,11 +1,14 @@ #![no_std] #![no_main] +use core::cell::RefCell; + use cortex_m_rt::{entry, exception}; #[cfg(feature = "defmt")] use defmt_rtt as _; use embassy_boot_stm32::*; -use embassy_stm32::flash::{Flash, ERASE_SIZE}; +use embassy_stm32::flash::{Flash, BANK1_REGION}; +use embassy_sync::blocking_mutex::Mutex; #[entry] fn main() -> ! { @@ -19,11 +22,16 @@ fn main() -> ! { } */ - let mut bl: BootLoader = BootLoader::default(); - let mut flash = Flash::unlock(p.FLASH); - let start = bl.prepare(&mut SingleFlashProvider::new(&mut flash)); - core::mem::drop(flash); - unsafe { bl.load(start) } + let layout = Flash::new_blocking(p.FLASH).into_blocking_regions(); + let flash = Mutex::new(RefCell::new(layout.bank1_region)); + + let config = BootLoaderConfig::from_linkerfile_blocking(&flash); + let active_offset = config.active.offset(); + let mut bl: BootLoader<_, _, _, 2048> = BootLoader::new(config); + + bl.prepare(); + + unsafe { bl.load(BANK1_REGION.base + active_offset) } } #[no_mangle] diff --git a/examples/nrf-rtos-trace/.cargo/config.toml b/examples/nrf-rtos-trace/.cargo/config.toml index 8ca28df39..17616a054 100644 --- a/examples/nrf-rtos-trace/.cargo/config.toml +++ b/examples/nrf-rtos-trace/.cargo/config.toml @@ -1,6 +1,6 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -# replace nRF82840_xxAA with your chip as listed in `probe-run --list-chips` -runner = "probe-run --chip nRF52840_xxAA" +# replace nRF82840_xxAA with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip nRF52840_xxAA" [build] target = "thumbv7em-none-eabi" diff --git a/examples/nrf-rtos-trace/Cargo.toml b/examples/nrf-rtos-trace/Cargo.toml index 87c9f33f5..30b67b7b2 100644 --- a/examples/nrf-rtos-trace/Cargo.toml +++ b/examples/nrf-rtos-trace/Cargo.toml @@ -2,6 +2,7 @@ edition = "2021" name = "embassy-nrf-rtos-trace-examples" version = "0.1.0" +license = "MIT OR Apache-2.0" [features] default = ["log", "nightly"] @@ -15,12 +16,12 @@ log = [ ] [dependencies] -embassy-sync = { version = "0.1.0", path = "../../embassy-sync" } -embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features=["rtos-trace", "rtos-trace-interrupt", "integrated-timers"] } -embassy-time = { version = "0.1.0", path = "../../embassy-time" } +embassy-sync = { version = "0.2.0", path = "../../embassy-sync" } +embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "rtos-trace", "rtos-trace-interrupt", "integrated-timers"] } +embassy-time = { version = "0.1.2", path = "../../embassy-time" } embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] } -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" panic-probe = { version = "0.3" } futures = { version = "0.3.17", default-features = false, features = ["async-await"] } diff --git a/examples/nrf-rtos-trace/src/bin/rtos_trace.rs b/examples/nrf-rtos-trace/src/bin/rtos_trace.rs index 7d1ad87c8..cf8b2f808 100644 --- a/examples/nrf-rtos-trace/src/bin/rtos_trace.rs +++ b/examples/nrf-rtos-trace/src/bin/rtos_trace.rs @@ -2,6 +2,7 @@ #![no_main] #![feature(type_alias_impl_trait)] +use core::future::poll_fn; use core::task::Poll; use embassy_executor::Spawner; @@ -46,7 +47,7 @@ async fn run2() { #[embassy_executor::task] async fn run3() { - futures::future::poll_fn(|cx| { + poll_fn(|cx| { cx.waker().wake_by_ref(); Poll::<()>::Pending }) diff --git a/examples/nrf/src/bin/awaitable_timer.rs b/examples/nrf/src/bin/awaitable_timer.rs deleted file mode 100644 index b32af236c..000000000 --- a/examples/nrf/src/bin/awaitable_timer.rs +++ /dev/null @@ -1,26 +0,0 @@ -#![no_std] -#![no_main] -#![feature(type_alias_impl_trait)] - -use defmt::info; -use embassy_executor::Spawner; -use embassy_nrf::interrupt; -use embassy_nrf::timer::Timer; -use {defmt_rtt as _, panic_probe as _}; - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - let p = embassy_nrf::init(Default::default()); - let mut t = Timer::new_awaitable(p.TIMER0, interrupt::take!(TIMER0)); - // default frequency is 1MHz, so this triggers every second - t.cc(0).write(1_000_000); - // clear the timer value on cc[0] compare match - t.cc(0).short_compare_clear(); - t.start(); - - loop { - // wait for compare match - t.cc(0).wait().await; - info!("hardware timer tick"); - } -} diff --git a/examples/nrf/src/bin/pdm.rs b/examples/nrf/src/bin/pdm.rs deleted file mode 100644 index 605eca59e..000000000 --- a/examples/nrf/src/bin/pdm.rs +++ /dev/null @@ -1,46 +0,0 @@ -#![no_std] -#![no_main] -#![feature(type_alias_impl_trait)] - -use defmt::info; -use embassy_executor::Spawner; -use embassy_nrf::interrupt; -use embassy_nrf::pdm::{Config, Channels, Pdm}; -use embassy_time::{Duration, Timer}; -use fixed::types::I7F1; -use num_integer::Roots; -use {defmt_rtt as _, panic_probe as _}; - -#[embassy_executor::main] -async fn main(_p: Spawner) { - let mut p = embassy_nrf::init(Default::default()); - let mut config = Config::default(); - // Pins are correct for the onboard microphone on the Feather nRF52840 Sense. - config.channels = Channels::Mono; - config.gain_left = I7F1::from_bits(5); // 2.5 dB - let mut pdm = Pdm::new(p.PDM, interrupt::take!(PDM), &mut p.P0_00, &mut p.P0_01, config); - - loop { - for gain in [I7F1::from_num(-20), I7F1::from_num(0), I7F1::from_num(20)] { - pdm.set_gain(gain, gain); - info!("Gain = {} dB", defmt::Debug2Format(&gain)); - for _ in 0..10 { - let mut buf = [0; 1500]; - pdm.sample(5, &mut buf).await; - let mean = (buf.iter().map(|v| i32::from(*v)).sum::() / buf.len() as i32) as i16; - info!( - "{} samples, min {=i16}, max {=i16}, mean {=i16}, AC RMS {=i16}", - buf.len(), - buf.iter().min().unwrap(), - buf.iter().max().unwrap(), - mean, - ( - buf.iter().map(|v| i32::from(*v - mean).pow(2)).fold(0i32, |a,b| a.saturating_add(b)) - / buf.len() as i32).sqrt() as i16, - ); - info!("samples = {}", &buf); - Timer::after(Duration::from_millis(100)).await; - } - } - } -} diff --git a/examples/nrf/src/bin/usb_ethernet.rs b/examples/nrf/src/bin/usb_ethernet.rs deleted file mode 100644 index ca6c7e0d1..000000000 --- a/examples/nrf/src/bin/usb_ethernet.rs +++ /dev/null @@ -1,275 +0,0 @@ -#![no_std] -#![no_main] -#![feature(generic_associated_types)] -#![feature(type_alias_impl_trait)] - -use core::mem; -use core::sync::atomic::{AtomicBool, Ordering}; -use core::task::Waker; - -use defmt::*; -use embassy_executor::Spawner; -use embassy_net::tcp::TcpSocket; -use embassy_net::{PacketBox, PacketBoxExt, PacketBuf, Stack, StackResources}; -use embassy_nrf::rng::Rng; -use embassy_nrf::usb::{Driver, PowerUsb}; -use embassy_nrf::{interrupt, pac, peripherals}; -use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; -use embassy_sync::channel::Channel; -use embassy_usb::{Builder, Config, UsbDevice}; -use embassy_usb_ncm::{CdcNcmClass, Receiver, Sender, State}; -use embedded_io::asynch::{Read, Write}; -use static_cell::StaticCell; -use {defmt_rtt as _, panic_probe as _}; - -type MyDriver = Driver<'static, peripherals::USBD, PowerUsb>; - -macro_rules! singleton { - ($val:expr) => {{ - type T = impl Sized; - static STATIC_CELL: StaticCell = StaticCell::new(); - STATIC_CELL.init_with(move || $val) - }}; -} - -#[embassy_executor::task] -async fn usb_task(mut device: UsbDevice<'static, MyDriver>) -> ! { - device.run().await -} - -#[embassy_executor::task] -async fn usb_ncm_rx_task(mut class: Receiver<'static, MyDriver>) { - loop { - warn!("WAITING for connection"); - LINK_UP.store(false, Ordering::Relaxed); - - class.wait_connection().await.unwrap(); - - warn!("Connected"); - LINK_UP.store(true, Ordering::Relaxed); - - loop { - let mut p = unwrap!(PacketBox::new(embassy_net::Packet::new())); - let n = match class.read_packet(&mut p[..]).await { - Ok(n) => n, - Err(e) => { - warn!("error reading packet: {:?}", e); - break; - } - }; - - let buf = p.slice(0..n); - if RX_CHANNEL.try_send(buf).is_err() { - warn!("Failed pushing rx'd packet to channel."); - } - } - } -} - -#[embassy_executor::task] -async fn usb_ncm_tx_task(mut class: Sender<'static, MyDriver>) { - loop { - let pkt = TX_CHANNEL.recv().await; - if let Err(e) = class.write_packet(&pkt[..]).await { - warn!("Failed to TX packet: {:?}", e); - } - } -} - -#[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { - stack.run().await -} - -#[embassy_executor::main] -async fn main(spawner: Spawner) { - let p = embassy_nrf::init(Default::default()); - let clock: pac::CLOCK = unsafe { mem::transmute(()) }; - - info!("Enabling ext hfosc..."); - clock.tasks_hfclkstart.write(|w| unsafe { w.bits(1) }); - while clock.events_hfclkstarted.read().bits() != 1 {} - - // Create the driver, from the HAL. - let irq = interrupt::take!(USBD); - let power_irq = interrupt::take!(POWER_CLOCK); - let driver = Driver::new(p.USBD, irq, PowerUsb::new(power_irq)); - - // Create embassy-usb Config - let mut config = Config::new(0xc0de, 0xcafe); - config.manufacturer = Some("Embassy"); - config.product = Some("USB-Ethernet example"); - config.serial_number = Some("12345678"); - config.max_power = 100; - config.max_packet_size_0 = 64; - - // Required for Windows support. - config.composite_with_iads = true; - config.device_class = 0xEF; - config.device_sub_class = 0x02; - config.device_protocol = 0x01; - - struct Resources { - device_descriptor: [u8; 256], - config_descriptor: [u8; 256], - bos_descriptor: [u8; 256], - control_buf: [u8; 128], - serial_state: State<'static>, - } - let res: &mut Resources = singleton!(Resources { - device_descriptor: [0; 256], - config_descriptor: [0; 256], - bos_descriptor: [0; 256], - control_buf: [0; 128], - serial_state: State::new(), - }); - - // Create embassy-usb DeviceBuilder using the driver and config. - let mut builder = Builder::new( - driver, - config, - &mut res.device_descriptor, - &mut res.config_descriptor, - &mut res.bos_descriptor, - &mut res.control_buf, - None, - ); - - // WARNINGS for Android ethernet tethering: - // - On Pixel 4a, it refused to work on Android 11, worked on Android 12. - // - if the host's MAC address has the "locally-administered" bit set (bit 1 of first byte), - // it doesn't work! The "Ethernet tethering" option in settings doesn't get enabled. - // This is due to regex spaghetti: https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-mainline-12.0.0_r84/core/res/res/values/config.xml#417 - // and this nonsense in the linux kernel: https://github.com/torvalds/linux/blob/c00c5e1d157bec0ef0b0b59aa5482eb8dc7e8e49/drivers/net/usb/usbnet.c#L1751-L1757 - - // Our MAC addr. - let our_mac_addr = [0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC]; - // Host's MAC addr. This is the MAC the host "thinks" its USB-to-ethernet adapter has. - let host_mac_addr = [0x88, 0x88, 0x88, 0x88, 0x88, 0x88]; - - // Create classes on the builder. - let class = CdcNcmClass::new(&mut builder, &mut res.serial_state, host_mac_addr, 64); - - // Build the builder. - let usb = builder.build(); - - unwrap!(spawner.spawn(usb_task(usb))); - - let (tx, rx) = class.split(); - unwrap!(spawner.spawn(usb_ncm_rx_task(rx))); - unwrap!(spawner.spawn(usb_ncm_tx_task(tx))); - - let config = embassy_net::ConfigStrategy::Dhcp; - //let config = embassy_net::ConfigStrategy::Static(embassy_net::Config { - // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), - // dns_servers: Vec::new(), - // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), - //}); - - // Generate random seed - let mut rng = Rng::new(p.RNG, interrupt::take!(RNG)); - let mut seed = [0; 8]; - rng.blocking_fill_bytes(&mut seed); - let seed = u64::from_le_bytes(seed); - - // Init network stack - let device = Device { mac_addr: our_mac_addr }; - let stack = &*singleton!(Stack::new( - device, - config, - singleton!(StackResources::<1, 2, 8>::new()), - seed - )); - - unwrap!(spawner.spawn(net_task(stack))); - - // And now we can use it! - - let mut rx_buffer = [0; 4096]; - let mut tx_buffer = [0; 4096]; - let mut buf = [0; 4096]; - - loop { - let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); - socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10))); - - info!("Listening on TCP:1234..."); - if let Err(e) = socket.accept(1234).await { - warn!("accept error: {:?}", e); - continue; - } - - info!("Received connection from {:?}", socket.remote_endpoint()); - - loop { - let n = match socket.read(&mut buf).await { - Ok(0) => { - warn!("read EOF"); - break; - } - Ok(n) => n, - Err(e) => { - warn!("read error: {:?}", e); - break; - } - }; - - info!("rxd {:02x}", &buf[..n]); - - match socket.write_all(&buf[..n]).await { - Ok(()) => {} - Err(e) => { - warn!("write error: {:?}", e); - break; - } - }; - } - } -} - -static TX_CHANNEL: Channel = Channel::new(); -static RX_CHANNEL: Channel = Channel::new(); -static LINK_UP: AtomicBool = AtomicBool::new(false); - -struct Device { - mac_addr: [u8; 6], -} - -impl embassy_net::Device for Device { - fn register_waker(&mut self, waker: &Waker) { - // loopy loopy wakey wakey - waker.wake_by_ref() - } - - fn link_state(&mut self) -> embassy_net::LinkState { - match LINK_UP.load(Ordering::Relaxed) { - true => embassy_net::LinkState::Up, - false => embassy_net::LinkState::Down, - } - } - - fn capabilities(&self) -> embassy_net::DeviceCapabilities { - let mut caps = embassy_net::DeviceCapabilities::default(); - caps.max_transmission_unit = 1514; // 1500 IP + 14 ethernet header - caps.medium = embassy_net::Medium::Ethernet; - caps - } - - fn is_transmit_ready(&mut self) -> bool { - true - } - - fn transmit(&mut self, pkt: PacketBuf) { - if TX_CHANNEL.try_send(pkt).is_err() { - warn!("TX failed") - } - } - - fn receive<'a>(&mut self) -> Option { - RX_CHANNEL.try_recv().ok() - } - - fn ethernet_address(&self) -> [u8; 6] { - self.mac_addr - } -} diff --git a/examples/nrf/.cargo/config.toml b/examples/nrf52840-rtic/.cargo/config.toml similarity index 51% rename from examples/nrf/.cargo/config.toml rename to examples/nrf52840-rtic/.cargo/config.toml index 8ca28df39..17616a054 100644 --- a/examples/nrf/.cargo/config.toml +++ b/examples/nrf52840-rtic/.cargo/config.toml @@ -1,6 +1,6 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -# replace nRF82840_xxAA with your chip as listed in `probe-run --list-chips` -runner = "probe-run --chip nRF52840_xxAA" +# replace nRF82840_xxAA with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip nRF52840_xxAA" [build] target = "thumbv7em-none-eabi" diff --git a/examples/nrf52840-rtic/Cargo.toml b/examples/nrf52840-rtic/Cargo.toml new file mode 100644 index 000000000..ded3b7db8 --- /dev/null +++ b/examples/nrf52840-rtic/Cargo.toml @@ -0,0 +1,21 @@ +[package] +edition = "2021" +name = "embassy-nrf52840-rtic-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +rtic = { version = "2", features = ["thumbv7-backend"] } + +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["nightly", "unstable-traits", "defmt", "defmt-timestamp-uptime", "generic-queue"] } +embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["nightly", "unstable-traits", "defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +panic-probe = { version = "0.3", features = ["print-defmt"] } +futures = { version = "0.3.17", default-features = false, features = ["async-await"] } diff --git a/examples/nrf52840-rtic/build.rs b/examples/nrf52840-rtic/build.rs new file mode 100644 index 000000000..30691aa97 --- /dev/null +++ b/examples/nrf52840-rtic/build.rs @@ -0,0 +1,35 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/examples/nrf52840-rtic/memory.x b/examples/nrf52840-rtic/memory.x new file mode 100644 index 000000000..9b04edec0 --- /dev/null +++ b/examples/nrf52840-rtic/memory.x @@ -0,0 +1,7 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + /* These values correspond to the NRF52840 with Softdevices S140 7.0.1 */ + FLASH : ORIGIN = 0x00000000, LENGTH = 1024K + RAM : ORIGIN = 0x20000000, LENGTH = 256K +} diff --git a/examples/nrf52840-rtic/src/bin/blinky.rs b/examples/nrf52840-rtic/src/bin/blinky.rs new file mode 100644 index 000000000..a682c1932 --- /dev/null +++ b/examples/nrf52840-rtic/src/bin/blinky.rs @@ -0,0 +1,43 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use {defmt_rtt as _, panic_probe as _}; + +#[rtic::app(device = embassy_nrf, peripherals = false, dispatchers = [SWI0_EGU0, SWI1_EGU1])] +mod app { + use defmt::info; + use embassy_nrf::gpio::{Level, Output, OutputDrive}; + use embassy_nrf::peripherals; + use embassy_time::{Duration, Timer}; + + #[shared] + struct Shared {} + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + info!("Hello World!"); + + let p = embassy_nrf::init(Default::default()); + blink::spawn(p.P0_13).map_err(|_| ()).unwrap(); + + (Shared {}, Local {}) + } + + #[task(priority = 1)] + async fn blink(_cx: blink::Context, pin: peripherals::P0_13) { + let mut led = Output::new(pin, Level::Low, OutputDrive::Standard); + + loop { + info!("off!"); + led.set_high(); + Timer::after(Duration::from_millis(300)).await; + info!("on!"); + led.set_low(); + Timer::after(Duration::from_millis(300)).await; + } + } +} diff --git a/examples/nrf52840/.cargo/config.toml b/examples/nrf52840/.cargo/config.toml new file mode 100644 index 000000000..17616a054 --- /dev/null +++ b/examples/nrf52840/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace nRF82840_xxAA with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip nRF52840_xxAA" + +[build] +target = "thumbv7em-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/examples/nrf52840/Cargo.toml b/examples/nrf52840/Cargo.toml new file mode 100644 index 000000000..9b41ec5ab --- /dev/null +++ b/examples/nrf52840/Cargo.toml @@ -0,0 +1,61 @@ +[package] +edition = "2021" +name = "embassy-nrf52840-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[features] +default = ["nightly"] +nightly = [ + "embedded-hal-async", + "embassy-executor/nightly", + "embassy-nrf/nightly", + "embassy-net/nightly", + "embassy-net-esp-hosted", + "embassy-nrf/unstable-traits", + "embassy-time/nightly", + "embassy-time/unstable-traits", + "static_cell/nightly", + "embassy-usb", + "embedded-io/async", + "embassy-net", + "embassy-lora", + "lora-phy", + "lorawan-device", + "lorawan", +] + +[dependencies] +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac", "time"] } +embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"], optional = true } +embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt", "msos-descriptor",], optional = true } +embedded-io = "0.4.0" +embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["time", "defmt"], optional = true } +lora-phy = { version = "1", optional = true } +lorawan-device = { version = "0.10.0", default-features = false, features = ["async", "external-lora-phy"], optional = true } +lorawan = { version = "0.7.3", default-features = false, features = ["default-crypto"], optional = true } +embassy-net-esp-hosted = { version = "0.1.0", path = "../../embassy-net-esp-hosted", features = ["defmt"], optional = true } + +defmt = "0.3" +defmt-rtt = "0.4" + +fixed = "1.10.0" +static_cell = "1.1" +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +panic-probe = { version = "0.3", features = ["print-defmt"] } +futures = { version = "0.3.17", default-features = false, features = ["async-await"] } +rand = { version = "0.8.4", default-features = false } +embedded-storage = "0.3.0" +usbd-hid = "0.6.0" +serde = { version = "1.0.136", default-features = false } +embedded-hal-async = { version = "0.2.0-alpha.2", optional = true } +num-integer = { version = "0.1.45", default-features = false } +microfft = "0.5.0" + +[patch.crates-io] +lora-phy = { git = "https://github.com/embassy-rs/lora-phy", rev = "ad289428fd44b02788e2fa2116445cc8f640a265" } diff --git a/examples/nrf52840/build.rs b/examples/nrf52840/build.rs new file mode 100644 index 000000000..30691aa97 --- /dev/null +++ b/examples/nrf52840/build.rs @@ -0,0 +1,35 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/examples/nrf52840/memory.x b/examples/nrf52840/memory.x new file mode 100644 index 000000000..9b04edec0 --- /dev/null +++ b/examples/nrf52840/memory.x @@ -0,0 +1,7 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + /* These values correspond to the NRF52840 with Softdevices S140 7.0.1 */ + FLASH : ORIGIN = 0x00000000, LENGTH = 1024K + RAM : ORIGIN = 0x20000000, LENGTH = 256K +} diff --git a/examples/nrf/src/bin/blinky.rs b/examples/nrf52840/src/bin/blinky.rs similarity index 100% rename from examples/nrf/src/bin/blinky.rs rename to examples/nrf52840/src/bin/blinky.rs diff --git a/examples/nrf/src/bin/buffered_uart.rs b/examples/nrf52840/src/bin/buffered_uart.rs similarity index 69% rename from examples/nrf/src/bin/buffered_uart.rs rename to examples/nrf52840/src/bin/buffered_uart.rs index ea566f4b2..238695371 100644 --- a/examples/nrf/src/bin/buffered_uart.rs +++ b/examples/nrf52840/src/bin/buffered_uart.rs @@ -4,12 +4,15 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_nrf::buffered_uarte::{BufferedUarte, State}; -use embassy_nrf::{interrupt, uarte}; -use embedded_io::asynch::{BufRead, Write}; -use futures::pin_mut; +use embassy_nrf::buffered_uarte::{self, BufferedUarte}; +use embassy_nrf::{bind_interrupts, peripherals, uarte}; +use embedded_io::asynch::Write; use {defmt_rtt as _, panic_probe as _}; +bind_interrupts!(struct Irqs { + UARTE0_UART0 => buffered_uarte::InterruptHandler; +}); + #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_nrf::init(Default::default()); @@ -20,25 +23,19 @@ async fn main(_spawner: Spawner) { let mut tx_buffer = [0u8; 4096]; let mut rx_buffer = [0u8; 4096]; - let irq = interrupt::take!(UARTE0_UART0); - let mut state = State::new(); - // Please note - important to have hardware flow control (https://github.com/embassy-rs/embassy/issues/536) - let u = BufferedUarte::new( - &mut state, + let mut u = BufferedUarte::new( p.UARTE0, p.TIMER0, p.PPI_CH0, p.PPI_CH1, - irq, + p.PPI_GROUP0, + Irqs, p.P0_08, p.P0_06, - p.P0_07, - p.P0_05, config, &mut rx_buffer, &mut tx_buffer, ); - pin_mut!(u); info!("uarte initialized!"); diff --git a/examples/nrf/src/bin/channel.rs b/examples/nrf52840/src/bin/channel.rs similarity index 100% rename from examples/nrf/src/bin/channel.rs rename to examples/nrf52840/src/bin/channel.rs diff --git a/examples/nrf/src/bin/channel_sender_receiver.rs b/examples/nrf52840/src/bin/channel_sender_receiver.rs similarity index 100% rename from examples/nrf/src/bin/channel_sender_receiver.rs rename to examples/nrf52840/src/bin/channel_sender_receiver.rs diff --git a/examples/nrf/src/bin/executor_fairness_test.rs b/examples/nrf52840/src/bin/executor_fairness_test.rs similarity index 94% rename from examples/nrf/src/bin/executor_fairness_test.rs rename to examples/nrf52840/src/bin/executor_fairness_test.rs index 9ae030d07..2a28f2763 100644 --- a/examples/nrf/src/bin/executor_fairness_test.rs +++ b/examples/nrf52840/src/bin/executor_fairness_test.rs @@ -2,6 +2,7 @@ #![no_main] #![feature(type_alias_impl_trait)] +use core::future::poll_fn; use core::task::Poll; use defmt::{info, unwrap}; @@ -26,7 +27,7 @@ async fn run2() { #[embassy_executor::task] async fn run3() { - futures::future::poll_fn(|cx| { + poll_fn(|cx| { cx.waker().wake_by_ref(); Poll::<()>::Pending }) diff --git a/examples/nrf/src/bin/gpiote_channel.rs b/examples/nrf52840/src/bin/gpiote_channel.rs similarity index 100% rename from examples/nrf/src/bin/gpiote_channel.rs rename to examples/nrf52840/src/bin/gpiote_channel.rs diff --git a/examples/nrf/src/bin/gpiote_port.rs b/examples/nrf52840/src/bin/gpiote_port.rs similarity index 100% rename from examples/nrf/src/bin/gpiote_port.rs rename to examples/nrf52840/src/bin/gpiote_port.rs diff --git a/examples/nrf52840/src/bin/i2s_effect.rs b/examples/nrf52840/src/bin/i2s_effect.rs new file mode 100644 index 000000000..391514d93 --- /dev/null +++ b/examples/nrf52840/src/bin/i2s_effect.rs @@ -0,0 +1,116 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use core::f32::consts::PI; + +use defmt::{error, info}; +use embassy_executor::Spawner; +use embassy_nrf::i2s::{self, Channels, Config, MasterClock, MultiBuffering, Sample as _, SampleWidth, I2S}; +use embassy_nrf::{bind_interrupts, peripherals}; +use {defmt_rtt as _, panic_probe as _}; + +type Sample = i16; + +const NUM_BUFFERS: usize = 2; +const NUM_SAMPLES: usize = 4; + +bind_interrupts!(struct Irqs { + I2S => i2s::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + + let master_clock: MasterClock = i2s::ExactSampleRate::_50000.into(); + + let sample_rate = master_clock.sample_rate(); + info!("Sample rate: {}", sample_rate); + + let mut config = Config::default(); + config.sample_width = SampleWidth::_16bit; + config.channels = Channels::MonoLeft; + + let buffers_out = MultiBuffering::::new(); + let buffers_in = MultiBuffering::::new(); + let mut full_duplex_stream = I2S::new_master(p.I2S, Irqs, p.P0_25, p.P0_26, p.P0_27, master_clock, config) + .full_duplex(p.P0_29, p.P0_28, buffers_out, buffers_in); + + let mut modulator = SineOsc::new(); + modulator.set_frequency(8.0, 1.0 / sample_rate as f32); + modulator.set_amplitude(1.0); + + full_duplex_stream.start().await.expect("I2S Start"); + + loop { + let (buff_out, buff_in) = full_duplex_stream.buffers(); + for i in 0..NUM_SAMPLES { + let modulation = (Sample::SCALE as f32 * bipolar_to_unipolar(modulator.generate())) as Sample; + buff_out[i] = buff_in[i] * modulation; + } + + if let Err(err) = full_duplex_stream.send_and_receive().await { + error!("{}", err); + } + } +} + +struct SineOsc { + amplitude: f32, + modulo: f32, + phase_inc: f32, +} + +impl SineOsc { + const B: f32 = 4.0 / PI; + const C: f32 = -4.0 / (PI * PI); + const P: f32 = 0.225; + + pub fn new() -> Self { + Self { + amplitude: 1.0, + modulo: 0.0, + phase_inc: 0.0, + } + } + + pub fn set_frequency(&mut self, freq: f32, inv_sample_rate: f32) { + self.phase_inc = freq * inv_sample_rate; + } + + pub fn set_amplitude(&mut self, amplitude: f32) { + self.amplitude = amplitude; + } + + pub fn generate(&mut self) -> f32 { + let signal = self.parabolic_sin(self.modulo); + self.modulo += self.phase_inc; + if self.modulo < 0.0 { + self.modulo += 1.0; + } else if self.modulo > 1.0 { + self.modulo -= 1.0; + } + signal * self.amplitude + } + + fn parabolic_sin(&mut self, modulo: f32) -> f32 { + let angle = PI - modulo * 2.0 * PI; + let y = Self::B * angle + Self::C * angle * abs(angle); + Self::P * (y * abs(y) - y) + y + } +} + +#[inline] +fn abs(value: f32) -> f32 { + if value < 0.0 { + -value + } else { + value + } +} + +#[inline] +fn bipolar_to_unipolar(value: f32) -> f32 { + (value + 1.0) / 2.0 +} diff --git a/examples/nrf52840/src/bin/i2s_monitor.rs b/examples/nrf52840/src/bin/i2s_monitor.rs new file mode 100644 index 000000000..4ed597c0d --- /dev/null +++ b/examples/nrf52840/src/bin/i2s_monitor.rs @@ -0,0 +1,118 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::{debug, error, info}; +use embassy_executor::Spawner; +use embassy_nrf::i2s::{self, Channels, Config, DoubleBuffering, MasterClock, Sample as _, SampleWidth, I2S}; +use embassy_nrf::pwm::{Prescaler, SimplePwm}; +use embassy_nrf::{bind_interrupts, peripherals}; +use {defmt_rtt as _, panic_probe as _}; + +type Sample = i16; + +const NUM_SAMPLES: usize = 500; + +bind_interrupts!(struct Irqs { + I2S => i2s::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + + let master_clock: MasterClock = i2s::ExactSampleRate::_50000.into(); + + let sample_rate = master_clock.sample_rate(); + info!("Sample rate: {}", sample_rate); + + let mut config = Config::default(); + config.sample_width = SampleWidth::_16bit; + config.channels = Channels::MonoLeft; + + let buffers = DoubleBuffering::::new(); + let mut input_stream = + I2S::new_master(p.I2S, Irqs, p.P0_25, p.P0_26, p.P0_27, master_clock, config).input(p.P0_29, buffers); + + // Configure the PWM to use the pins corresponding to the RGB leds + let mut pwm = SimplePwm::new_3ch(p.PWM0, p.P0_23, p.P0_22, p.P0_24); + pwm.set_prescaler(Prescaler::Div1); + pwm.set_max_duty(255); + + let mut rms_online = RmsOnline::::default(); + + input_stream.start().await.expect("I2S Start"); + + loop { + let rms = rms_online.process(input_stream.buffer()); + let rgb = rgb_from_rms(rms); + + debug!("RMS: {}, RGB: {:?}", rms, rgb); + for i in 0..3 { + pwm.set_duty(i, rgb[i].into()); + } + + if let Err(err) = input_stream.receive().await { + error!("{}", err); + } + } +} + +/// RMS from 0.0 until 0.75 will give green with a proportional intensity +/// RMS from 0.75 until 0.9 will give a blend between orange and red proportionally to the intensity +/// RMS above 0.9 will give a red with a proportional intensity +fn rgb_from_rms(rms: f32) -> [u8; 3] { + if rms < 0.75 { + let intensity = rms / 0.75; + [0, (intensity * 165.0) as u8, 0] + } else if rms < 0.9 { + let intensity = (rms - 0.75) / 0.15; + [200, 165 - (165.0 * intensity) as u8, 0] + } else { + let intensity = (rms - 0.9) / 0.1; + [200 + (55.0 * intensity) as u8, 0, 0] + } +} + +pub struct RmsOnline { + pub squares: [f32; N], + pub head: usize, +} + +impl Default for RmsOnline { + fn default() -> Self { + RmsOnline { + squares: [0.0; N], + head: 0, + } + } +} + +impl RmsOnline { + pub fn reset(&mut self) { + self.squares = [0.0; N]; + self.head = 0; + } + + pub fn process(&mut self, buf: &[Sample]) -> f32 { + buf.iter() + .for_each(|sample| self.push(*sample as f32 / Sample::SCALE as f32)); + + let sum_of_squares = self.squares.iter().fold(0.0, |acc, v| acc + *v); + Self::approx_sqrt(sum_of_squares / N as f32) + } + + pub fn push(&mut self, signal: f32) { + let square = signal * signal; + self.squares[self.head] = square; + self.head = (self.head + 1) % N; + } + + /// Approximated sqrt taken from [micromath] + /// + /// [micromath]: https://docs.rs/micromath/latest/src/micromath/float/sqrt.rs.html#11-17 + /// + fn approx_sqrt(value: f32) -> f32 { + f32::from_bits((value.to_bits() + 0x3f80_0000) >> 1) + } +} diff --git a/examples/nrf52840/src/bin/i2s_waveform.rs b/examples/nrf52840/src/bin/i2s_waveform.rs new file mode 100644 index 000000000..f2c1166b1 --- /dev/null +++ b/examples/nrf52840/src/bin/i2s_waveform.rs @@ -0,0 +1,154 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use core::f32::consts::PI; + +use defmt::{error, info}; +use embassy_executor::Spawner; +use embassy_nrf::i2s::{self, Channels, Config, DoubleBuffering, MasterClock, Sample as _, SampleWidth, I2S}; +use embassy_nrf::{bind_interrupts, peripherals}; +use {defmt_rtt as _, panic_probe as _}; + +type Sample = i16; + +const NUM_SAMPLES: usize = 50; + +bind_interrupts!(struct Irqs { + I2S => i2s::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + + let master_clock: MasterClock = i2s::ExactSampleRate::_50000.into(); + + let sample_rate = master_clock.sample_rate(); + info!("Sample rate: {}", sample_rate); + + let mut config = Config::default(); + config.sample_width = SampleWidth::_16bit; + config.channels = Channels::MonoLeft; + + let buffers = DoubleBuffering::::new(); + let mut output_stream = + I2S::new_master(p.I2S, Irqs, p.P0_25, p.P0_26, p.P0_27, master_clock, config).output(p.P0_28, buffers); + + let mut waveform = Waveform::new(1.0 / sample_rate as f32); + + waveform.process(output_stream.buffer()); + + output_stream.start().await.expect("I2S Start"); + + loop { + waveform.process(output_stream.buffer()); + + if let Err(err) = output_stream.send().await { + error!("{}", err); + } + } +} + +struct Waveform { + inv_sample_rate: f32, + carrier: SineOsc, + freq_mod: SineOsc, + amp_mod: SineOsc, +} + +impl Waveform { + fn new(inv_sample_rate: f32) -> Self { + let mut carrier = SineOsc::new(); + carrier.set_frequency(110.0, inv_sample_rate); + + let mut freq_mod = SineOsc::new(); + freq_mod.set_frequency(1.0, inv_sample_rate); + freq_mod.set_amplitude(1.0); + + let mut amp_mod = SineOsc::new(); + amp_mod.set_frequency(16.0, inv_sample_rate); + amp_mod.set_amplitude(0.5); + + Self { + inv_sample_rate, + carrier, + freq_mod, + amp_mod, + } + } + + fn process(&mut self, buf: &mut [Sample]) { + for sample in buf.chunks_mut(1) { + let freq_modulation = bipolar_to_unipolar(self.freq_mod.generate()); + self.carrier + .set_frequency(110.0 + 440.0 * freq_modulation, self.inv_sample_rate); + + let amp_modulation = bipolar_to_unipolar(self.amp_mod.generate()); + self.carrier.set_amplitude(amp_modulation); + + let signal = self.carrier.generate(); + + sample[0] = (Sample::SCALE as f32 * signal) as Sample; + } + } +} + +struct SineOsc { + amplitude: f32, + modulo: f32, + phase_inc: f32, +} + +impl SineOsc { + const B: f32 = 4.0 / PI; + const C: f32 = -4.0 / (PI * PI); + const P: f32 = 0.225; + + pub fn new() -> Self { + Self { + amplitude: 1.0, + modulo: 0.0, + phase_inc: 0.0, + } + } + + pub fn set_frequency(&mut self, freq: f32, inv_sample_rate: f32) { + self.phase_inc = freq * inv_sample_rate; + } + + pub fn set_amplitude(&mut self, amplitude: f32) { + self.amplitude = amplitude; + } + + pub fn generate(&mut self) -> f32 { + let signal = self.parabolic_sin(self.modulo); + self.modulo += self.phase_inc; + if self.modulo < 0.0 { + self.modulo += 1.0; + } else if self.modulo > 1.0 { + self.modulo -= 1.0; + } + signal * self.amplitude + } + + fn parabolic_sin(&mut self, modulo: f32) -> f32 { + let angle = PI - modulo * 2.0 * PI; + let y = Self::B * angle + Self::C * angle * abs(angle); + Self::P * (y * abs(y) - y) + y + } +} + +#[inline] +fn abs(value: f32) -> f32 { + if value < 0.0 { + -value + } else { + value + } +} + +#[inline] +fn bipolar_to_unipolar(value: f32) -> f32 { + (value + 1.0) / 2.0 +} diff --git a/examples/nrf52840/src/bin/lora_cad.rs b/examples/nrf52840/src/bin/lora_cad.rs new file mode 100644 index 000000000..beca061ed --- /dev/null +++ b/examples/nrf52840/src/bin/lora_cad.rs @@ -0,0 +1,99 @@ +//! This example runs on the RAK4631 WisBlock, which has an nRF52840 MCU and Semtech Sx126x radio. +//! Other nrf/sx126x combinations may work with appropriate pin modifications. +//! It demonstrates LORA CAD functionality. +#![no_std] +#![no_main] +#![macro_use] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_lora::iv::GenericSx126xInterfaceVariant; +use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pin as _, Pull}; +use embassy_nrf::{bind_interrupts, peripherals, spim}; +use embassy_time::{Delay, Duration, Timer}; +use lora_phy::mod_params::*; +use lora_phy::sx1261_2::SX1261_2; +use lora_phy::LoRa; +use {defmt_rtt as _, panic_probe as _}; + +const LORA_FREQUENCY_IN_HZ: u32 = 903_900_000; // warning: set this appropriately for the region + +bind_interrupts!(struct Irqs { + SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1 => spim::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let mut spi_config = spim::Config::default(); + spi_config.frequency = spim::Frequency::M16; + + let spim = spim::Spim::new(p.TWISPI1, Irqs, p.P1_11, p.P1_13, p.P1_12, spi_config); + + let nss = Output::new(p.P1_10.degrade(), Level::High, OutputDrive::Standard); + let reset = Output::new(p.P1_06.degrade(), Level::High, OutputDrive::Standard); + let dio1 = Input::new(p.P1_15.degrade(), Pull::Down); + let busy = Input::new(p.P1_14.degrade(), Pull::Down); + let rf_switch_rx = Output::new(p.P1_05.degrade(), Level::Low, OutputDrive::Standard); + let rf_switch_tx = Output::new(p.P1_07.degrade(), Level::Low, OutputDrive::Standard); + + let iv = + GenericSx126xInterfaceVariant::new(nss, reset, dio1, busy, Some(rf_switch_rx), Some(rf_switch_tx)).unwrap(); + + let mut delay = Delay; + + let mut lora = { + match LoRa::new(SX1261_2::new(BoardType::Rak4631Sx1262, spim, iv), false, &mut delay).await { + Ok(l) => l, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + let mut debug_indicator = Output::new(p.P1_03, Level::Low, OutputDrive::Standard); + let mut start_indicator = Output::new(p.P1_04, Level::Low, OutputDrive::Standard); + + start_indicator.set_high(); + Timer::after(Duration::from_secs(5)).await; + start_indicator.set_low(); + + let mdltn_params = { + match lora.create_modulation_params( + SpreadingFactor::_10, + Bandwidth::_250KHz, + CodingRate::_4_8, + LORA_FREQUENCY_IN_HZ, + ) { + Ok(mp) => mp, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + match lora.prepare_for_cad(&mdltn_params, true).await { + Ok(()) => {} + Err(err) => { + info!("Radio error = {}", err); + return; + } + }; + + match lora.cad().await { + Ok(cad_activity_detected) => { + if cad_activity_detected { + info!("cad successful with activity detected") + } else { + info!("cad successful without activity detected") + } + debug_indicator.set_high(); + Timer::after(Duration::from_secs(5)).await; + debug_indicator.set_low(); + } + Err(err) => info!("cad unsuccessful = {}", err), + } +} diff --git a/examples/nrf52840/src/bin/lora_lorawan.rs b/examples/nrf52840/src/bin/lora_lorawan.rs new file mode 100644 index 000000000..c953680c6 --- /dev/null +++ b/examples/nrf52840/src/bin/lora_lorawan.rs @@ -0,0 +1,83 @@ +//! This example runs on the RAK4631 WisBlock, which has an nRF52840 MCU and Semtech Sx126x radio. +//! Other nrf/sx126x combinations may work with appropriate pin modifications. +//! It demonstrates LoRaWAN join functionality. +#![no_std] +#![no_main] +#![macro_use] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_lora::iv::GenericSx126xInterfaceVariant; +use embassy_lora::LoraTimer; +use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pin as _, Pull}; +use embassy_nrf::rng::Rng; +use embassy_nrf::{bind_interrupts, peripherals, rng, spim}; +use embassy_time::Delay; +use lora_phy::mod_params::*; +use lora_phy::sx1261_2::SX1261_2; +use lora_phy::LoRa; +use lorawan::default_crypto::DefaultFactory as Crypto; +use lorawan_device::async_device::lora_radio::LoRaRadio; +use lorawan_device::async_device::{region, Device, JoinMode}; +use {defmt_rtt as _, panic_probe as _}; + +const LORAWAN_REGION: region::Region = region::Region::EU868; // warning: set this appropriately for the region + +bind_interrupts!(struct Irqs { + SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1 => spim::InterruptHandler; + RNG => rng::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let mut spi_config = spim::Config::default(); + spi_config.frequency = spim::Frequency::M16; + + let spim = spim::Spim::new(p.TWISPI1, Irqs, p.P1_11, p.P1_13, p.P1_12, spi_config); + + let nss = Output::new(p.P1_10.degrade(), Level::High, OutputDrive::Standard); + let reset = Output::new(p.P1_06.degrade(), Level::High, OutputDrive::Standard); + let dio1 = Input::new(p.P1_15.degrade(), Pull::Down); + let busy = Input::new(p.P1_14.degrade(), Pull::Down); + let rf_switch_rx = Output::new(p.P1_05.degrade(), Level::Low, OutputDrive::Standard); + let rf_switch_tx = Output::new(p.P1_07.degrade(), Level::Low, OutputDrive::Standard); + + let iv = + GenericSx126xInterfaceVariant::new(nss, reset, dio1, busy, Some(rf_switch_rx), Some(rf_switch_tx)).unwrap(); + + let mut delay = Delay; + + let lora = { + match LoRa::new(SX1261_2::new(BoardType::Rak4631Sx1262, spim, iv), true, &mut delay).await { + Ok(l) => l, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + let radio = LoRaRadio::new(lora); + let region: region::Configuration = region::Configuration::new(LORAWAN_REGION); + let mut device: Device<_, Crypto, _, _> = Device::new(region, radio, LoraTimer::new(), Rng::new(p.RNG, Irqs)); + + defmt::info!("Joining LoRaWAN network"); + + // TODO: Adjust the EUI and Keys according to your network credentials + match device + .join(&JoinMode::OTAA { + deveui: [0, 0, 0, 0, 0, 0, 0, 0], + appeui: [0, 0, 0, 0, 0, 0, 0, 0], + appkey: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + }) + .await + { + Ok(()) => defmt::info!("LoRaWAN network joined"), + Err(err) => { + info!("Radio error = {}", err); + return; + } + }; +} diff --git a/examples/nrf52840/src/bin/lora_p2p_receive.rs b/examples/nrf52840/src/bin/lora_p2p_receive.rs new file mode 100644 index 000000000..563fe42ec --- /dev/null +++ b/examples/nrf52840/src/bin/lora_p2p_receive.rs @@ -0,0 +1,121 @@ +//! This example runs on the RAK4631 WisBlock, which has an nRF52840 MCU and Semtech Sx126x radio. +//! Other nrf/sx126x combinations may work with appropriate pin modifications. +//! It demonstrates LORA P2P receive functionality in conjunction with the lora_p2p_send example. +#![no_std] +#![no_main] +#![macro_use] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_lora::iv::GenericSx126xInterfaceVariant; +use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pin as _, Pull}; +use embassy_nrf::{bind_interrupts, peripherals, spim}; +use embassy_time::{Delay, Duration, Timer}; +use lora_phy::mod_params::*; +use lora_phy::sx1261_2::SX1261_2; +use lora_phy::LoRa; +use {defmt_rtt as _, panic_probe as _}; + +const LORA_FREQUENCY_IN_HZ: u32 = 903_900_000; // warning: set this appropriately for the region + +bind_interrupts!(struct Irqs { + SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1 => spim::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let mut spi_config = spim::Config::default(); + spi_config.frequency = spim::Frequency::M16; + + let spim = spim::Spim::new(p.TWISPI1, Irqs, p.P1_11, p.P1_13, p.P1_12, spi_config); + + let nss = Output::new(p.P1_10.degrade(), Level::High, OutputDrive::Standard); + let reset = Output::new(p.P1_06.degrade(), Level::High, OutputDrive::Standard); + let dio1 = Input::new(p.P1_15.degrade(), Pull::Down); + let busy = Input::new(p.P1_14.degrade(), Pull::Down); + let rf_switch_rx = Output::new(p.P1_05.degrade(), Level::Low, OutputDrive::Standard); + let rf_switch_tx = Output::new(p.P1_07.degrade(), Level::Low, OutputDrive::Standard); + + let iv = + GenericSx126xInterfaceVariant::new(nss, reset, dio1, busy, Some(rf_switch_rx), Some(rf_switch_tx)).unwrap(); + + let mut delay = Delay; + + let mut lora = { + match LoRa::new(SX1261_2::new(BoardType::Rak4631Sx1262, spim, iv), false, &mut delay).await { + Ok(l) => l, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + let mut debug_indicator = Output::new(p.P1_03, Level::Low, OutputDrive::Standard); + let mut start_indicator = Output::new(p.P1_04, Level::Low, OutputDrive::Standard); + + start_indicator.set_high(); + Timer::after(Duration::from_secs(5)).await; + start_indicator.set_low(); + + let mut receiving_buffer = [00u8; 100]; + + let mdltn_params = { + match lora.create_modulation_params( + SpreadingFactor::_10, + Bandwidth::_250KHz, + CodingRate::_4_8, + LORA_FREQUENCY_IN_HZ, + ) { + Ok(mp) => mp, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + let rx_pkt_params = { + match lora.create_rx_packet_params(4, false, receiving_buffer.len() as u8, true, false, &mdltn_params) { + Ok(pp) => pp, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + match lora + .prepare_for_rx(&mdltn_params, &rx_pkt_params, None, true, false, 0, 0x00ffffffu32) + .await + { + Ok(()) => {} + Err(err) => { + info!("Radio error = {}", err); + return; + } + }; + + loop { + receiving_buffer = [00u8; 100]; + match lora.rx(&rx_pkt_params, &mut receiving_buffer).await { + Ok((received_len, _rx_pkt_status)) => { + if (received_len == 3) + && (receiving_buffer[0] == 0x01u8) + && (receiving_buffer[1] == 0x02u8) + && (receiving_buffer[2] == 0x03u8) + { + info!("rx successful"); + debug_indicator.set_high(); + Timer::after(Duration::from_secs(5)).await; + debug_indicator.set_low(); + } else { + info!("rx unknown packet"); + } + } + Err(err) => info!("rx unsuccessful = {}", err), + } + } +} diff --git a/examples/nrf52840/src/bin/lora_p2p_receive_duty_cycle.rs b/examples/nrf52840/src/bin/lora_p2p_receive_duty_cycle.rs new file mode 100644 index 000000000..1fd8f61a2 --- /dev/null +++ b/examples/nrf52840/src/bin/lora_p2p_receive_duty_cycle.rs @@ -0,0 +1,131 @@ +//! This example runs on the RAK4631 WisBlock, which has an nRF52840 MCU and Semtech Sx126x radio. +//! Other nrf/sx126x combinations may work with appropriate pin modifications. +//! It demonstrates LoRa Rx duty cycle functionality in conjunction with the lora_p2p_send example. +#![no_std] +#![no_main] +#![macro_use] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_lora::iv::GenericSx126xInterfaceVariant; +use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pin as _, Pull}; +use embassy_nrf::{bind_interrupts, peripherals, spim}; +use embassy_time::{Delay, Duration, Timer}; +use lora_phy::mod_params::*; +use lora_phy::sx1261_2::SX1261_2; +use lora_phy::LoRa; +use {defmt_rtt as _, panic_probe as _}; + +const LORA_FREQUENCY_IN_HZ: u32 = 903_900_000; // warning: set this appropriately for the region + +bind_interrupts!(struct Irqs { + SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1 => spim::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let mut spi_config = spim::Config::default(); + spi_config.frequency = spim::Frequency::M16; + + let spim = spim::Spim::new(p.TWISPI1, Irqs, p.P1_11, p.P1_13, p.P1_12, spi_config); + + let nss = Output::new(p.P1_10.degrade(), Level::High, OutputDrive::Standard); + let reset = Output::new(p.P1_06.degrade(), Level::High, OutputDrive::Standard); + let dio1 = Input::new(p.P1_15.degrade(), Pull::Down); + let busy = Input::new(p.P1_14.degrade(), Pull::Down); + let rf_switch_rx = Output::new(p.P1_05.degrade(), Level::Low, OutputDrive::Standard); + let rf_switch_tx = Output::new(p.P1_07.degrade(), Level::Low, OutputDrive::Standard); + + let iv = + GenericSx126xInterfaceVariant::new(nss, reset, dio1, busy, Some(rf_switch_rx), Some(rf_switch_tx)).unwrap(); + + let mut delay = Delay; + + let mut lora = { + match LoRa::new(SX1261_2::new(BoardType::Rak4631Sx1262, spim, iv), false, &mut delay).await { + Ok(l) => l, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + let mut debug_indicator = Output::new(p.P1_03, Level::Low, OutputDrive::Standard); + let mut start_indicator = Output::new(p.P1_04, Level::Low, OutputDrive::Standard); + + start_indicator.set_high(); + Timer::after(Duration::from_secs(5)).await; + start_indicator.set_low(); + + let mut receiving_buffer = [00u8; 100]; + + let mdltn_params = { + match lora.create_modulation_params( + SpreadingFactor::_10, + Bandwidth::_250KHz, + CodingRate::_4_8, + LORA_FREQUENCY_IN_HZ, + ) { + Ok(mp) => mp, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + let rx_pkt_params = { + match lora.create_rx_packet_params(4, false, receiving_buffer.len() as u8, true, false, &mdltn_params) { + Ok(pp) => pp, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + // See "RM0453 Reference manual STM32WL5x advanced Arm®-based 32-bit MCUs with sub-GHz radio solution" for the best explanation of Rx duty cycle processing. + match lora + .prepare_for_rx( + &mdltn_params, + &rx_pkt_params, + Some(&DutyCycleParams { + rx_time: 300_000, // 300_000 units * 15.625 us/unit = 4.69 s + sleep_time: 200_000, // 200_000 units * 15.625 us/unit = 3.13 s + }), + false, + false, + 0, + 0, + ) + .await + { + Ok(()) => {} + Err(err) => { + info!("Radio error = {}", err); + return; + } + }; + + receiving_buffer = [00u8; 100]; + match lora.rx(&rx_pkt_params, &mut receiving_buffer).await { + Ok((received_len, _rx_pkt_status)) => { + if (received_len == 3) + && (receiving_buffer[0] == 0x01u8) + && (receiving_buffer[1] == 0x02u8) + && (receiving_buffer[2] == 0x03u8) + { + info!("rx successful"); + debug_indicator.set_high(); + Timer::after(Duration::from_secs(5)).await; + debug_indicator.set_low(); + } else { + info!("rx unknown packet") + } + } + Err(err) => info!("rx unsuccessful = {}", err), + } +} diff --git a/examples/nrf52840/src/bin/lora_p2p_send.rs b/examples/nrf52840/src/bin/lora_p2p_send.rs new file mode 100644 index 000000000..1c8bbc27a --- /dev/null +++ b/examples/nrf52840/src/bin/lora_p2p_send.rs @@ -0,0 +1,104 @@ +//! This example runs on the RAK4631 WisBlock, which has an nRF52840 MCU and Semtech Sx126x radio. +//! Other nrf/sx126x combinations may work with appropriate pin modifications. +//! It demonstrates LORA P2P send functionality. +#![no_std] +#![no_main] +#![macro_use] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_lora::iv::GenericSx126xInterfaceVariant; +use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pin as _, Pull}; +use embassy_nrf::{bind_interrupts, peripherals, spim}; +use embassy_time::Delay; +use lora_phy::mod_params::*; +use lora_phy::sx1261_2::SX1261_2; +use lora_phy::LoRa; +use {defmt_rtt as _, panic_probe as _}; + +const LORA_FREQUENCY_IN_HZ: u32 = 903_900_000; // warning: set this appropriately for the region + +bind_interrupts!(struct Irqs { + SPIM1_SPIS1_TWIM1_TWIS1_SPI1_TWI1 => spim::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let mut spi_config = spim::Config::default(); + spi_config.frequency = spim::Frequency::M16; + + let spim = spim::Spim::new(p.TWISPI1, Irqs, p.P1_11, p.P1_13, p.P1_12, spi_config); + + let nss = Output::new(p.P1_10.degrade(), Level::High, OutputDrive::Standard); + let reset = Output::new(p.P1_06.degrade(), Level::High, OutputDrive::Standard); + let dio1 = Input::new(p.P1_15.degrade(), Pull::Down); + let busy = Input::new(p.P1_14.degrade(), Pull::Down); + let rf_switch_rx = Output::new(p.P1_05.degrade(), Level::Low, OutputDrive::Standard); + let rf_switch_tx = Output::new(p.P1_07.degrade(), Level::Low, OutputDrive::Standard); + + let iv = + GenericSx126xInterfaceVariant::new(nss, reset, dio1, busy, Some(rf_switch_rx), Some(rf_switch_tx)).unwrap(); + + let mut delay = Delay; + + let mut lora = { + match LoRa::new(SX1261_2::new(BoardType::Rak4631Sx1262, spim, iv), false, &mut delay).await { + Ok(l) => l, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + let mdltn_params = { + match lora.create_modulation_params( + SpreadingFactor::_10, + Bandwidth::_250KHz, + CodingRate::_4_8, + LORA_FREQUENCY_IN_HZ, + ) { + Ok(mp) => mp, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + let mut tx_pkt_params = { + match lora.create_tx_packet_params(4, false, true, false, &mdltn_params) { + Ok(pp) => pp, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + match lora.prepare_for_tx(&mdltn_params, 20, false).await { + Ok(()) => {} + Err(err) => { + info!("Radio error = {}", err); + return; + } + }; + + let buffer = [0x01u8, 0x02u8, 0x03u8]; + match lora.tx(&mdltn_params, &mut tx_pkt_params, &buffer, 0xffffff).await { + Ok(()) => { + info!("TX DONE"); + } + Err(err) => { + info!("Radio error = {}", err); + return; + } + }; + + match lora.sleep(&mut delay).await { + Ok(()) => info!("Sleep successful"), + Err(err) => info!("Sleep unsuccessful = {}", err), + } +} diff --git a/examples/nrf52840/src/bin/manually_create_executor.rs b/examples/nrf52840/src/bin/manually_create_executor.rs new file mode 100644 index 000000000..12ce660f9 --- /dev/null +++ b/examples/nrf52840/src/bin/manually_create_executor.rs @@ -0,0 +1,49 @@ +// This example showcases how to manually create an executor. +// This is what the #[embassy::main] macro does behind the scenes. + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use cortex_m_rt::entry; +use defmt::{info, unwrap}; +use embassy_executor::Executor; +use embassy_time::{Duration, Timer}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::task] +async fn run1() { + loop { + info!("BIG INFREQUENT TICK"); + Timer::after(Duration::from_ticks(64000)).await; + } +} + +#[embassy_executor::task] +async fn run2() { + loop { + info!("tick"); + Timer::after(Duration::from_ticks(13000)).await; + } +} + +static EXECUTOR: StaticCell = StaticCell::new(); + +#[entry] +fn main() -> ! { + info!("Hello World!"); + + let _p = embassy_nrf::init(Default::default()); + + // Create the executor and put it in a StaticCell, because `run` needs `&'static mut Executor`. + let executor = EXECUTOR.init(Executor::new()); + + // Run it. + // `run` calls the closure then runs the executor forever. It never returns. + executor.run(|spawner| { + // Here we get access to a spawner to spawn the initial tasks. + unwrap!(spawner.spawn(run1())); + unwrap!(spawner.spawn(run2())); + }); +} diff --git a/examples/nrf/src/bin/multiprio.rs b/examples/nrf52840/src/bin/multiprio.rs similarity index 85% rename from examples/nrf/src/bin/multiprio.rs rename to examples/nrf52840/src/bin/multiprio.rs index 25806ae48..aab819117 100644 --- a/examples/nrf/src/bin/multiprio.rs +++ b/examples/nrf52840/src/bin/multiprio.rs @@ -59,9 +59,9 @@ use cortex_m_rt::entry; use defmt::{info, unwrap}; -use embassy_nrf::executor::{Executor, InterruptExecutor}; +use embassy_executor::{Executor, InterruptExecutor}; use embassy_nrf::interrupt; -use embassy_nrf::interrupt::InterruptExt; +use embassy_nrf::interrupt::{InterruptExt, Priority}; use embassy_time::{Duration, Instant, Timer}; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -108,10 +108,20 @@ async fn run_low() { } } -static EXECUTOR_HIGH: StaticCell> = StaticCell::new(); -static EXECUTOR_MED: StaticCell> = StaticCell::new(); +static EXECUTOR_HIGH: InterruptExecutor = InterruptExecutor::new(); +static EXECUTOR_MED: InterruptExecutor = InterruptExecutor::new(); static EXECUTOR_LOW: StaticCell = StaticCell::new(); +#[interrupt] +unsafe fn SWI1_EGU1() { + EXECUTOR_HIGH.on_interrupt() +} + +#[interrupt] +unsafe fn SWI0_EGU0() { + EXECUTOR_MED.on_interrupt() +} + #[entry] fn main() -> ! { info!("Hello World!"); @@ -119,17 +129,13 @@ fn main() -> ! { let _p = embassy_nrf::init(Default::default()); // High-priority executor: SWI1_EGU1, priority level 6 - let irq = interrupt::take!(SWI1_EGU1); - irq.set_priority(interrupt::Priority::P6); - let executor = EXECUTOR_HIGH.init(InterruptExecutor::new(irq)); - let spawner = executor.start(); + interrupt::SWI1_EGU1.set_priority(Priority::P6); + let spawner = EXECUTOR_HIGH.start(interrupt::SWI1_EGU1); unwrap!(spawner.spawn(run_high())); // Medium-priority executor: SWI0_EGU0, priority level 7 - let irq = interrupt::take!(SWI0_EGU0); - irq.set_priority(interrupt::Priority::P7); - let executor = EXECUTOR_MED.init(InterruptExecutor::new(irq)); - let spawner = executor.start(); + interrupt::SWI0_EGU0.set_priority(Priority::P7); + let spawner = EXECUTOR_MED.start(interrupt::SWI0_EGU0); unwrap!(spawner.spawn(run_med())); // Low priority executor: runs in thread mode, using WFE/SEV diff --git a/examples/nrf/src/bin/mutex.rs b/examples/nrf52840/src/bin/mutex.rs similarity index 100% rename from examples/nrf/src/bin/mutex.rs rename to examples/nrf52840/src/bin/mutex.rs diff --git a/examples/nrf/src/bin/nvmc.rs b/examples/nrf52840/src/bin/nvmc.rs similarity index 94% rename from examples/nrf/src/bin/nvmc.rs rename to examples/nrf52840/src/bin/nvmc.rs index 75d090fbb..31c6fe4b6 100644 --- a/examples/nrf/src/bin/nvmc.rs +++ b/examples/nrf52840/src/bin/nvmc.rs @@ -14,7 +14,7 @@ async fn main(_spawner: Spawner) { let p = embassy_nrf::init(Default::default()); info!("Hello NVMC!"); - // probe-run breaks without this, I'm not sure why. + // probe-rs run breaks without this, I'm not sure why. Timer::after(Duration::from_secs(1)).await; let mut f = Nvmc::new(p.NVMC); diff --git a/examples/nrf52840/src/bin/pdm.rs b/examples/nrf52840/src/bin/pdm.rs new file mode 100644 index 000000000..47fe67733 --- /dev/null +++ b/examples/nrf52840/src/bin/pdm.rs @@ -0,0 +1,55 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_nrf::pdm::{self, Config, Pdm}; +use embassy_nrf::{bind_interrupts, peripherals}; +use embassy_time::{Duration, Timer}; +use fixed::types::I7F1; +use num_integer::Roots; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PDM => pdm::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_p: Spawner) { + let p = embassy_nrf::init(Default::default()); + let config = Config::default(); + let mut pdm = Pdm::new(p.PDM, Irqs, p.P0_01, p.P0_00, config); + + loop { + for gain in [I7F1::from_num(-20), I7F1::from_num(0), I7F1::from_num(20)] { + pdm.set_gain(gain, gain); + info!("Gain = {} dB", defmt::Debug2Format(&gain)); + pdm.start().await; + + // wait some time till the microphon settled + Timer::after(Duration::from_millis(1000)).await; + + const SAMPLES: usize = 2048; + let mut buf = [0i16; SAMPLES]; + pdm.sample(&mut buf).await.unwrap(); + + let mean = (buf.iter().map(|v| i32::from(*v)).sum::() / buf.len() as i32) as i16; + info!( + "{} samples, min {=i16}, max {=i16}, mean {=i16}, AC RMS {=i16}", + buf.len(), + buf.iter().min().unwrap(), + buf.iter().max().unwrap(), + mean, + ( + buf.iter().map(|v| i32::from(*v - mean).pow(2)).fold(0i32, |a,b| a.saturating_add(b)) + / buf.len() as i32).sqrt() as i16, + ); + + info!("samples: {:?}", &buf); + + pdm.stop().await; + Timer::after(Duration::from_millis(100)).await; + } + } +} diff --git a/examples/nrf/src/bin/pdm_continuous.rs b/examples/nrf52840/src/bin/pdm_continuous.rs similarity index 87% rename from examples/nrf/src/bin/pdm_continuous.rs rename to examples/nrf52840/src/bin/pdm_continuous.rs index 284a68af2..9eaf30717 100644 --- a/examples/nrf/src/bin/pdm_continuous.rs +++ b/examples/nrf52840/src/bin/pdm_continuous.rs @@ -5,8 +5,8 @@ use defmt::info; use core::cmp::Ordering; use embassy_executor::Spawner; -use embassy_nrf::interrupt; -use embassy_nrf::pdm::{Config, Channels, Pdm, SamplerState, Frequency, Ratio}; +use embassy_nrf::{bind_interrupts, peripherals}; +use embassy_nrf::pdm::{self, Config, OperationMode, Pdm, SamplerState, Frequency, Ratio}; use fixed::types::I7F1; use num_integer::Roots; use microfft::real::rfft_1024; @@ -14,6 +14,10 @@ use {defmt_rtt as _, panic_probe as _}; // Demonstrates both continuous sampling and scanning multiple channels driven by a PPI linked timer +bind_interrupts!(struct Irqs { + PDM => pdm::InterruptHandler; +}); + #[embassy_executor::main] async fn main(_p: Spawner) { let mut p = embassy_nrf::init(Default::default()); @@ -21,9 +25,9 @@ async fn main(_p: Spawner) { // Pins are correct for the onboard microphone on the Feather nRF52840 Sense. config.frequency = Frequency::_1280K; // 16 kHz sample rate config.ratio = Ratio::RATIO80; - config.channels = Channels::Mono; + config.operation_mode = OperationMode::Mono; config.gain_left = I7F1::from_bits(5); // 2.5 dB - let mut pdm = Pdm::new(p.PDM, interrupt::take!(PDM), &mut p.P0_00, &mut p.P0_01, config); + let mut pdm = Pdm::new(p.PDM, Irqs, &mut p.P0_00, &mut p.P0_01, config); let mut bufs = [[0; 1024]; 2]; @@ -54,7 +58,7 @@ async fn main(_p: Spawner) { SamplerState::Sampled }, ) - .await; + .await.unwrap(); } fn fft_peak_freq(input: &[i16; 1024]) -> (usize, u32) { diff --git a/examples/nrf/src/bin/ppi.rs b/examples/nrf52840/src/bin/ppi.rs similarity index 100% rename from examples/nrf/src/bin/ppi.rs rename to examples/nrf52840/src/bin/ppi.rs diff --git a/examples/nrf/src/bin/pubsub.rs b/examples/nrf52840/src/bin/pubsub.rs similarity index 97% rename from examples/nrf/src/bin/pubsub.rs rename to examples/nrf52840/src/bin/pubsub.rs index 688e6d075..cca60ebc9 100644 --- a/examples/nrf/src/bin/pubsub.rs +++ b/examples/nrf52840/src/bin/pubsub.rs @@ -74,9 +74,9 @@ async fn fast_logger(mut messages: Subscriber<'static, ThreadModeRawMutex, Messa } /// A logger task that awaits the messages, but also does some other work. -/// Because of this, depeding on how the messages were published, the subscriber might miss some messages +/// Because of this, depending on how the messages were published, the subscriber might miss some messages. /// -/// This takes the dynamic `DynSubscriber`. This is not as performant as the generic version, but let's you ignore some of the generics +/// This takes the dynamic `DynSubscriber`. This is not as performant as the generic version, but let's you ignore some of the generics. #[embassy_executor::task] async fn slow_logger(mut messages: DynSubscriber<'static, Message>) { loop { diff --git a/examples/nrf/src/bin/pwm.rs b/examples/nrf52840/src/bin/pwm.rs similarity index 100% rename from examples/nrf/src/bin/pwm.rs rename to examples/nrf52840/src/bin/pwm.rs diff --git a/examples/nrf/src/bin/pwm_double_sequence.rs b/examples/nrf52840/src/bin/pwm_double_sequence.rs similarity index 100% rename from examples/nrf/src/bin/pwm_double_sequence.rs rename to examples/nrf52840/src/bin/pwm_double_sequence.rs diff --git a/examples/nrf/src/bin/pwm_sequence.rs b/examples/nrf52840/src/bin/pwm_sequence.rs similarity index 100% rename from examples/nrf/src/bin/pwm_sequence.rs rename to examples/nrf52840/src/bin/pwm_sequence.rs diff --git a/examples/nrf/src/bin/pwm_sequence_ppi.rs b/examples/nrf52840/src/bin/pwm_sequence_ppi.rs similarity index 100% rename from examples/nrf/src/bin/pwm_sequence_ppi.rs rename to examples/nrf52840/src/bin/pwm_sequence_ppi.rs diff --git a/examples/nrf/src/bin/pwm_sequence_ws2812b.rs b/examples/nrf52840/src/bin/pwm_sequence_ws2812b.rs similarity index 100% rename from examples/nrf/src/bin/pwm_sequence_ws2812b.rs rename to examples/nrf52840/src/bin/pwm_sequence_ws2812b.rs diff --git a/examples/nrf/src/bin/pwm_servo.rs b/examples/nrf52840/src/bin/pwm_servo.rs similarity index 100% rename from examples/nrf/src/bin/pwm_servo.rs rename to examples/nrf52840/src/bin/pwm_servo.rs diff --git a/examples/nrf/src/bin/qdec.rs b/examples/nrf52840/src/bin/qdec.rs similarity index 69% rename from examples/nrf/src/bin/qdec.rs rename to examples/nrf52840/src/bin/qdec.rs index 600bba07a..59783d312 100644 --- a/examples/nrf/src/bin/qdec.rs +++ b/examples/nrf52840/src/bin/qdec.rs @@ -4,16 +4,19 @@ use defmt::info; use embassy_executor::Spawner; -use embassy_nrf::interrupt; use embassy_nrf::qdec::{self, Qdec}; +use embassy_nrf::{bind_interrupts, peripherals}; use {defmt_rtt as _, panic_probe as _}; +bind_interrupts!(struct Irqs { + QDEC => qdec::InterruptHandler; +}); + #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_nrf::init(Default::default()); - let irq = interrupt::take!(QDEC); let config = qdec::Config::default(); - let mut rotary_enc = Qdec::new(p.QDEC, irq, p.P0_31, p.P0_30, config); + let mut rotary_enc = Qdec::new(p.QDEC, Irqs, p.P0_31, p.P0_30, config); info!("Turn rotary encoder!"); let mut value = 0; diff --git a/examples/nrf/src/bin/qspi.rs b/examples/nrf52840/src/bin/qspi.rs similarity index 69% rename from examples/nrf/src/bin/qspi.rs rename to examples/nrf52840/src/bin/qspi.rs index bdcf710b8..9e8a01f4e 100644 --- a/examples/nrf/src/bin/qspi.rs +++ b/examples/nrf52840/src/bin/qspi.rs @@ -4,7 +4,8 @@ use defmt::{assert_eq, info, unwrap}; use embassy_executor::Spawner; -use embassy_nrf::{interrupt, qspi}; +use embassy_nrf::qspi::Frequency; +use embassy_nrf::{bind_interrupts, peripherals, qspi}; use {defmt_rtt as _, panic_probe as _}; const PAGE_SIZE: usize = 4096; @@ -14,18 +15,23 @@ const PAGE_SIZE: usize = 4096; #[repr(C, align(4))] struct AlignedBuf([u8; 4096]); +bind_interrupts!(struct Irqs { + QSPI => qspi::InterruptHandler; +}); + #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_nrf::init(Default::default()); // Config for the MX25R64 present in the nRF52840 DK let mut config = qspi::Config::default(); + config.capacity = 8 * 1024 * 1024; // 8 MB + config.frequency = Frequency::M32; config.read_opcode = qspi::ReadOpcode::READ4IO; config.write_opcode = qspi::WriteOpcode::PP4IO; config.write_page_size = qspi::WritePageSize::_256BYTES; - let irq = interrupt::take!(QSPI); - let mut q: qspi::Qspi<_, 67108864> = qspi::Qspi::new( - p.QSPI, irq, p.P0_19, p.P0_17, p.P0_20, p.P0_21, p.P0_22, p.P0_23, config, + let mut q = qspi::Qspi::new( + p.QSPI, Irqs, p.P0_19, p.P0_17, p.P0_20, p.P0_21, p.P0_22, p.P0_23, config, ); let mut id = [1; 3]; @@ -52,23 +58,23 @@ async fn main(_spawner: Spawner) { for i in 0..8 { info!("page {:?}: erasing... ", i); - unwrap!(q.erase(i * PAGE_SIZE).await); + unwrap!(q.erase(i * PAGE_SIZE as u32).await); for j in 0..PAGE_SIZE { - buf.0[j] = pattern((j + i * PAGE_SIZE) as u32); + buf.0[j] = pattern((j as u32 + i * PAGE_SIZE as u32) as u32); } info!("programming..."); - unwrap!(q.write(i * PAGE_SIZE, &buf.0).await); + unwrap!(q.write(i * PAGE_SIZE as u32, &buf.0).await); } for i in 0..8 { info!("page {:?}: reading... ", i); - unwrap!(q.read(i * PAGE_SIZE, &mut buf.0).await); + unwrap!(q.read(i * PAGE_SIZE as u32, &mut buf.0).await); info!("verifying..."); for j in 0..PAGE_SIZE { - assert_eq!(buf.0[j], pattern((j + i * PAGE_SIZE) as u32)); + assert_eq!(buf.0[j], pattern((j as u32 + i * PAGE_SIZE as u32) as u32)); } } diff --git a/examples/nrf/src/bin/qspi_lowpower.rs b/examples/nrf52840/src/bin/qspi_lowpower.rs similarity index 87% rename from examples/nrf/src/bin/qspi_lowpower.rs rename to examples/nrf52840/src/bin/qspi_lowpower.rs index 9341a2376..22a5c0c6d 100644 --- a/examples/nrf/src/bin/qspi_lowpower.rs +++ b/examples/nrf52840/src/bin/qspi_lowpower.rs @@ -6,7 +6,8 @@ use core::mem; use defmt::{info, unwrap}; use embassy_executor::Spawner; -use embassy_nrf::{interrupt, qspi}; +use embassy_nrf::qspi::Frequency; +use embassy_nrf::{bind_interrupts, peripherals, qspi}; use embassy_time::{Duration, Timer}; use {defmt_rtt as _, panic_probe as _}; @@ -15,14 +16,19 @@ use {defmt_rtt as _, panic_probe as _}; #[repr(C, align(4))] struct AlignedBuf([u8; 64]); +bind_interrupts!(struct Irqs { + QSPI => qspi::InterruptHandler; +}); + #[embassy_executor::main] async fn main(_p: Spawner) { let mut p = embassy_nrf::init(Default::default()); - let mut irq = interrupt::take!(QSPI); loop { // Config for the MX25R64 present in the nRF52840 DK let mut config = qspi::Config::default(); + config.capacity = 8 * 1024 * 1024; // 8 MB + config.frequency = Frequency::M32; config.read_opcode = qspi::ReadOpcode::READ4IO; config.write_opcode = qspi::WriteOpcode::PP4IO; config.write_page_size = qspi::WritePageSize::_256BYTES; @@ -31,9 +37,9 @@ async fn main(_p: Spawner) { exit_time: 3, // tRDP = 35uS }); - let mut q: qspi::Qspi<_, 67108864> = qspi::Qspi::new( + let mut q = qspi::Qspi::new( &mut p.QSPI, - &mut irq, + Irqs, &mut p.P0_19, &mut p.P0_17, &mut p.P0_20, diff --git a/examples/nrf/src/bin/raw_spawn.rs b/examples/nrf52840/src/bin/raw_spawn.rs similarity index 100% rename from examples/nrf/src/bin/raw_spawn.rs rename to examples/nrf52840/src/bin/raw_spawn.rs diff --git a/examples/nrf/src/bin/rng.rs b/examples/nrf52840/src/bin/rng.rs similarity index 83% rename from examples/nrf/src/bin/rng.rs rename to examples/nrf52840/src/bin/rng.rs index 647073949..855743f50 100644 --- a/examples/nrf/src/bin/rng.rs +++ b/examples/nrf52840/src/bin/rng.rs @@ -3,15 +3,19 @@ #![feature(type_alias_impl_trait)] use embassy_executor::Spawner; -use embassy_nrf::interrupt; use embassy_nrf::rng::Rng; +use embassy_nrf::{bind_interrupts, peripherals, rng}; use rand::Rng as _; use {defmt_rtt as _, panic_probe as _}; +bind_interrupts!(struct Irqs { + RNG => rng::InterruptHandler; +}); + #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_nrf::init(Default::default()); - let mut rng = Rng::new(p.RNG, interrupt::take!(RNG)); + let mut rng = Rng::new(p.RNG, Irqs); // Async API let mut bytes = [0; 4]; diff --git a/examples/nrf/src/bin/saadc.rs b/examples/nrf52840/src/bin/saadc.rs similarity index 77% rename from examples/nrf/src/bin/saadc.rs rename to examples/nrf52840/src/bin/saadc.rs index 7cf588090..ffd9a7f4b 100644 --- a/examples/nrf/src/bin/saadc.rs +++ b/examples/nrf52840/src/bin/saadc.rs @@ -4,17 +4,21 @@ use defmt::info; use embassy_executor::Spawner; -use embassy_nrf::interrupt; use embassy_nrf::saadc::{ChannelConfig, Config, Saadc}; +use embassy_nrf::{bind_interrupts, saadc}; use embassy_time::{Duration, Timer}; use {defmt_rtt as _, panic_probe as _}; +bind_interrupts!(struct Irqs { + SAADC => saadc::InterruptHandler; +}); + #[embassy_executor::main] async fn main(_p: Spawner) { let mut p = embassy_nrf::init(Default::default()); let config = Config::default(); let channel_config = ChannelConfig::single_ended(&mut p.P0_02); - let mut saadc = Saadc::new(p.SAADC, interrupt::take!(SAADC), config, [channel_config]); + let mut saadc = Saadc::new(p.SAADC, Irqs, config, [channel_config]); loop { let mut buf = [0; 1]; diff --git a/examples/nrf/src/bin/saadc_continuous.rs b/examples/nrf52840/src/bin/saadc_continuous.rs similarity index 90% rename from examples/nrf/src/bin/saadc_continuous.rs rename to examples/nrf52840/src/bin/saadc_continuous.rs index bb50ac65e..a25e17465 100644 --- a/examples/nrf/src/bin/saadc_continuous.rs +++ b/examples/nrf52840/src/bin/saadc_continuous.rs @@ -4,14 +4,18 @@ use defmt::info; use embassy_executor::Spawner; -use embassy_nrf::interrupt; -use embassy_nrf::saadc::{ChannelConfig, Config, Saadc, SamplerState}; +use embassy_nrf::saadc::{CallbackResult, ChannelConfig, Config, Saadc}; use embassy_nrf::timer::Frequency; +use embassy_nrf::{bind_interrupts, saadc}; use embassy_time::Duration; use {defmt_rtt as _, panic_probe as _}; // Demonstrates both continuous sampling and scanning multiple channels driven by a PPI linked timer +bind_interrupts!(struct Irqs { + SAADC => saadc::InterruptHandler; +}); + #[embassy_executor::main] async fn main(_p: Spawner) { let mut p = embassy_nrf::init(Default::default()); @@ -21,7 +25,7 @@ async fn main(_p: Spawner) { let channel_3_config = ChannelConfig::single_ended(&mut p.P0_04); let mut saadc = Saadc::new( p.SAADC, - interrupt::take!(SAADC), + Irqs, config, [channel_1_config, channel_2_config, channel_3_config], ); @@ -61,7 +65,7 @@ async fn main(_p: Spawner) { c = 0; a = 0; } - SamplerState::Sampled + CallbackResult::Continue }, ) .await; diff --git a/examples/nrf/src/bin/self_spawn.rs b/examples/nrf52840/src/bin/self_spawn.rs similarity index 81% rename from examples/nrf/src/bin/self_spawn.rs rename to examples/nrf52840/src/bin/self_spawn.rs index 196255a52..31ea6c81e 100644 --- a/examples/nrf/src/bin/self_spawn.rs +++ b/examples/nrf52840/src/bin/self_spawn.rs @@ -7,7 +7,11 @@ use embassy_executor::Spawner; use embassy_time::{Duration, Timer}; use {defmt_rtt as _, panic_probe as _}; -#[embassy_executor::task(pool_size = 2)] +mod config { + pub const MY_TASK_POOL_SIZE: usize = 2; +} + +#[embassy_executor::task(pool_size = config::MY_TASK_POOL_SIZE)] async fn my_task(spawner: Spawner, n: u32) { Timer::after(Duration::from_secs(1)).await; info!("Spawning self! {}", n); diff --git a/examples/nrf/src/bin/self_spawn_current_executor.rs b/examples/nrf52840/src/bin/self_spawn_current_executor.rs similarity index 100% rename from examples/nrf/src/bin/self_spawn_current_executor.rs rename to examples/nrf52840/src/bin/self_spawn_current_executor.rs diff --git a/examples/nrf/src/bin/spim.rs b/examples/nrf52840/src/bin/spim.rs similarity index 87% rename from examples/nrf/src/bin/spim.rs rename to examples/nrf52840/src/bin/spim.rs index 132e01660..9d1843a8f 100644 --- a/examples/nrf/src/bin/spim.rs +++ b/examples/nrf52840/src/bin/spim.rs @@ -5,9 +5,13 @@ use defmt::{info, unwrap}; use embassy_executor::Spawner; use embassy_nrf::gpio::{Level, Output, OutputDrive}; -use embassy_nrf::{interrupt, spim}; +use embassy_nrf::{bind_interrupts, peripherals, spim}; use {defmt_rtt as _, panic_probe as _}; +bind_interrupts!(struct Irqs { + SPIM3 => spim::InterruptHandler; +}); + #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_nrf::init(Default::default()); @@ -16,8 +20,7 @@ async fn main(_spawner: Spawner) { let mut config = spim::Config::default(); config.frequency = spim::Frequency::M16; - let irq = interrupt::take!(SPIM3); - let mut spim = spim::Spim::new(p.SPI3, irq, p.P0_29, p.P0_28, p.P0_30, config); + let mut spim = spim::Spim::new(p.SPI3, Irqs, p.P0_29, p.P0_28, p.P0_30, config); let mut ncs = Output::new(p.P0_31, Level::High, OutputDrive::Standard); diff --git a/examples/nrf52840/src/bin/spis.rs b/examples/nrf52840/src/bin/spis.rs new file mode 100644 index 000000000..77b6e8b64 --- /dev/null +++ b/examples/nrf52840/src/bin/spis.rs @@ -0,0 +1,30 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_nrf::spis::{Config, Spis}; +use embassy_nrf::{bind_interrupts, peripherals, spis}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + SPIM2_SPIS2_SPI2 => spis::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + info!("Running!"); + + let mut spis = Spis::new(p.SPI2, Irqs, p.P0_31, p.P0_29, p.P0_28, p.P0_30, Config::default()); + + loop { + let mut rx_buf = [0_u8; 64]; + let tx_buf = [1_u8, 2, 3, 4, 5, 6, 7, 8]; + if let Ok((n_rx, n_tx)) = spis.transfer(&mut rx_buf, &tx_buf).await { + info!("RX: {:?}", rx_buf[..n_rx]); + info!("TX: {:?}", tx_buf[..n_tx]); + } + } +} diff --git a/examples/nrf/src/bin/temp.rs b/examples/nrf52840/src/bin/temp.rs similarity index 75% rename from examples/nrf/src/bin/temp.rs rename to examples/nrf52840/src/bin/temp.rs index b06ac709e..70957548f 100644 --- a/examples/nrf/src/bin/temp.rs +++ b/examples/nrf52840/src/bin/temp.rs @@ -4,16 +4,19 @@ use defmt::info; use embassy_executor::Spawner; -use embassy_nrf::interrupt; use embassy_nrf::temp::Temp; +use embassy_nrf::{bind_interrupts, temp}; use embassy_time::{Duration, Timer}; use {defmt_rtt as _, panic_probe as _}; +bind_interrupts!(struct Irqs { + TEMP => temp::InterruptHandler; +}); + #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_nrf::init(Default::default()); - let irq = interrupt::take!(TEMP); - let mut temp = Temp::new(p.TEMP, irq); + let mut temp = Temp::new(p.TEMP, Irqs); loop { let value = temp.read().await; diff --git a/examples/nrf/src/bin/timer.rs b/examples/nrf52840/src/bin/timer.rs similarity index 100% rename from examples/nrf/src/bin/timer.rs rename to examples/nrf52840/src/bin/timer.rs diff --git a/examples/nrf/src/bin/twim.rs b/examples/nrf52840/src/bin/twim.rs similarity index 72% rename from examples/nrf/src/bin/twim.rs rename to examples/nrf52840/src/bin/twim.rs index a027cc1e7..959e3a4be 100644 --- a/examples/nrf/src/bin/twim.rs +++ b/examples/nrf52840/src/bin/twim.rs @@ -8,19 +8,22 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_nrf::interrupt; use embassy_nrf::twim::{self, Twim}; +use embassy_nrf::{bind_interrupts, peripherals}; use {defmt_rtt as _, panic_probe as _}; const ADDRESS: u8 = 0x50; +bind_interrupts!(struct Irqs { + SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0 => twim::InterruptHandler; +}); + #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_nrf::init(Default::default()); info!("Initializing TWI..."); let config = twim::Config::default(); - let irq = interrupt::take!(SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); - let mut twi = Twim::new(p.TWISPI0, irq, p.P0_03, p.P0_04, config); + let mut twi = Twim::new(p.TWISPI0, Irqs, p.P0_03, p.P0_04, config); info!("Reading..."); diff --git a/examples/nrf/src/bin/twim_lowpower.rs b/examples/nrf52840/src/bin/twim_lowpower.rs similarity index 84% rename from examples/nrf/src/bin/twim_lowpower.rs rename to examples/nrf52840/src/bin/twim_lowpower.rs index e30cc9688..0970d3c3c 100644 --- a/examples/nrf/src/bin/twim_lowpower.rs +++ b/examples/nrf52840/src/bin/twim_lowpower.rs @@ -12,25 +12,28 @@ use core::mem; use defmt::*; use embassy_executor::Spawner; -use embassy_nrf::interrupt; use embassy_nrf::twim::{self, Twim}; +use embassy_nrf::{bind_interrupts, peripherals}; use embassy_time::{Duration, Timer}; use {defmt_rtt as _, panic_probe as _}; const ADDRESS: u8 = 0x50; +bind_interrupts!(struct Irqs { + SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0 => twim::InterruptHandler; +}); + #[embassy_executor::main] async fn main(_p: Spawner) { let mut p = embassy_nrf::init(Default::default()); info!("Started!"); - let mut irq = interrupt::take!(SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0); loop { info!("Initializing TWI..."); let config = twim::Config::default(); // Create the TWIM instance with borrowed singletons, so they're not consumed. - let mut twi = Twim::new(&mut p.TWISPI0, &mut irq, &mut p.P0_03, &mut p.P0_04, config); + let mut twi = Twim::new(&mut p.TWISPI0, Irqs, &mut p.P0_03, &mut p.P0_04, config); info!("Reading..."); diff --git a/examples/nrf52840/src/bin/twis.rs b/examples/nrf52840/src/bin/twis.rs new file mode 100644 index 000000000..aa42b679e --- /dev/null +++ b/examples/nrf52840/src/bin/twis.rs @@ -0,0 +1,48 @@ +//! TWIS example + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_nrf::twis::{self, Command, Twis}; +use embassy_nrf::{bind_interrupts, peripherals}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0 => twis::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + + let mut config = twis::Config::default(); + config.address0 = 0x55; // Set i2c address + let mut i2c = Twis::new(p.TWISPI0, Irqs, p.P0_03, p.P0_04, config); + + info!("Listening..."); + loop { + let response = [1, 2, 3, 4, 5, 6, 7, 8]; + // This buffer is used if the i2c master performs a Write or WriteRead + let mut buf = [0u8; 16]; + match i2c.listen(&mut buf).await { + Ok(Command::Read) => { + info!("Got READ command. Respond with data:\n{:?}\n", response); + if let Err(e) = i2c.respond_to_read(&response).await { + error!("{:?}", e); + } + } + Ok(Command::Write(n)) => info!("Got WRITE command with data:\n{:?}\n", buf[..n]), + Ok(Command::WriteRead(n)) => { + info!("Got WRITE/READ command with data:\n{:?}", buf[..n]); + info!("Respond with data:\n{:?}\n", response); + if let Err(e) = i2c.respond_to_read(&response).await { + error!("{:?}", e); + } + } + Err(e) => error!("{:?}", e), + } + } +} diff --git a/examples/nrf/src/bin/uart.rs b/examples/nrf52840/src/bin/uart.rs similarity index 76% rename from examples/nrf/src/bin/uart.rs rename to examples/nrf52840/src/bin/uart.rs index 600f7a6ef..50d5cab8c 100644 --- a/examples/nrf/src/bin/uart.rs +++ b/examples/nrf52840/src/bin/uart.rs @@ -4,9 +4,13 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_nrf::{interrupt, uarte}; +use embassy_nrf::{bind_interrupts, peripherals, uarte}; use {defmt_rtt as _, panic_probe as _}; +bind_interrupts!(struct Irqs { + UARTE0_UART0 => uarte::InterruptHandler; +}); + #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_nrf::init(Default::default()); @@ -14,8 +18,7 @@ async fn main(_spawner: Spawner) { config.parity = uarte::Parity::EXCLUDED; config.baudrate = uarte::Baudrate::BAUD115200; - let irq = interrupt::take!(UARTE0_UART0); - let mut uart = uarte::Uarte::new(p.UARTE0, irq, p.P0_08, p.P0_06, config); + let mut uart = uarte::Uarte::new(p.UARTE0, Irqs, p.P0_08, p.P0_06, config); info!("uarte initialized!"); diff --git a/examples/nrf52840/src/bin/uart_idle.rs b/examples/nrf52840/src/bin/uart_idle.rs new file mode 100644 index 000000000..e1f42fa6c --- /dev/null +++ b/examples/nrf52840/src/bin/uart_idle.rs @@ -0,0 +1,39 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_nrf::peripherals::UARTE0; +use embassy_nrf::{bind_interrupts, uarte}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + UARTE0_UART0 => uarte::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let mut config = uarte::Config::default(); + config.parity = uarte::Parity::EXCLUDED; + config.baudrate = uarte::Baudrate::BAUD115200; + + let uart = uarte::Uarte::new(p.UARTE0, Irqs, p.P0_08, p.P0_06, config); + let (mut tx, mut rx) = uart.split_with_idle(p.TIMER0, p.PPI_CH0, p.PPI_CH1); + + info!("uarte initialized!"); + + // Message must be in SRAM + let mut buf = [0; 8]; + buf.copy_from_slice(b"Hello!\r\n"); + + unwrap!(tx.write(&buf).await); + info!("wrote hello in uart!"); + + loop { + info!("reading..."); + let n = unwrap!(rx.read_until_idle(&mut buf).await); + info!("got {} bytes", n); + } +} diff --git a/examples/nrf/src/bin/uart_split.rs b/examples/nrf52840/src/bin/uart_split.rs similarity index 87% rename from examples/nrf/src/bin/uart_split.rs rename to examples/nrf52840/src/bin/uart_split.rs index 1adaf53fd..9979a1d53 100644 --- a/examples/nrf/src/bin/uart_split.rs +++ b/examples/nrf52840/src/bin/uart_split.rs @@ -6,13 +6,17 @@ use defmt::*; use embassy_executor::Spawner; use embassy_nrf::peripherals::UARTE0; use embassy_nrf::uarte::UarteRx; -use embassy_nrf::{interrupt, uarte}; +use embassy_nrf::{bind_interrupts, uarte}; use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; use embassy_sync::channel::Channel; use {defmt_rtt as _, panic_probe as _}; static CHANNEL: Channel = Channel::new(); +bind_interrupts!(struct Irqs { + UARTE0_UART0 => uarte::InterruptHandler; +}); + #[embassy_executor::main] async fn main(spawner: Spawner) { let p = embassy_nrf::init(Default::default()); @@ -20,8 +24,7 @@ async fn main(spawner: Spawner) { config.parity = uarte::Parity::EXCLUDED; config.baudrate = uarte::Baudrate::BAUD115200; - let irq = interrupt::take!(UARTE0_UART0); - let uart = uarte::Uarte::new(p.UARTE0, irq, p.P0_08, p.P0_06, config); + let uart = uarte::Uarte::new(p.UARTE0, Irqs, p.P0_08, p.P0_06, config); let (mut tx, rx) = uart.split(); info!("uarte initialized!"); diff --git a/examples/nrf52840/src/bin/usb_ethernet.rs b/examples/nrf52840/src/bin/usb_ethernet.rs new file mode 100644 index 000000000..f527c0d7f --- /dev/null +++ b/examples/nrf52840/src/bin/usb_ethernet.rs @@ -0,0 +1,165 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use core::mem; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Stack, StackResources}; +use embassy_nrf::rng::Rng; +use embassy_nrf::usb::vbus_detect::HardwareVbusDetect; +use embassy_nrf::usb::Driver; +use embassy_nrf::{bind_interrupts, pac, peripherals, rng, usb}; +use embassy_usb::class::cdc_ncm::embassy_net::{Device, Runner, State as NetState}; +use embassy_usb::class::cdc_ncm::{CdcNcmClass, State}; +use embassy_usb::{Builder, Config, UsbDevice}; +use embedded_io::asynch::Write; +use static_cell::make_static; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USBD => usb::InterruptHandler; + POWER_CLOCK => usb::vbus_detect::InterruptHandler; + RNG => rng::InterruptHandler; +}); + +type MyDriver = Driver<'static, peripherals::USBD, HardwareVbusDetect>; + +const MTU: usize = 1514; + +#[embassy_executor::task] +async fn usb_task(mut device: UsbDevice<'static, MyDriver>) -> ! { + device.run().await +} + +#[embassy_executor::task] +async fn usb_ncm_task(class: Runner<'static, MyDriver, MTU>) -> ! { + class.run().await +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let clock: pac::CLOCK = unsafe { mem::transmute(()) }; + + info!("Enabling ext hfosc..."); + clock.tasks_hfclkstart.write(|w| unsafe { w.bits(1) }); + while clock.events_hfclkstarted.read().bits() != 1 {} + + // Create the driver, from the HAL. + let driver = Driver::new(p.USBD, Irqs, HardwareVbusDetect::new(Irqs)); + + // Create embassy-usb Config + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-Ethernet example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Required for Windows support. + config.composite_with_iads = true; + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + + // Create embassy-usb DeviceBuilder using the driver and config. + let mut builder = Builder::new( + driver, + config, + &mut make_static!([0; 256])[..], + &mut make_static!([0; 256])[..], + &mut make_static!([0; 256])[..], + &mut make_static!([0; 128])[..], + &mut make_static!([0; 128])[..], + ); + + // Our MAC addr. + let our_mac_addr = [0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC]; + // Host's MAC addr. This is the MAC the host "thinks" its USB-to-ethernet adapter has. + let host_mac_addr = [0x88, 0x88, 0x88, 0x88, 0x88, 0x88]; + + // Create classes on the builder. + let class = CdcNcmClass::new(&mut builder, make_static!(State::new()), host_mac_addr, 64); + + // Build the builder. + let usb = builder.build(); + + unwrap!(spawner.spawn(usb_task(usb))); + + let (runner, device) = class.into_embassy_net_device::(make_static!(NetState::new()), our_mac_addr); + unwrap!(spawner.spawn(usb_ncm_task(runner))); + + let config = embassy_net::Config::dhcpv4(Default::default()); + // let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), + // }); + + // Generate random seed + let mut rng = Rng::new(p.RNG, Irqs); + let mut seed = [0; 8]; + rng.blocking_fill_bytes(&mut seed); + let seed = u64::from_le_bytes(seed); + + // Init network stack + let stack = &*make_static!(Stack::new( + device, + config, + make_static!(StackResources::<2>::new()), + seed + )); + + unwrap!(spawner.spawn(net_task(stack))); + + // And now we can use it! + + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut buf = [0; 4096]; + + loop { + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); + + info!("Listening on TCP:1234..."); + if let Err(e) = socket.accept(1234).await { + warn!("accept error: {:?}", e); + continue; + } + + info!("Received connection from {:?}", socket.remote_endpoint()); + + loop { + let n = match socket.read(&mut buf).await { + Ok(0) => { + warn!("read EOF"); + break; + } + Ok(n) => n, + Err(e) => { + warn!("read error: {:?}", e); + break; + } + }; + + info!("rxd {:02x}", &buf[..n]); + + match socket.write_all(&buf[..n]).await { + Ok(()) => {} + Err(e) => { + warn!("write error: {:?}", e); + break; + } + }; + } + } +} diff --git a/examples/nrf/src/bin/usb_hid_keyboard.rs b/examples/nrf52840/src/bin/usb_hid_keyboard.rs similarity index 82% rename from examples/nrf/src/bin/usb_hid_keyboard.rs rename to examples/nrf52840/src/bin/usb_hid_keyboard.rs index ba2159c72..7ccd2946a 100644 --- a/examples/nrf/src/bin/usb_hid_keyboard.rs +++ b/examples/nrf52840/src/bin/usb_hid_keyboard.rs @@ -1,6 +1,5 @@ #![no_std] #![no_main] -#![feature(generic_associated_types)] #![feature(type_alias_impl_trait)] use core::mem; @@ -8,18 +7,25 @@ use core::sync::atomic::{AtomicBool, Ordering}; use defmt::*; use embassy_executor::Spawner; -use embassy_futures::{select, Either}; +use embassy_futures::join::join; +use embassy_futures::select::{select, Either}; use embassy_nrf::gpio::{Input, Pin, Pull}; -use embassy_nrf::usb::{Driver, PowerUsb}; -use embassy_nrf::{interrupt, pac}; +use embassy_nrf::usb::vbus_detect::HardwareVbusDetect; +use embassy_nrf::usb::Driver; +use embassy_nrf::{bind_interrupts, pac, peripherals, usb}; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::signal::Signal; +use embassy_usb::class::hid::{HidReaderWriter, ReportId, RequestHandler, State}; use embassy_usb::control::OutResponse; -use embassy_usb::{Builder, Config, DeviceStateHandler}; -use embassy_usb_hid::{HidReaderWriter, ReportId, RequestHandler, State}; -use futures::future::join; +use embassy_usb::{Builder, Config, Handler}; use usbd_hid::descriptor::{KeyboardReport, SerializedDescriptor}; use {defmt_rtt as _, panic_probe as _}; +bind_interrupts!(struct Irqs { + USBD => usb::InterruptHandler; + POWER_CLOCK => usb::vbus_detect::InterruptHandler; +}); + static SUSPENDED: AtomicBool = AtomicBool::new(false); #[embassy_executor::main] @@ -32,9 +38,7 @@ async fn main(_spawner: Spawner) { while clock.events_hfclkstarted.read().bits() != 1 {} // Create the driver, from the HAL. - let irq = interrupt::take!(USBD); - let power_irq = interrupt::take!(POWER_CLOCK); - let driver = Driver::new(p.USBD, irq, PowerUsb::new(power_irq)); + let driver = Driver::new(p.USBD, Irqs, HardwareVbusDetect::new(Irqs)); // Create embassy-usb Config let mut config = Config::new(0xc0de, 0xcafe); @@ -50,9 +54,10 @@ async fn main(_spawner: Spawner) { let mut device_descriptor = [0; 256]; let mut config_descriptor = [0; 256]; let mut bos_descriptor = [0; 256]; + let mut msos_descriptor = [0; 256]; let mut control_buf = [0; 64]; let request_handler = MyRequestHandler {}; - let device_state_handler = MyDeviceStateHandler::new(); + let mut device_handler = MyDeviceHandler::new(); let mut state = State::new(); @@ -62,12 +67,14 @@ async fn main(_spawner: Spawner) { &mut device_descriptor, &mut config_descriptor, &mut bos_descriptor, + &mut msos_descriptor, &mut control_buf, - Some(&device_state_handler), ); + builder.handler(&mut device_handler); + // Create classes on the builder. - let config = embassy_usb_hid::Config { + let config = embassy_usb::class::hid::Config { report_descriptor: KeyboardReport::desc(), request_handler: Some(&request_handler), poll_ms: 60, @@ -78,7 +85,7 @@ async fn main(_spawner: Spawner) { // Build the builder. let mut usb = builder.build(); - let remote_wakeup = Signal::new(); + let remote_wakeup: Signal = Signal::new(); // Run the USB device. let usb_fut = async { @@ -164,20 +171,20 @@ impl RequestHandler for MyRequestHandler { } } -struct MyDeviceStateHandler { +struct MyDeviceHandler { configured: AtomicBool, } -impl MyDeviceStateHandler { +impl MyDeviceHandler { fn new() -> Self { - MyDeviceStateHandler { + MyDeviceHandler { configured: AtomicBool::new(false), } } } -impl DeviceStateHandler for MyDeviceStateHandler { - fn enabled(&self, enabled: bool) { +impl Handler for MyDeviceHandler { + fn enabled(&mut self, enabled: bool) { self.configured.store(false, Ordering::Relaxed); SUSPENDED.store(false, Ordering::Release); if enabled { @@ -187,17 +194,17 @@ impl DeviceStateHandler for MyDeviceStateHandler { } } - fn reset(&self) { + fn reset(&mut self) { self.configured.store(false, Ordering::Relaxed); info!("Bus reset, the Vbus current limit is 100mA"); } - fn addressed(&self, addr: u8) { + fn addressed(&mut self, addr: u8) { self.configured.store(false, Ordering::Relaxed); info!("USB address set to: {}", addr); } - fn configured(&self, configured: bool) { + fn configured(&mut self, configured: bool) { self.configured.store(configured, Ordering::Relaxed); if configured { info!("Device configured, it may now draw up to the configured current limit from Vbus.") @@ -206,7 +213,7 @@ impl DeviceStateHandler for MyDeviceStateHandler { } } - fn suspended(&self, suspended: bool) { + fn suspended(&mut self, suspended: bool) { if suspended { info!("Device suspended, the Vbus current limit is 500µA (or 2.5mA for high-power devices with remote wakeup enabled)."); SUSPENDED.store(true, Ordering::Release); diff --git a/examples/nrf/src/bin/usb_hid_mouse.rs b/examples/nrf52840/src/bin/usb_hid_mouse.rs similarity index 84% rename from examples/nrf/src/bin/usb_hid_mouse.rs rename to examples/nrf52840/src/bin/usb_hid_mouse.rs index 7cd2ece17..edf634a5e 100644 --- a/examples/nrf/src/bin/usb_hid_mouse.rs +++ b/examples/nrf52840/src/bin/usb_hid_mouse.rs @@ -1,22 +1,27 @@ #![no_std] #![no_main] -#![feature(generic_associated_types)] #![feature(type_alias_impl_trait)] use core::mem; use defmt::*; use embassy_executor::Spawner; -use embassy_nrf::usb::{Driver, PowerUsb}; -use embassy_nrf::{interrupt, pac}; +use embassy_futures::join::join; +use embassy_nrf::usb::vbus_detect::HardwareVbusDetect; +use embassy_nrf::usb::Driver; +use embassy_nrf::{bind_interrupts, pac, peripherals, usb}; use embassy_time::{Duration, Timer}; +use embassy_usb::class::hid::{HidWriter, ReportId, RequestHandler, State}; use embassy_usb::control::OutResponse; use embassy_usb::{Builder, Config}; -use embassy_usb_hid::{HidWriter, ReportId, RequestHandler, State}; -use futures::future::join; use usbd_hid::descriptor::{MouseReport, SerializedDescriptor}; use {defmt_rtt as _, panic_probe as _}; +bind_interrupts!(struct Irqs { + USBD => usb::InterruptHandler; + POWER_CLOCK => usb::vbus_detect::InterruptHandler; +}); + #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_nrf::init(Default::default()); @@ -27,9 +32,7 @@ async fn main(_spawner: Spawner) { while clock.events_hfclkstarted.read().bits() != 1 {} // Create the driver, from the HAL. - let irq = interrupt::take!(USBD); - let power_irq = interrupt::take!(POWER_CLOCK); - let driver = Driver::new(p.USBD, irq, PowerUsb::new(power_irq)); + let driver = Driver::new(p.USBD, Irqs, HardwareVbusDetect::new(Irqs)); // Create embassy-usb Config let mut config = Config::new(0xc0de, 0xcafe); @@ -44,6 +47,7 @@ async fn main(_spawner: Spawner) { let mut device_descriptor = [0; 256]; let mut config_descriptor = [0; 256]; let mut bos_descriptor = [0; 256]; + let mut msos_descriptor = [0; 256]; let mut control_buf = [0; 64]; let request_handler = MyRequestHandler {}; @@ -55,12 +59,12 @@ async fn main(_spawner: Spawner) { &mut device_descriptor, &mut config_descriptor, &mut bos_descriptor, + &mut msos_descriptor, &mut control_buf, - None, ); // Create classes on the builder. - let config = embassy_usb_hid::Config { + let config = embassy_usb::class::hid::Config { report_descriptor: MouseReport::desc(), request_handler: Some(&request_handler), poll_ms: 60, diff --git a/examples/nrf/src/bin/usb_serial.rs b/examples/nrf52840/src/bin/usb_serial.rs similarity index 81% rename from examples/nrf/src/bin/usb_serial.rs rename to examples/nrf52840/src/bin/usb_serial.rs index a68edb329..dc95cde84 100644 --- a/examples/nrf/src/bin/usb_serial.rs +++ b/examples/nrf52840/src/bin/usb_serial.rs @@ -1,20 +1,25 @@ #![no_std] #![no_main] -#![feature(generic_associated_types)] #![feature(type_alias_impl_trait)] use core::mem; use defmt::{info, panic}; use embassy_executor::Spawner; -use embassy_nrf::usb::{Driver, Instance, PowerUsb, UsbSupply}; -use embassy_nrf::{interrupt, pac}; +use embassy_futures::join::join; +use embassy_nrf::usb::vbus_detect::{HardwareVbusDetect, VbusDetect}; +use embassy_nrf::usb::{Driver, Instance}; +use embassy_nrf::{bind_interrupts, pac, peripherals, usb}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; use embassy_usb::driver::EndpointError; use embassy_usb::{Builder, Config}; -use embassy_usb_serial::{CdcAcmClass, State}; -use futures::future::join; use {defmt_rtt as _, panic_probe as _}; +bind_interrupts!(struct Irqs { + USBD => usb::InterruptHandler; + POWER_CLOCK => usb::vbus_detect::InterruptHandler; +}); + #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_nrf::init(Default::default()); @@ -25,9 +30,7 @@ async fn main(_spawner: Spawner) { while clock.events_hfclkstarted.read().bits() != 1 {} // Create the driver, from the HAL. - let irq = interrupt::take!(USBD); - let power_irq = interrupt::take!(POWER_CLOCK); - let driver = Driver::new(p.USBD, irq, PowerUsb::new(power_irq)); + let driver = Driver::new(p.USBD, Irqs, HardwareVbusDetect::new(Irqs)); // Create embassy-usb Config let mut config = Config::new(0xc0de, 0xcafe); @@ -37,7 +40,7 @@ async fn main(_spawner: Spawner) { config.max_power = 100; config.max_packet_size_0 = 64; - // Required for windows compatiblity. + // Required for windows compatibility. // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help config.device_class = 0xEF; config.device_sub_class = 0x02; @@ -49,6 +52,7 @@ async fn main(_spawner: Spawner) { let mut device_descriptor = [0; 256]; let mut config_descriptor = [0; 256]; let mut bos_descriptor = [0; 256]; + let mut msos_descriptor = [0; 256]; let mut control_buf = [0; 64]; let mut state = State::new(); @@ -59,8 +63,8 @@ async fn main(_spawner: Spawner) { &mut device_descriptor, &mut config_descriptor, &mut bos_descriptor, + &mut msos_descriptor, &mut control_buf, - None, ); // Create classes on the builder. @@ -98,7 +102,7 @@ impl From for Disconnected { } } -async fn echo<'d, T: Instance + 'd, P: UsbSupply + 'd>( +async fn echo<'d, T: Instance + 'd, P: VbusDetect + 'd>( class: &mut CdcAcmClass<'d, Driver<'d, T, P>>, ) -> Result<(), Disconnected> { let mut buf = [0; 64]; diff --git a/examples/nrf/src/bin/usb_serial_multitask.rs b/examples/nrf52840/src/bin/usb_serial_multitask.rs similarity index 67% rename from examples/nrf/src/bin/usb_serial_multitask.rs rename to examples/nrf52840/src/bin/usb_serial_multitask.rs index d62d7e520..cd4392903 100644 --- a/examples/nrf/src/bin/usb_serial_multitask.rs +++ b/examples/nrf52840/src/bin/usb_serial_multitask.rs @@ -1,21 +1,26 @@ #![no_std] #![no_main] -#![feature(generic_associated_types)] #![feature(type_alias_impl_trait)] use core::mem; use defmt::{info, panic, unwrap}; use embassy_executor::Spawner; -use embassy_nrf::usb::{Driver, PowerUsb}; -use embassy_nrf::{interrupt, pac, peripherals}; +use embassy_nrf::usb::vbus_detect::HardwareVbusDetect; +use embassy_nrf::usb::Driver; +use embassy_nrf::{bind_interrupts, pac, peripherals, usb}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; use embassy_usb::driver::EndpointError; use embassy_usb::{Builder, Config, UsbDevice}; -use embassy_usb_serial::{CdcAcmClass, State}; -use static_cell::StaticCell; +use static_cell::make_static; use {defmt_rtt as _, panic_probe as _}; -type MyDriver = Driver<'static, peripherals::USBD, PowerUsb>; +bind_interrupts!(struct Irqs { + USBD => usb::InterruptHandler; + POWER_CLOCK => usb::vbus_detect::InterruptHandler; +}); + +type MyDriver = Driver<'static, peripherals::USBD, HardwareVbusDetect>; #[embassy_executor::task] async fn usb_task(mut device: UsbDevice<'static, MyDriver>) { @@ -40,10 +45,9 @@ async fn main(spawner: Spawner) { info!("Enabling ext hfosc..."); clock.tasks_hfclkstart.write(|w| unsafe { w.bits(1) }); while clock.events_hfclkstarted.read().bits() != 1 {} + // Create the driver, from the HAL. - let irq = interrupt::take!(USBD); - let power_irq = interrupt::take!(POWER_CLOCK); - let driver = Driver::new(p.USBD, irq, PowerUsb::new(power_irq)); + let driver = Driver::new(p.USBD, Irqs, HardwareVbusDetect::new(Irqs)); // Create embassy-usb Config let mut config = Config::new(0xc0de, 0xcafe); @@ -53,42 +57,28 @@ async fn main(spawner: Spawner) { config.max_power = 100; config.max_packet_size_0 = 64; - // Required for windows compatiblity. + // Required for windows compatibility. // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help config.device_class = 0xEF; config.device_sub_class = 0x02; config.device_protocol = 0x01; config.composite_with_iads = true; - struct Resources { - device_descriptor: [u8; 256], - config_descriptor: [u8; 256], - bos_descriptor: [u8; 256], - control_buf: [u8; 64], - serial_state: State<'static>, - } - static RESOURCES: StaticCell = StaticCell::new(); - let res = RESOURCES.init(Resources { - device_descriptor: [0; 256], - config_descriptor: [0; 256], - bos_descriptor: [0; 256], - control_buf: [0; 64], - serial_state: State::new(), - }); + let state = make_static!(State::new()); // Create embassy-usb DeviceBuilder using the driver and config. let mut builder = Builder::new( driver, config, - &mut res.device_descriptor, - &mut res.config_descriptor, - &mut res.bos_descriptor, - &mut res.control_buf, - None, + &mut make_static!([0; 256])[..], + &mut make_static!([0; 256])[..], + &mut make_static!([0; 256])[..], + &mut make_static!([0; 128])[..], + &mut make_static!([0; 128])[..], ); // Create classes on the builder. - let class = CdcAcmClass::new(&mut builder, &mut res.serial_state, 64); + let class = CdcAcmClass::new(&mut builder, state, 64); // Build the builder. let usb = builder.build(); diff --git a/examples/nrf52840/src/bin/usb_serial_winusb.rs b/examples/nrf52840/src/bin/usb_serial_winusb.rs new file mode 100644 index 000000000..1d39d3841 --- /dev/null +++ b/examples/nrf52840/src/bin/usb_serial_winusb.rs @@ -0,0 +1,134 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use core::mem; + +use defmt::{info, panic}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_nrf::usb::vbus_detect::{HardwareVbusDetect, VbusDetect}; +use embassy_nrf::usb::{Driver, Instance}; +use embassy_nrf::{bind_interrupts, pac, peripherals, usb}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::msos::{self, windows_version}; +use embassy_usb::types::InterfaceNumber; +use embassy_usb::{Builder, Config}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USBD => usb::InterruptHandler; + POWER_CLOCK => usb::vbus_detect::InterruptHandler; +}); + +// This is a randomly generated GUID to allow clients on Windows to find our device +const DEVICE_INTERFACE_GUIDS: &[&str] = &["{EAA9A5DC-30BA-44BC-9232-606CDC875321}"]; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let clock: pac::CLOCK = unsafe { mem::transmute(()) }; + + info!("Enabling ext hfosc..."); + clock.tasks_hfclkstart.write(|w| unsafe { w.bits(1) }); + while clock.events_hfclkstarted.read().bits() != 1 {} + + // Create the driver, from the HAL. + let driver = Driver::new(p.USBD, Irqs, HardwareVbusDetect::new(Irqs)); + + // Create embassy-usb Config + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut device_descriptor = [0; 256]; + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut msos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut device_descriptor, + &mut config_descriptor, + &mut bos_descriptor, + &mut msos_descriptor, + &mut control_buf, + ); + + builder.msos_descriptor(windows_version::WIN8_1, 2); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Since we want to create MS OS feature descriptors that apply to a function that has already been added to the + // builder, need to get the MsOsDescriptorWriter from the builder and manually add those descriptors. + // Inside a class constructor, you would just need to call `FunctionBuilder::msos_feature` instead. + let msos_writer = builder.msos_writer(); + msos_writer.configuration(0); + msos_writer.function(InterfaceNumber(0)); + msos_writer.function_feature(msos::CompatibleIdFeatureDescriptor::new("WINUSB", "")); + msos_writer.function_feature(msos::RegistryPropertyFeatureDescriptor::new( + "DeviceInterfaceGUIDs", + msos::PropertyData::RegMultiSz(DEVICE_INTERFACE_GUIDS), + )); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd, P: VbusDetect + 'd>( + class: &mut CdcAcmClass<'d, Driver<'d, T, P>>, +) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} diff --git a/examples/nrf/src/bin/wdt.rs b/examples/nrf52840/src/bin/wdt.rs similarity index 93% rename from examples/nrf/src/bin/wdt.rs rename to examples/nrf52840/src/bin/wdt.rs index b0b9c3b81..058746518 100644 --- a/examples/nrf/src/bin/wdt.rs +++ b/examples/nrf52840/src/bin/wdt.rs @@ -16,7 +16,7 @@ async fn main(_spawner: Spawner) { let mut config = Config::default(); config.timeout_ticks = 32768 * 3; // 3 seconds - // This is needed for `probe-run` to be able to catch the panic message + // This is needed for `probe-rs run` to be able to catch the panic message // in the WDT interrupt. The core resets 2 ticks after firing the interrupt. config.run_during_debug_halt = false; diff --git a/examples/nrf52840/src/bin/wifi_esp_hosted.rs b/examples/nrf52840/src/bin/wifi_esp_hosted.rs new file mode 100644 index 000000000..112e41bcd --- /dev/null +++ b/examples/nrf52840/src/bin/wifi_esp_hosted.rs @@ -0,0 +1,143 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::{info, unwrap, warn}; +use embassy_executor::Spawner; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Stack, StackResources}; +use embassy_nrf::gpio::{AnyPin, Input, Level, Output, OutputDrive, Pin, Pull}; +use embassy_nrf::rng::Rng; +use embassy_nrf::spim::{self, Spim}; +use embassy_nrf::{bind_interrupts, peripherals}; +use embassy_time::Delay; +use embedded_hal_async::spi::ExclusiveDevice; +use embedded_io::asynch::Write; +use static_cell::make_static; +use {defmt_rtt as _, embassy_net_esp_hosted as hosted, panic_probe as _}; + +const WIFI_NETWORK: &str = "EmbassyTest"; +const WIFI_PASSWORD: &str = "V8YxhKt5CdIAJFud"; + +bind_interrupts!(struct Irqs { + SPIM3 => spim::InterruptHandler; + RNG => embassy_nrf::rng::InterruptHandler; +}); + +#[embassy_executor::task] +async fn wifi_task( + runner: hosted::Runner< + 'static, + ExclusiveDevice, Output<'static, peripherals::P0_31>, Delay>, + Input<'static, AnyPin>, + Output<'static, peripherals::P1_05>, + >, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + info!("Hello World!"); + + let p = embassy_nrf::init(Default::default()); + + let miso = p.P0_28; + let sck = p.P0_29; + let mosi = p.P0_30; + let cs = Output::new(p.P0_31, Level::High, OutputDrive::HighDrive); + let handshake = Input::new(p.P1_01.degrade(), Pull::Up); + let ready = Input::new(p.P1_04.degrade(), Pull::None); + let reset = Output::new(p.P1_05, Level::Low, OutputDrive::Standard); + + let mut config = spim::Config::default(); + config.frequency = spim::Frequency::M32; + config.mode = spim::MODE_2; // !!! + let spi = spim::Spim::new(p.SPI3, Irqs, sck, miso, mosi, config); + let spi = ExclusiveDevice::new(spi, cs, Delay); + + let (device, mut control, runner) = embassy_net_esp_hosted::new( + make_static!(embassy_net_esp_hosted::State::new()), + spi, + handshake, + ready, + reset, + ) + .await; + + unwrap!(spawner.spawn(wifi_task(runner))); + + control.init().await; + control.join(WIFI_NETWORK, WIFI_PASSWORD).await; + + let config = embassy_net::Config::dhcpv4(Default::default()); + // let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), + // }); + + // Generate random seed + let mut rng = Rng::new(p.RNG, Irqs); + let mut seed = [0; 8]; + rng.blocking_fill_bytes(&mut seed); + let seed = u64::from_le_bytes(seed); + + // Init network stack + let stack = &*make_static!(Stack::new( + device, + config, + make_static!(StackResources::<2>::new()), + seed + )); + + unwrap!(spawner.spawn(net_task(stack))); + + // And now we can use it! + + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut buf = [0; 4096]; + + loop { + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); + + info!("Listening on TCP:1234..."); + if let Err(e) = socket.accept(1234).await { + warn!("accept error: {:?}", e); + continue; + } + + info!("Received connection from {:?}", socket.remote_endpoint()); + + loop { + let n = match socket.read(&mut buf).await { + Ok(0) => { + warn!("read EOF"); + break; + } + Ok(n) => n, + Err(e) => { + warn!("read error: {:?}", e); + break; + } + }; + + info!("rxd {:02x}", &buf[..n]); + + match socket.write_all(&buf[..n]).await { + Ok(()) => {} + Err(e) => { + warn!("write error: {:?}", e); + break; + } + }; + } + } +} diff --git a/examples/nrf5340/.cargo/config.toml b/examples/nrf5340/.cargo/config.toml new file mode 100644 index 000000000..4c3cf3d32 --- /dev/null +++ b/examples/nrf5340/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace nRF5340_xxAA with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip nRF5340_xxAA" + +[build] +target = "thumbv8m.main-none-eabihf" + +[env] +DEFMT_LOG = "trace" diff --git a/examples/nrf5340/Cargo.toml b/examples/nrf5340/Cargo.toml new file mode 100644 index 000000000..f1d45f336 --- /dev/null +++ b/examples/nrf5340/Cargo.toml @@ -0,0 +1,55 @@ +[package] +edition = "2021" +name = "embassy-nrf5340-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = [ + "defmt", +] } +embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", + "nightly", + "defmt", + "integrated-timers", +] } +embassy-time = { version = "0.1.2", path = "../../embassy-time", features = [ + "defmt", + "defmt-timestamp-uptime", +] } +embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = [ + "nightly", + "unstable-traits", + "defmt", + "nrf5340-app-s", + "time-driver-rtc1", + "gpiote", + "unstable-pac", +] } +embassy-net = { version = "0.1.0", path = "../../embassy-net", features = [ + "nightly", + "defmt", + "tcp", + "dhcpv4", + "medium-ethernet", +] } +embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = [ + "defmt", +] } +embedded-io = { version = "0.4.0", features = [ "async" ]} + +defmt = "0.3" +defmt-rtt = "0.4" + +static_cell = { version = "1.1", features = ["nightly"]} +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +panic-probe = { version = "0.3", features = ["print-defmt"] } +futures = { version = "0.3.17", default-features = false, features = [ + "async-await", +] } +rand = { version = "0.8.4", default-features = false } +embedded-storage = "0.3.0" +usbd-hid = "0.6.0" +serde = { version = "1.0.136", default-features = false } diff --git a/examples/nrf5340/build.rs b/examples/nrf5340/build.rs new file mode 100644 index 000000000..30691aa97 --- /dev/null +++ b/examples/nrf5340/build.rs @@ -0,0 +1,35 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/examples/nrf5340/memory.x b/examples/nrf5340/memory.x new file mode 100644 index 000000000..a122dc24a --- /dev/null +++ b/examples/nrf5340/memory.x @@ -0,0 +1,7 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + /* These values correspond to the NRF5340 */ + FLASH : ORIGIN = 0x00000000, LENGTH = 1024K + RAM : ORIGIN = 0x20000000, LENGTH = 256K +} diff --git a/examples/nrf5340/src/bin/blinky.rs b/examples/nrf5340/src/bin/blinky.rs new file mode 100644 index 000000000..3422cedf0 --- /dev/null +++ b/examples/nrf5340/src/bin/blinky.rs @@ -0,0 +1,21 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use embassy_executor::Spawner; +use embassy_nrf::gpio::{Level, Output, OutputDrive}; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let mut led = Output::new(p.P0_28, Level::Low, OutputDrive::Standard); + + loop { + led.set_high(); + Timer::after(Duration::from_millis(300)).await; + led.set_low(); + Timer::after(Duration::from_millis(300)).await; + } +} diff --git a/examples/nrf5340/src/bin/gpiote_channel.rs b/examples/nrf5340/src/bin/gpiote_channel.rs new file mode 100644 index 000000000..ceab1194a --- /dev/null +++ b/examples/nrf5340/src/bin/gpiote_channel.rs @@ -0,0 +1,66 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_nrf::gpio::{Input, Pull}; +use embassy_nrf::gpiote::{InputChannel, InputChannelPolarity}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + info!("Starting!"); + + let ch1 = InputChannel::new( + p.GPIOTE_CH0, + Input::new(p.P0_23, Pull::Up), + InputChannelPolarity::HiToLo, + ); + let ch2 = InputChannel::new( + p.GPIOTE_CH1, + Input::new(p.P0_24, Pull::Up), + InputChannelPolarity::LoToHi, + ); + let ch3 = InputChannel::new( + p.GPIOTE_CH2, + Input::new(p.P0_08, Pull::Up), + InputChannelPolarity::Toggle, + ); + let ch4 = InputChannel::new( + p.GPIOTE_CH3, + Input::new(p.P0_09, Pull::Up), + InputChannelPolarity::Toggle, + ); + + let button1 = async { + loop { + ch1.wait().await; + info!("Button 1 pressed") + } + }; + + let button2 = async { + loop { + ch2.wait().await; + info!("Button 2 released") + } + }; + + let button3 = async { + loop { + ch3.wait().await; + info!("Button 3 toggled") + } + }; + + let button4 = async { + loop { + ch4.wait().await; + info!("Button 4 toggled") + } + }; + + futures::join!(button1, button2, button3, button4); +} diff --git a/examples/nrf/src/bin/uart_idle.rs b/examples/nrf5340/src/bin/uart.rs similarity index 64% rename from examples/nrf/src/bin/uart_idle.rs rename to examples/nrf5340/src/bin/uart.rs index 09ec624c0..d68539702 100644 --- a/examples/nrf/src/bin/uart_idle.rs +++ b/examples/nrf5340/src/bin/uart.rs @@ -4,9 +4,14 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_nrf::{interrupt, uarte}; +use embassy_nrf::peripherals::SERIAL0; +use embassy_nrf::{bind_interrupts, uarte}; use {defmt_rtt as _, panic_probe as _}; +bind_interrupts!(struct Irqs { + SERIAL0 => uarte::InterruptHandler; +}); + #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_nrf::init(Default::default()); @@ -14,8 +19,7 @@ async fn main(_spawner: Spawner) { config.parity = uarte::Parity::EXCLUDED; config.baudrate = uarte::Baudrate::BAUD115200; - let irq = interrupt::take!(UARTE0_UART0); - let mut uart = uarte::UarteWithIdle::new(p.UARTE0, p.TIMER0, p.PPI_CH0, p.PPI_CH1, irq, p.P0_08, p.P0_06, config); + let mut uart = uarte::Uarte::new(p.SERIAL0, Irqs, p.P1_00, p.P1_01, config); info!("uarte initialized!"); @@ -28,7 +32,8 @@ async fn main(_spawner: Spawner) { loop { info!("reading..."); - let n = unwrap!(uart.read_until_idle(&mut buf).await); - info!("got {} bytes", n); + unwrap!(uart.read(&mut buf).await); + info!("writing..."); + unwrap!(uart.write(&buf).await); } } diff --git a/examples/rp/.cargo/config.toml b/examples/rp/.cargo/config.toml index 3d6051389..3d7d61740 100644 --- a/examples/rp/.cargo/config.toml +++ b/examples/rp/.cargo/config.toml @@ -1,8 +1,8 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -runner = "probe-run --chip RP2040" +runner = "probe-rs run --chip RP2040" [build] target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+ [env] -DEFMT_LOG = "trace" +DEFMT_LOG = "debug" diff --git a/examples/rp/Cargo.toml b/examples/rp/Cargo.toml index d804a660b..c812cb3ee 100644 --- a/examples/rp/Cargo.toml +++ b/examples/rp/Cargo.toml @@ -2,18 +2,34 @@ edition = "2021" name = "embassy-rp-examples" version = "0.1.0" +license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } -embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } -embassy-rp = { version = "0.1.0", path = "../../embassy-rp", features = ["defmt", "unstable-traits", "nightly", "unstable-pac"] } +embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal", features = ["defmt"] } +embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["nightly", "unstable-traits", "defmt", "defmt-timestamp-uptime"] } +embassy-rp = { version = "0.1.0", path = "../../embassy-rp", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver", "critical-section-impl"] } +embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "udp", "dhcpv4", "medium-ethernet"] } +embassy-net-w5500 = { version = "0.1.0", path = "../../embassy-net-w5500", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +embassy-usb-logger = { version = "0.1.0", path = "../../embassy-usb-logger" } +embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["time", "defmt"] } +lora-phy = { version = "1" } +lorawan-device = { version = "0.10.0", default-features = false, features = ["async", "external-lora-phy"] } +lorawan = { version = "0.7.3", default-features = false, features = ["default-crypto"] } +cyw43 = { path = "../../cyw43", features = ["defmt", "firmware-logs"] } +cyw43-pio = { path = "../../cyw43-pio", features = ["defmt", "overclock"] } defmt = "0.3" -defmt-rtt = "0.3" +defmt-rtt = "0.4" +fixed = "1.23.1" +fixed-macro = "1.2" -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +#cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm"] } cortex-m-rt = "0.7.0" panic-probe = { version = "0.3", features = ["print-defmt"] } futures = { version = "0.3.17", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] } @@ -22,6 +38,22 @@ embedded-graphics = "0.7.1" st7789 = "0.6.1" display-interface = "0.4.1" byte-slice-cast = { version = "1.2.0", default-features = false } +smart-leds = "0.3.0" +heapless = "0.7.15" +usbd-hid = "0.6.1" -embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.8" } -embedded-hal-async = { version = "0.1.0-alpha.1" } +embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11" } +embedded-hal-async = "0.2.0-alpha.2" +embedded-io = { version = "0.4.0", features = ["async", "defmt"] } +embedded-storage = { version = "0.3" } +static_cell = { version = "1.1", features = ["nightly"]} +log = "0.4" +pio-proc = "0.2" +pio = "0.2.1" +rand = { version = "0.8.5", default-features = false } + +[profile.release] +debug = true + +[patch.crates-io] +lora-phy = { git = "https://github.com/embassy-rs/lora-phy", rev = "ad289428fd44b02788e2fa2116445cc8f640a265" } diff --git a/examples/rp/src/bin/adc.rs b/examples/rp/src/bin/adc.rs new file mode 100644 index 000000000..81a8b8340 --- /dev/null +++ b/examples/rp/src/bin/adc.rs @@ -0,0 +1,48 @@ +//! This example test the ADC (Analog to Digital Conversion) of the RS2040 pin 26, 27 and 28. +//! It also reads the temperature sensor in the chip. + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::adc::{Adc, Config, InterruptHandler, Pin}; +use embassy_rp::bind_interrupts; +use embassy_rp::gpio::Pull; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + ADC_IRQ_FIFO => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut adc = Adc::new(p.ADC, Irqs, Config::default()); + + let mut p26 = Pin::new(p.PIN_26, Pull::None); + let mut p27 = Pin::new(p.PIN_27, Pull::None); + let mut p28 = Pin::new(p.PIN_28, Pull::None); + + loop { + let level = adc.read(&mut p26).await.unwrap(); + info!("Pin 26 ADC: {}", level); + let level = adc.read(&mut p27).await.unwrap(); + info!("Pin 27 ADC: {}", level); + let level = adc.read(&mut p28).await.unwrap(); + info!("Pin 28 ADC: {}", level); + let temp = adc.read_temperature().await.unwrap(); + info!("Temp: {} degrees", convert_to_celsius(temp)); + Timer::after(Duration::from_secs(1)).await; + } +} + +fn convert_to_celsius(raw_temp: u16) -> f32 { + // According to chapter 4.9.5. Temperature Sensor in RP2040 datasheet + let temp = 27.0 - (raw_temp as f32 * 3.3 / 4096.0 - 0.706) / 0.001721; + let sign = if temp < 0.0 { -1.0 } else { 1.0 }; + let rounded_temp_x10: i16 = ((temp * 10.0) + 0.5 * sign) as i16; + (rounded_temp_x10 as f32) / 10.0 +} diff --git a/examples/rp/src/bin/blinky.rs b/examples/rp/src/bin/blinky.rs index 7aa36a19f..295b000f3 100644 --- a/examples/rp/src/bin/blinky.rs +++ b/examples/rp/src/bin/blinky.rs @@ -1,3 +1,7 @@ +//! This example test the RP Pico on board LED. +//! +//! It does not work with the RP Pico W board. See wifi_blinky.rs. + #![no_std] #![no_main] #![feature(type_alias_impl_trait)] diff --git a/examples/rp/src/bin/button.rs b/examples/rp/src/bin/button.rs index c5422c616..d7aa89410 100644 --- a/examples/rp/src/bin/button.rs +++ b/examples/rp/src/bin/button.rs @@ -1,3 +1,7 @@ +//! This example uses the RP Pico on board LED to test input pin 28. This is not the button on the board. +//! +//! It does not work with the RP Pico W board. Use wifi_blinky.rs and add input pin. + #![no_std] #![no_main] #![feature(type_alias_impl_trait)] @@ -9,9 +13,12 @@ use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); - let button = Input::new(p.PIN_28, Pull::Up); let mut led = Output::new(p.PIN_25, Level::Low); + // Use PIN_28, Pin34 on J0 for RP Pico, as a input. + // You need to add your own button. + let button = Input::new(p.PIN_28, Pull::Up); + loop { if button.is_high() { led.set_high(); diff --git a/examples/rp/src/bin/ethernet_w5500_multisocket.rs b/examples/rp/src/bin/ethernet_w5500_multisocket.rs new file mode 100644 index 000000000..e81da177b --- /dev/null +++ b/examples/rp/src/bin/ethernet_w5500_multisocket.rs @@ -0,0 +1,136 @@ +//! This example shows how you can allow multiple simultaneous TCP connections, by having multiple sockets listening on the same port. +//! +//! Example written for the [`WIZnet W5500-EVB-Pico`](https://www.wiznet.io/product-item/w5500-evb-pico/) board. + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::yield_now; +use embassy_net::{Stack, StackResources}; +use embassy_net_w5500::*; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Input, Level, Output, Pull}; +use embassy_rp::peripherals::{PIN_17, PIN_20, PIN_21, SPI0}; +use embassy_rp::spi::{Async, Config as SpiConfig, Spi}; +use embassy_time::{Delay, Duration}; +use embedded_hal_async::spi::ExclusiveDevice; +use embedded_io::asynch::Write; +use rand::RngCore; +use static_cell::make_static; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::task] +async fn ethernet_task( + runner: Runner< + 'static, + ExclusiveDevice, Output<'static, PIN_17>, Delay>, + Input<'static, PIN_21>, + Output<'static, PIN_20>, + >, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut rng = RoscRng; + + let mut spi_cfg = SpiConfig::default(); + spi_cfg.frequency = 50_000_000; + let (miso, mosi, clk) = (p.PIN_16, p.PIN_19, p.PIN_18); + let spi = Spi::new(p.SPI0, clk, mosi, miso, p.DMA_CH0, p.DMA_CH1, spi_cfg); + let cs = Output::new(p.PIN_17, Level::High); + let w5500_int = Input::new(p.PIN_21, Pull::Up); + let w5500_reset = Output::new(p.PIN_20, Level::High); + + let mac_addr = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00]; + let state = make_static!(State::<8, 8>::new()); + let (device, runner) = embassy_net_w5500::new( + mac_addr, + state, + ExclusiveDevice::new(spi, cs, Delay), + w5500_int, + w5500_reset, + ) + .await; + unwrap!(spawner.spawn(ethernet_task(runner))); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + let stack = &*make_static!(Stack::new( + device, + embassy_net::Config::dhcpv4(Default::default()), + make_static!(StackResources::<3>::new()), + seed + )); + + // Launch network task + unwrap!(spawner.spawn(net_task(&stack))); + + info!("Waiting for DHCP..."); + let cfg = wait_for_config(stack).await; + let local_addr = cfg.address.address(); + info!("IP address: {:?}", local_addr); + + // Create two sockets listening to the same port, to handle simultaneous connections + unwrap!(spawner.spawn(listen_task(&stack, 0, 1234))); + unwrap!(spawner.spawn(listen_task(&stack, 1, 1234))); +} + +#[embassy_executor::task(pool_size = 2)] +async fn listen_task(stack: &'static Stack>, id: u8, port: u16) { + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut buf = [0; 4096]; + loop { + let mut socket = embassy_net::tcp::TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + + info!("SOCKET {}: Listening on TCP:{}...", id, port); + if let Err(e) = socket.accept(port).await { + warn!("accept error: {:?}", e); + continue; + } + info!("SOCKET {}: Received connection from {:?}", id, socket.remote_endpoint()); + + loop { + let n = match socket.read(&mut buf).await { + Ok(0) => { + warn!("read EOF"); + break; + } + Ok(n) => n, + Err(e) => { + warn!("SOCKET {}: {:?}", id, e); + break; + } + }; + info!("SOCKET {}: rxd {}", id, core::str::from_utf8(&buf[..n]).unwrap()); + + if let Err(e) = socket.write_all(&buf[..n]).await { + warn!("write error: {:?}", e); + break; + } + } + } +} + +async fn wait_for_config(stack: &'static Stack>) -> embassy_net::StaticConfigV4 { + loop { + if let Some(config) = stack.config_v4() { + return config.clone(); + } + yield_now().await; + } +} diff --git a/examples/rp/src/bin/ethernet_w5500_tcp_client.rs b/examples/rp/src/bin/ethernet_w5500_tcp_client.rs new file mode 100644 index 000000000..9dd7ae973 --- /dev/null +++ b/examples/rp/src/bin/ethernet_w5500_tcp_client.rs @@ -0,0 +1,124 @@ +//! This example implements a TCP client that attempts to connect to a host on port 1234 and send it some data once per second. +//! +//! Example written for the [`WIZnet W5500-EVB-Pico`](https://www.wiznet.io/product-item/w5500-evb-pico/) board. + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use core::str::FromStr; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::yield_now; +use embassy_net::{Stack, StackResources}; +use embassy_net_w5500::*; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Input, Level, Output, Pull}; +use embassy_rp::peripherals::{PIN_17, PIN_20, PIN_21, SPI0}; +use embassy_rp::spi::{Async, Config as SpiConfig, Spi}; +use embassy_time::{Delay, Duration, Timer}; +use embedded_hal_async::spi::ExclusiveDevice; +use embedded_io::asynch::Write; +use rand::RngCore; +use static_cell::make_static; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::task] +async fn ethernet_task( + runner: Runner< + 'static, + ExclusiveDevice, Output<'static, PIN_17>, Delay>, + Input<'static, PIN_21>, + Output<'static, PIN_20>, + >, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut rng = RoscRng; + let mut led = Output::new(p.PIN_25, Level::Low); + + let mut spi_cfg = SpiConfig::default(); + spi_cfg.frequency = 50_000_000; + let (miso, mosi, clk) = (p.PIN_16, p.PIN_19, p.PIN_18); + let spi = Spi::new(p.SPI0, clk, mosi, miso, p.DMA_CH0, p.DMA_CH1, spi_cfg); + let cs = Output::new(p.PIN_17, Level::High); + let w5500_int = Input::new(p.PIN_21, Pull::Up); + let w5500_reset = Output::new(p.PIN_20, Level::High); + + let mac_addr = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00]; + let state = make_static!(State::<8, 8>::new()); + let (device, runner) = embassy_net_w5500::new( + mac_addr, + state, + ExclusiveDevice::new(spi, cs, Delay), + w5500_int, + w5500_reset, + ) + .await; + unwrap!(spawner.spawn(ethernet_task(runner))); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + let stack = &*make_static!(Stack::new( + device, + embassy_net::Config::dhcpv4(Default::default()), + make_static!(StackResources::<2>::new()), + seed + )); + + // Launch network task + unwrap!(spawner.spawn(net_task(&stack))); + + info!("Waiting for DHCP..."); + let cfg = wait_for_config(stack).await; + let local_addr = cfg.address.address(); + info!("IP address: {:?}", local_addr); + + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + loop { + let mut socket = embassy_net::tcp::TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + + led.set_low(); + info!("Connecting..."); + let host_addr = embassy_net::Ipv4Address::from_str("192.168.1.110").unwrap(); + if let Err(e) = socket.connect((host_addr, 1234)).await { + warn!("connect error: {:?}", e); + continue; + } + info!("Connected to {:?}", socket.remote_endpoint()); + led.set_high(); + + let msg = b"Hello world!\n"; + loop { + if let Err(e) = socket.write_all(msg).await { + warn!("write error: {:?}", e); + break; + } + info!("txd: {}", core::str::from_utf8(msg).unwrap()); + Timer::after(Duration::from_secs(1)).await; + } + } +} + +async fn wait_for_config(stack: &'static Stack>) -> embassy_net::StaticConfigV4 { + loop { + if let Some(config) = stack.config_v4() { + return config.clone(); + } + yield_now().await; + } +} diff --git a/examples/rp/src/bin/ethernet_w5500_tcp_server.rs b/examples/rp/src/bin/ethernet_w5500_tcp_server.rs new file mode 100644 index 000000000..db21c2b6f --- /dev/null +++ b/examples/rp/src/bin/ethernet_w5500_tcp_server.rs @@ -0,0 +1,132 @@ +//! This example implements a TCP echo server on port 1234 and using DHCP. +//! Send it some data, you should see it echoed back and printed in the console. +//! +//! Example written for the [`WIZnet W5500-EVB-Pico`](https://www.wiznet.io/product-item/w5500-evb-pico/) board. + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::yield_now; +use embassy_net::{Stack, StackResources}; +use embassy_net_w5500::*; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Input, Level, Output, Pull}; +use embassy_rp::peripherals::{PIN_17, PIN_20, PIN_21, SPI0}; +use embassy_rp::spi::{Async, Config as SpiConfig, Spi}; +use embassy_time::{Delay, Duration}; +use embedded_hal_async::spi::ExclusiveDevice; +use embedded_io::asynch::Write; +use rand::RngCore; +use static_cell::make_static; +use {defmt_rtt as _, panic_probe as _}; +#[embassy_executor::task] +async fn ethernet_task( + runner: Runner< + 'static, + ExclusiveDevice, Output<'static, PIN_17>, Delay>, + Input<'static, PIN_21>, + Output<'static, PIN_20>, + >, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut rng = RoscRng; + let mut led = Output::new(p.PIN_25, Level::Low); + + let mut spi_cfg = SpiConfig::default(); + spi_cfg.frequency = 50_000_000; + let (miso, mosi, clk) = (p.PIN_16, p.PIN_19, p.PIN_18); + let spi = Spi::new(p.SPI0, clk, mosi, miso, p.DMA_CH0, p.DMA_CH1, spi_cfg); + let cs = Output::new(p.PIN_17, Level::High); + let w5500_int = Input::new(p.PIN_21, Pull::Up); + let w5500_reset = Output::new(p.PIN_20, Level::High); + + let mac_addr = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00]; + let state = make_static!(State::<8, 8>::new()); + let (device, runner) = embassy_net_w5500::new( + mac_addr, + state, + ExclusiveDevice::new(spi, cs, Delay), + w5500_int, + w5500_reset, + ) + .await; + unwrap!(spawner.spawn(ethernet_task(runner))); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + let stack = &*make_static!(Stack::new( + device, + embassy_net::Config::dhcpv4(Default::default()), + make_static!(StackResources::<2>::new()), + seed + )); + + // Launch network task + unwrap!(spawner.spawn(net_task(&stack))); + + info!("Waiting for DHCP..."); + let cfg = wait_for_config(stack).await; + let local_addr = cfg.address.address(); + info!("IP address: {:?}", local_addr); + + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut buf = [0; 4096]; + loop { + let mut socket = embassy_net::tcp::TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + + led.set_low(); + info!("Listening on TCP:1234..."); + if let Err(e) = socket.accept(1234).await { + warn!("accept error: {:?}", e); + continue; + } + info!("Received connection from {:?}", socket.remote_endpoint()); + led.set_high(); + + loop { + let n = match socket.read(&mut buf).await { + Ok(0) => { + warn!("read EOF"); + break; + } + Ok(n) => n, + Err(e) => { + warn!("{:?}", e); + break; + } + }; + info!("rxd {}", core::str::from_utf8(&buf[..n]).unwrap()); + + if let Err(e) = socket.write_all(&buf[..n]).await { + warn!("write error: {:?}", e); + break; + } + } + } +} + +async fn wait_for_config(stack: &'static Stack>) -> embassy_net::StaticConfigV4 { + loop { + if let Some(config) = stack.config_v4() { + return config.clone(); + } + yield_now().await; + } +} diff --git a/examples/rp/src/bin/ethernet_w5500_udp.rs b/examples/rp/src/bin/ethernet_w5500_udp.rs new file mode 100644 index 000000000..038432b17 --- /dev/null +++ b/examples/rp/src/bin/ethernet_w5500_udp.rs @@ -0,0 +1,112 @@ +//! This example implements a UDP server listening on port 1234 and echoing back the data. +//! +//! Example written for the [`WIZnet W5500-EVB-Pico`](https://www.wiznet.io/product-item/w5500-evb-pico/) board. + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::yield_now; +use embassy_net::udp::{PacketMetadata, UdpSocket}; +use embassy_net::{Stack, StackResources}; +use embassy_net_w5500::*; +use embassy_rp::clocks::RoscRng; +use embassy_rp::gpio::{Input, Level, Output, Pull}; +use embassy_rp::peripherals::{PIN_17, PIN_20, PIN_21, SPI0}; +use embassy_rp::spi::{Async, Config as SpiConfig, Spi}; +use embassy_time::Delay; +use embedded_hal_async::spi::ExclusiveDevice; +use rand::RngCore; +use static_cell::make_static; +use {defmt_rtt as _, panic_probe as _}; +#[embassy_executor::task] +async fn ethernet_task( + runner: Runner< + 'static, + ExclusiveDevice, Output<'static, PIN_17>, Delay>, + Input<'static, PIN_21>, + Output<'static, PIN_20>, + >, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut rng = RoscRng; + + let mut spi_cfg = SpiConfig::default(); + spi_cfg.frequency = 50_000_000; + let (miso, mosi, clk) = (p.PIN_16, p.PIN_19, p.PIN_18); + let spi = Spi::new(p.SPI0, clk, mosi, miso, p.DMA_CH0, p.DMA_CH1, spi_cfg); + let cs = Output::new(p.PIN_17, Level::High); + let w5500_int = Input::new(p.PIN_21, Pull::Up); + let w5500_reset = Output::new(p.PIN_20, Level::High); + + let mac_addr = [0x02, 0x00, 0x00, 0x00, 0x00, 0x00]; + let state = make_static!(State::<8, 8>::new()); + let (device, runner) = embassy_net_w5500::new( + mac_addr, + state, + ExclusiveDevice::new(spi, cs, Delay), + w5500_int, + w5500_reset, + ) + .await; + unwrap!(spawner.spawn(ethernet_task(runner))); + + // Generate random seed + let seed = rng.next_u64(); + + // Init network stack + let stack = &*make_static!(Stack::new( + device, + embassy_net::Config::dhcpv4(Default::default()), + make_static!(StackResources::<2>::new()), + seed + )); + + // Launch network task + unwrap!(spawner.spawn(net_task(&stack))); + + info!("Waiting for DHCP..."); + let cfg = wait_for_config(stack).await; + let local_addr = cfg.address.address(); + info!("IP address: {:?}", local_addr); + + // Then we can use it! + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut rx_meta = [PacketMetadata::EMPTY; 16]; + let mut tx_meta = [PacketMetadata::EMPTY; 16]; + let mut buf = [0; 4096]; + loop { + let mut socket = UdpSocket::new(stack, &mut rx_meta, &mut rx_buffer, &mut tx_meta, &mut tx_buffer); + socket.bind(1234).unwrap(); + + loop { + let (n, ep) = socket.recv_from(&mut buf).await.unwrap(); + if let Ok(s) = core::str::from_utf8(&buf[..n]) { + info!("rxd from {}: {}", ep, s); + } + socket.send_to(&buf[..n], ep).await.unwrap(); + } + } +} + +async fn wait_for_config(stack: &'static Stack>) -> embassy_net::StaticConfigV4 { + loop { + if let Some(config) = stack.config_v4() { + return config.clone(); + } + yield_now().await; + } +} diff --git a/examples/rp/src/bin/flash.rs b/examples/rp/src/bin/flash.rs new file mode 100644 index 000000000..4c4982acc --- /dev/null +++ b/examples/rp/src/bin/flash.rs @@ -0,0 +1,101 @@ +//! This example test the flash connected to the RP2040 chip. + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::flash::{ERASE_SIZE, FLASH_BASE}; +use embassy_rp::peripherals::FLASH; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +const ADDR_OFFSET: u32 = 0x100000; +const FLASH_SIZE: usize = 2 * 1024 * 1024; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + // add some delay to give an attached debug probe time to parse the + // defmt RTT header. Reading that header might touch flash memory, which + // interferes with flash write operations. + // https://github.com/knurling-rs/defmt/pull/683 + Timer::after(Duration::from_millis(10)).await; + + let mut flash = embassy_rp::flash::Flash::<_, FLASH_SIZE>::new(p.FLASH); + + // Get JEDEC id + let jedec = flash.jedec_id().unwrap(); + info!("jedec id: 0x{:x}", jedec); + + // Get unique id + let mut uid = [0; 8]; + flash.unique_id(&mut uid).unwrap(); + info!("unique id: {:?}", uid); + + erase_write_sector(&mut flash, 0x00); + + multiwrite_bytes(&mut flash, ERASE_SIZE as u32); + + loop {} +} + +fn multiwrite_bytes(flash: &mut embassy_rp::flash::Flash<'_, FLASH, FLASH_SIZE>, offset: u32) { + info!(">>>> [multiwrite_bytes]"); + let mut read_buf = [0u8; ERASE_SIZE]; + defmt::unwrap!(flash.read(ADDR_OFFSET + offset, &mut read_buf)); + + info!("Addr of flash block is {:x}", ADDR_OFFSET + offset + FLASH_BASE as u32); + info!("Contents start with {=[u8]}", read_buf[0..4]); + + defmt::unwrap!(flash.erase(ADDR_OFFSET + offset, ADDR_OFFSET + offset + ERASE_SIZE as u32)); + + defmt::unwrap!(flash.read(ADDR_OFFSET + offset, &mut read_buf)); + info!("Contents after erase starts with {=[u8]}", read_buf[0..4]); + if read_buf.iter().any(|x| *x != 0xFF) { + defmt::panic!("unexpected"); + } + + defmt::unwrap!(flash.write(ADDR_OFFSET + offset, &[0x01])); + defmt::unwrap!(flash.write(ADDR_OFFSET + offset + 1, &[0x02])); + defmt::unwrap!(flash.write(ADDR_OFFSET + offset + 2, &[0x03])); + defmt::unwrap!(flash.write(ADDR_OFFSET + offset + 3, &[0x04])); + + defmt::unwrap!(flash.read(ADDR_OFFSET + offset, &mut read_buf)); + info!("Contents after write starts with {=[u8]}", read_buf[0..4]); + if &read_buf[0..4] != &[0x01, 0x02, 0x03, 0x04] { + defmt::panic!("unexpected"); + } +} + +fn erase_write_sector(flash: &mut embassy_rp::flash::Flash<'_, FLASH, FLASH_SIZE>, offset: u32) { + info!(">>>> [erase_write_sector]"); + let mut buf = [0u8; ERASE_SIZE]; + defmt::unwrap!(flash.read(ADDR_OFFSET + offset, &mut buf)); + + info!("Addr of flash block is {:x}", ADDR_OFFSET + offset + FLASH_BASE as u32); + info!("Contents start with {=[u8]}", buf[0..4]); + + defmt::unwrap!(flash.erase(ADDR_OFFSET + offset, ADDR_OFFSET + offset + ERASE_SIZE as u32)); + + defmt::unwrap!(flash.read(ADDR_OFFSET + offset, &mut buf)); + info!("Contents after erase starts with {=[u8]}", buf[0..4]); + if buf.iter().any(|x| *x != 0xFF) { + defmt::panic!("unexpected"); + } + + for b in buf.iter_mut() { + *b = 0xDA; + } + + defmt::unwrap!(flash.write(ADDR_OFFSET + offset, &buf)); + + defmt::unwrap!(flash.read(ADDR_OFFSET + offset, &mut buf)); + info!("Contents after write starts with {=[u8]}", buf[0..4]); + if buf.iter().any(|x| *x != 0xDA) { + defmt::panic!("unexpected"); + } +} diff --git a/examples/rp/src/bin/gpio_async.rs b/examples/rp/src/bin/gpio_async.rs index 52d13a9d5..bf58044d5 100644 --- a/examples/rp/src/bin/gpio_async.rs +++ b/examples/rp/src/bin/gpio_async.rs @@ -1,3 +1,7 @@ +//! This example shows how async gpio can be used with a RP2040. +//! +//! The LED on the RP Pico W board is connected differently. See wifi_blinky.rs. + #![no_std] #![no_main] #![feature(type_alias_impl_trait)] @@ -9,8 +13,6 @@ use embassy_time::{Duration, Timer}; use gpio::{Input, Level, Output, Pull}; use {defmt_rtt as _, panic_probe as _}; -/// This example shows how async gpio can be used with a RP2040. -/// /// It requires an external signal to be manually triggered on PIN 16. For /// example, this could be accomplished using an external power source with a /// button so that it is possible to toggle the signal from low to high. diff --git a/examples/rp/src/bin/gpout.rs b/examples/rp/src/bin/gpout.rs new file mode 100644 index 000000000..0a3b5fa98 --- /dev/null +++ b/examples/rp/src/bin/gpout.rs @@ -0,0 +1,38 @@ +//! This example shows how GPOUT (General purpose clock outputs) can toggle a output pin. +//! +//! The LED on the RP Pico W board is connected differently. Add a LED and resistor to another pin. + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::clocks; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + let gpout3 = clocks::Gpout::new(p.PIN_25); + gpout3.set_div(1000, 0); + gpout3.enable(); + + loop { + gpout3.set_src(clocks::GpoutSrc::Sys); + info!( + "Pin 25 is now outputing CLK_SYS/1000, should be toggling at {}", + gpout3.get_freq() + ); + Timer::after(Duration::from_secs(2)).await; + + gpout3.set_src(clocks::GpoutSrc::Ref); + info!( + "Pin 25 is now outputing CLK_REF/1000, should be toggling at {}", + gpout3.get_freq() + ); + Timer::after(Duration::from_secs(2)).await; + } +} diff --git a/examples/rp/src/bin/i2c_async.rs b/examples/rp/src/bin/i2c_async.rs new file mode 100644 index 000000000..93224bc43 --- /dev/null +++ b/examples/rp/src/bin/i2c_async.rs @@ -0,0 +1,111 @@ +//! This example shows how to communicate asynchronous using i2c with external chips. +//! +//! Example written for the [`MCP23017 16-Bit I2C I/O Expander with Serial Interface`] chip. +//! (https://www.microchip.com/en-us/product/mcp23017) + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::i2c::{self, Config, InterruptHandler}; +use embassy_rp::peripherals::I2C1; +use embassy_time::{Duration, Timer}; +use embedded_hal_async::i2c::I2c; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + I2C1_IRQ => InterruptHandler; +}); + +#[allow(dead_code)] +mod mcp23017 { + pub const ADDR: u8 = 0x20; // default addr + + macro_rules! mcpregs { + ($($name:ident : $val:expr),* $(,)?) => { + $( + pub const $name: u8 = $val; + )* + + pub fn regname(reg: u8) -> &'static str { + match reg { + $( + $val => stringify!($name), + )* + _ => panic!("bad reg"), + } + } + } + } + + // These are correct for IOCON.BANK=0 + mcpregs! { + IODIRA: 0x00, + IPOLA: 0x02, + GPINTENA: 0x04, + DEFVALA: 0x06, + INTCONA: 0x08, + IOCONA: 0x0A, + GPPUA: 0x0C, + INTFA: 0x0E, + INTCAPA: 0x10, + GPIOA: 0x12, + OLATA: 0x14, + IODIRB: 0x01, + IPOLB: 0x03, + GPINTENB: 0x05, + DEFVALB: 0x07, + INTCONB: 0x09, + IOCONB: 0x0B, + GPPUB: 0x0D, + INTFB: 0x0F, + INTCAPB: 0x11, + GPIOB: 0x13, + OLATB: 0x15, + } +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + let sda = p.PIN_14; + let scl = p.PIN_15; + + info!("set up i2c "); + let mut i2c = i2c::I2c::new_async(p.I2C1, scl, sda, Irqs, Config::default()); + + use mcp23017::*; + + info!("init mcp23017 config for IxpandO"); + // init - a outputs, b inputs + i2c.write(ADDR, &[IODIRA, 0x00]).await.unwrap(); + i2c.write(ADDR, &[IODIRB, 0xff]).await.unwrap(); + i2c.write(ADDR, &[GPPUB, 0xff]).await.unwrap(); // pullups + + let mut val = 1; + loop { + let mut portb = [0]; + + i2c.write_read(mcp23017::ADDR, &[GPIOB], &mut portb).await.unwrap(); + info!("portb = {:02x}", portb[0]); + i2c.write(mcp23017::ADDR, &[GPIOA, val | portb[0]]).await.unwrap(); + val = val.rotate_left(1); + + // get a register dump + info!("getting register dump"); + let mut regs = [0; 22]; + i2c.write_read(ADDR, &[0], &mut regs).await.unwrap(); + // always get the regdump but only display it if portb'0 is set + if portb[0] & 1 != 0 { + for (idx, reg) in regs.into_iter().enumerate() { + info!("{} => {:02x}", regname(idx as u8), reg); + } + } + + Timer::after(Duration::from_millis(100)).await; + } +} diff --git a/examples/rp/src/bin/i2c_blocking.rs b/examples/rp/src/bin/i2c_blocking.rs new file mode 100644 index 000000000..1c8c2039d --- /dev/null +++ b/examples/rp/src/bin/i2c_blocking.rs @@ -0,0 +1,75 @@ +//! This example shows how to communicate using i2c with external chips. +//! +//! Example written for the [`MCP23017 16-Bit I2C I/O Expander with Serial Interface`] chip. +//! (https://www.microchip.com/en-us/product/mcp23017) + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::i2c::{self, Config}; +use embassy_time::{Duration, Timer}; +use embedded_hal_1::i2c::I2c; +use {defmt_rtt as _, panic_probe as _}; + +#[allow(dead_code)] +mod mcp23017 { + pub const ADDR: u8 = 0x20; // default addr + + pub const IODIRA: u8 = 0x00; + pub const IPOLA: u8 = 0x02; + pub const GPINTENA: u8 = 0x04; + pub const DEFVALA: u8 = 0x06; + pub const INTCONA: u8 = 0x08; + pub const IOCONA: u8 = 0x0A; + pub const GPPUA: u8 = 0x0C; + pub const INTFA: u8 = 0x0E; + pub const INTCAPA: u8 = 0x10; + pub const GPIOA: u8 = 0x12; + pub const OLATA: u8 = 0x14; + pub const IODIRB: u8 = 0x01; + pub const IPOLB: u8 = 0x03; + pub const GPINTENB: u8 = 0x05; + pub const DEFVALB: u8 = 0x07; + pub const INTCONB: u8 = 0x09; + pub const IOCONB: u8 = 0x0B; + pub const GPPUB: u8 = 0x0D; + pub const INTFB: u8 = 0x0F; + pub const INTCAPB: u8 = 0x11; + pub const GPIOB: u8 = 0x13; + pub const OLATB: u8 = 0x15; +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + let sda = p.PIN_14; + let scl = p.PIN_15; + + info!("set up i2c "); + let mut i2c = i2c::I2c::new_blocking(p.I2C1, scl, sda, Config::default()); + + use mcp23017::*; + + info!("init mcp23017 config for IxpandO"); + // init - a outputs, b inputs + i2c.write(ADDR, &[IODIRA, 0x00]).unwrap(); + i2c.write(ADDR, &[IODIRB, 0xff]).unwrap(); + i2c.write(ADDR, &[GPPUB, 0xff]).unwrap(); // pullups + + let mut val = 0xaa; + loop { + let mut portb = [0]; + + i2c.write(mcp23017::ADDR, &[GPIOA, val]).unwrap(); + i2c.write_read(mcp23017::ADDR, &[GPIOB], &mut portb).unwrap(); + + info!("portb = {:02x}", portb[0]); + val = !val; + + Timer::after(Duration::from_secs(1)).await; + } +} diff --git a/examples/rp/src/bin/lora_lorawan.rs b/examples/rp/src/bin/lora_lorawan.rs new file mode 100644 index 000000000..d631fafa1 --- /dev/null +++ b/examples/rp/src/bin/lora_lorawan.rs @@ -0,0 +1,81 @@ +//! This example runs on the Raspberry Pi Pico with a Waveshare board containing a Semtech Sx1262 radio. +//! It demonstrates LoRaWAN join functionality. + +#![no_std] +#![no_main] +#![macro_use] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_lora::iv::GenericSx126xInterfaceVariant; +use embassy_lora::LoraTimer; +use embassy_rp::gpio::{Input, Level, Output, Pin, Pull}; +use embassy_rp::spi::{Config, Spi}; +use embassy_time::Delay; +use lora_phy::mod_params::*; +use lora_phy::sx1261_2::SX1261_2; +use lora_phy::LoRa; +use lorawan::default_crypto::DefaultFactory as Crypto; +use lorawan_device::async_device::lora_radio::LoRaRadio; +use lorawan_device::async_device::{region, Device, JoinMode}; +use {defmt_rtt as _, panic_probe as _}; + +const LORAWAN_REGION: region::Region = region::Region::EU868; // warning: set this appropriately for the region + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + let miso = p.PIN_12; + let mosi = p.PIN_11; + let clk = p.PIN_10; + let spi = Spi::new(p.SPI1, clk, mosi, miso, p.DMA_CH0, p.DMA_CH1, Config::default()); + + let nss = Output::new(p.PIN_3.degrade(), Level::High); + let reset = Output::new(p.PIN_15.degrade(), Level::High); + let dio1 = Input::new(p.PIN_20.degrade(), Pull::None); + let busy = Input::new(p.PIN_2.degrade(), Pull::None); + + let iv = GenericSx126xInterfaceVariant::new(nss, reset, dio1, busy, None, None).unwrap(); + + let mut delay = Delay; + + let lora = { + match LoRa::new( + SX1261_2::new(BoardType::RpPicoWaveshareSx1262, spi, iv), + true, + &mut delay, + ) + .await + { + Ok(l) => l, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + let radio = LoRaRadio::new(lora); + let region: region::Configuration = region::Configuration::new(LORAWAN_REGION); + let mut device: Device<_, Crypto, _, _> = Device::new(region, radio, LoraTimer::new(), embassy_rp::clocks::RoscRng); + + defmt::info!("Joining LoRaWAN network"); + + // TODO: Adjust the EUI and Keys according to your network credentials + match device + .join(&JoinMode::OTAA { + deveui: [0, 0, 0, 0, 0, 0, 0, 0], + appeui: [0, 0, 0, 0, 0, 0, 0, 0], + appkey: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + }) + .await + { + Ok(()) => defmt::info!("LoRaWAN network joined"), + Err(err) => { + info!("Radio error = {}", err); + return; + } + }; +} diff --git a/examples/rp/src/bin/lora_p2p_receive.rs b/examples/rp/src/bin/lora_p2p_receive.rs new file mode 100644 index 000000000..396d669de --- /dev/null +++ b/examples/rp/src/bin/lora_p2p_receive.rs @@ -0,0 +1,116 @@ +//! This example runs on the Raspberry Pi Pico with a Waveshare board containing a Semtech Sx1262 radio. +//! It demonstrates LORA P2P receive functionality in conjunction with the lora_p2p_send example. + +#![no_std] +#![no_main] +#![macro_use] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_lora::iv::GenericSx126xInterfaceVariant; +use embassy_rp::gpio::{Input, Level, Output, Pin, Pull}; +use embassy_rp::spi::{Config, Spi}; +use embassy_time::{Delay, Duration, Timer}; +use lora_phy::mod_params::*; +use lora_phy::sx1261_2::SX1261_2; +use lora_phy::LoRa; +use {defmt_rtt as _, panic_probe as _}; + +const LORA_FREQUENCY_IN_HZ: u32 = 903_900_000; // warning: set this appropriately for the region + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + let miso = p.PIN_12; + let mosi = p.PIN_11; + let clk = p.PIN_10; + let spi = Spi::new(p.SPI1, clk, mosi, miso, p.DMA_CH0, p.DMA_CH1, Config::default()); + + let nss = Output::new(p.PIN_3.degrade(), Level::High); + let reset = Output::new(p.PIN_15.degrade(), Level::High); + let dio1 = Input::new(p.PIN_20.degrade(), Pull::None); + let busy = Input::new(p.PIN_2.degrade(), Pull::None); + + let iv = GenericSx126xInterfaceVariant::new(nss, reset, dio1, busy, None, None).unwrap(); + + let mut delay = Delay; + + let mut lora = { + match LoRa::new( + SX1261_2::new(BoardType::RpPicoWaveshareSx1262, spi, iv), + false, + &mut delay, + ) + .await + { + Ok(l) => l, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + let mut debug_indicator = Output::new(p.PIN_25, Level::Low); + + let mut receiving_buffer = [00u8; 100]; + + let mdltn_params = { + match lora.create_modulation_params( + SpreadingFactor::_10, + Bandwidth::_250KHz, + CodingRate::_4_8, + LORA_FREQUENCY_IN_HZ, + ) { + Ok(mp) => mp, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + let rx_pkt_params = { + match lora.create_rx_packet_params(4, false, receiving_buffer.len() as u8, true, false, &mdltn_params) { + Ok(pp) => pp, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + match lora + .prepare_for_rx(&mdltn_params, &rx_pkt_params, None, true, false, 0, 0x00ffffffu32) + .await + { + Ok(()) => {} + Err(err) => { + info!("Radio error = {}", err); + return; + } + }; + + loop { + receiving_buffer = [00u8; 100]; + match lora.rx(&rx_pkt_params, &mut receiving_buffer).await { + Ok((received_len, _rx_pkt_status)) => { + if (received_len == 3) + && (receiving_buffer[0] == 0x01u8) + && (receiving_buffer[1] == 0x02u8) + && (receiving_buffer[2] == 0x03u8) + { + info!("rx successful"); + debug_indicator.set_high(); + Timer::after(Duration::from_secs(5)).await; + debug_indicator.set_low(); + } else { + info!("rx unknown packet"); + } + } + Err(err) => info!("rx unsuccessful = {}", err), + } + } +} diff --git a/examples/rp/src/bin/lora_p2p_send.rs b/examples/rp/src/bin/lora_p2p_send.rs new file mode 100644 index 000000000..a0f70fa5c --- /dev/null +++ b/examples/rp/src/bin/lora_p2p_send.rs @@ -0,0 +1,104 @@ +//! This example runs on the Raspberry Pi Pico with a Waveshare board containing a Semtech Sx1262 radio. +//! It demonstrates LORA P2P send functionality. + +#![no_std] +#![no_main] +#![macro_use] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_lora::iv::GenericSx126xInterfaceVariant; +use embassy_rp::gpio::{Input, Level, Output, Pin, Pull}; +use embassy_rp::spi::{Config, Spi}; +use embassy_time::Delay; +use lora_phy::mod_params::*; +use lora_phy::sx1261_2::SX1261_2; +use lora_phy::LoRa; +use {defmt_rtt as _, panic_probe as _}; + +const LORA_FREQUENCY_IN_HZ: u32 = 903_900_000; // warning: set this appropriately for the region + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + let miso = p.PIN_12; + let mosi = p.PIN_11; + let clk = p.PIN_10; + let spi = Spi::new(p.SPI1, clk, mosi, miso, p.DMA_CH0, p.DMA_CH1, Config::default()); + + let nss = Output::new(p.PIN_3.degrade(), Level::High); + let reset = Output::new(p.PIN_15.degrade(), Level::High); + let dio1 = Input::new(p.PIN_20.degrade(), Pull::None); + let busy = Input::new(p.PIN_2.degrade(), Pull::None); + + let iv = GenericSx126xInterfaceVariant::new(nss, reset, dio1, busy, None, None).unwrap(); + + let mut delay = Delay; + + let mut lora = { + match LoRa::new( + SX1261_2::new(BoardType::RpPicoWaveshareSx1262, spi, iv), + false, + &mut delay, + ) + .await + { + Ok(l) => l, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + let mdltn_params = { + match lora.create_modulation_params( + SpreadingFactor::_10, + Bandwidth::_250KHz, + CodingRate::_4_8, + LORA_FREQUENCY_IN_HZ, + ) { + Ok(mp) => mp, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + let mut tx_pkt_params = { + match lora.create_tx_packet_params(4, false, true, false, &mdltn_params) { + Ok(pp) => pp, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + match lora.prepare_for_tx(&mdltn_params, 20, false).await { + Ok(()) => {} + Err(err) => { + info!("Radio error = {}", err); + return; + } + }; + + let buffer = [0x01u8, 0x02u8, 0x03u8]; + match lora.tx(&mdltn_params, &mut tx_pkt_params, &buffer, 0xffffff).await { + Ok(()) => { + info!("TX DONE"); + } + Err(err) => { + info!("Radio error = {}", err); + return; + } + }; + + match lora.sleep(&mut delay).await { + Ok(()) => info!("Sleep successful"), + Err(err) => info!("Sleep unsuccessful = {}", err), + } +} diff --git a/examples/rp/src/bin/lora_p2p_send_multicore.rs b/examples/rp/src/bin/lora_p2p_send_multicore.rs new file mode 100644 index 000000000..89a62818d --- /dev/null +++ b/examples/rp/src/bin/lora_p2p_send_multicore.rs @@ -0,0 +1,140 @@ +//! This example runs on the Raspberry Pi Pico with a Waveshare board containing a Semtech Sx1262 radio. +//! It demonstrates LORA P2P send functionality using the second core, with data provided by the first core. + +#![no_std] +#![no_main] +#![macro_use] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Executor; +use embassy_lora::iv::GenericSx126xInterfaceVariant; +use embassy_rp::gpio::{AnyPin, Input, Level, Output, Pin, Pull}; +use embassy_rp::multicore::{spawn_core1, Stack}; +use embassy_rp::peripherals::SPI1; +use embassy_rp::spi::{Async, Config, Spi}; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::channel::Channel; +use embassy_time::{Delay, Duration, Timer}; +use lora_phy::mod_params::*; +use lora_phy::sx1261_2::SX1261_2; +use lora_phy::LoRa; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +static mut CORE1_STACK: Stack<4096> = Stack::new(); +static EXECUTOR0: StaticCell = StaticCell::new(); +static EXECUTOR1: StaticCell = StaticCell::new(); +static CHANNEL: Channel = Channel::new(); + +const LORA_FREQUENCY_IN_HZ: u32 = 903_900_000; // warning: set this appropriately for the region + +#[cortex_m_rt::entry] +fn main() -> ! { + let p = embassy_rp::init(Default::default()); + + let miso = p.PIN_12; + let mosi = p.PIN_11; + let clk = p.PIN_10; + let spi = Spi::new(p.SPI1, clk, mosi, miso, p.DMA_CH0, p.DMA_CH1, Config::default()); + + let nss = Output::new(p.PIN_3.degrade(), Level::High); + let reset = Output::new(p.PIN_15.degrade(), Level::High); + let dio1 = Input::new(p.PIN_20.degrade(), Pull::None); + let busy = Input::new(p.PIN_2.degrade(), Pull::None); + + let iv = GenericSx126xInterfaceVariant::new(nss, reset, dio1, busy, None, None).unwrap(); + + spawn_core1(p.CORE1, unsafe { &mut CORE1_STACK }, move || { + let executor1 = EXECUTOR1.init(Executor::new()); + executor1.run(|spawner| unwrap!(spawner.spawn(core1_task(spi, iv)))); + }); + + let executor0 = EXECUTOR0.init(Executor::new()); + executor0.run(|spawner| unwrap!(spawner.spawn(core0_task()))); +} + +#[embassy_executor::task] +async fn core0_task() { + info!("Hello from core 0"); + loop { + CHANNEL.send([0x01u8, 0x02u8, 0x03u8]).await; + Timer::after(Duration::from_millis(60 * 1000)).await; + } +} + +#[embassy_executor::task] +async fn core1_task( + spi: Spi<'static, SPI1, Async>, + iv: GenericSx126xInterfaceVariant, Input<'static, AnyPin>>, +) { + info!("Hello from core 1"); + let mut delay = Delay; + + let mut lora = { + match LoRa::new( + SX1261_2::new(BoardType::RpPicoWaveshareSx1262, spi, iv), + false, + &mut delay, + ) + .await + { + Ok(l) => l, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + let mdltn_params = { + match lora.create_modulation_params( + SpreadingFactor::_10, + Bandwidth::_250KHz, + CodingRate::_4_8, + LORA_FREQUENCY_IN_HZ, + ) { + Ok(mp) => mp, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + let mut tx_pkt_params = { + match lora.create_tx_packet_params(4, false, true, false, &mdltn_params) { + Ok(pp) => pp, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + loop { + let buffer: [u8; 3] = CHANNEL.recv().await; + match lora.prepare_for_tx(&mdltn_params, 20, false).await { + Ok(()) => {} + Err(err) => { + info!("Radio error = {}", err); + return; + } + }; + + match lora.tx(&mdltn_params, &mut tx_pkt_params, &buffer, 0xffffff).await { + Ok(()) => { + info!("TX DONE"); + } + Err(err) => { + info!("Radio error = {}", err); + return; + } + }; + + match lora.sleep(&mut delay).await { + Ok(()) => info!("Sleep successful"), + Err(err) => info!("Sleep unsuccessful = {}", err), + } + } +} diff --git a/examples/rp/src/bin/multicore.rs b/examples/rp/src/bin/multicore.rs new file mode 100644 index 000000000..893b724bf --- /dev/null +++ b/examples/rp/src/bin/multicore.rs @@ -0,0 +1,64 @@ +//! This example shows how to send messages between the two cores in the RP2040 chip. +//! +//! The LED on the RP Pico W board is connected differently. See wifi_blinky.rs. + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Executor; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::multicore::{spawn_core1, Stack}; +use embassy_rp::peripherals::PIN_25; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::channel::Channel; +use embassy_time::{Duration, Timer}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +static mut CORE1_STACK: Stack<4096> = Stack::new(); +static EXECUTOR0: StaticCell = StaticCell::new(); +static EXECUTOR1: StaticCell = StaticCell::new(); +static CHANNEL: Channel = Channel::new(); + +enum LedState { + On, + Off, +} + +#[cortex_m_rt::entry] +fn main() -> ! { + let p = embassy_rp::init(Default::default()); + let led = Output::new(p.PIN_25, Level::Low); + + spawn_core1(p.CORE1, unsafe { &mut CORE1_STACK }, move || { + let executor1 = EXECUTOR1.init(Executor::new()); + executor1.run(|spawner| unwrap!(spawner.spawn(core1_task(led)))); + }); + + let executor0 = EXECUTOR0.init(Executor::new()); + executor0.run(|spawner| unwrap!(spawner.spawn(core0_task()))); +} + +#[embassy_executor::task] +async fn core0_task() { + info!("Hello from core 0"); + loop { + CHANNEL.send(LedState::On).await; + Timer::after(Duration::from_millis(100)).await; + CHANNEL.send(LedState::Off).await; + Timer::after(Duration::from_millis(400)).await; + } +} + +#[embassy_executor::task] +async fn core1_task(mut led: Output<'static, PIN_25>) { + info!("Hello from core 1"); + loop { + match CHANNEL.recv().await { + LedState::On => led.set_high(), + LedState::Off => led.set_low(), + } + } +} diff --git a/examples/rp/src/bin/multiprio.rs b/examples/rp/src/bin/multiprio.rs new file mode 100644 index 000000000..9ace4cd68 --- /dev/null +++ b/examples/rp/src/bin/multiprio.rs @@ -0,0 +1,146 @@ +//! This example showcases how to create multiple Executor instances to run tasks at +//! different priority levels. +//! +//! Low priority executor runs in thread mode (not interrupt), and uses `sev` for signaling +//! there's work in the queue, and `wfe` for waiting for work. +//! +//! Medium and high priority executors run in two interrupts with different priorities. +//! Signaling work is done by pending the interrupt. No "waiting" needs to be done explicitly, since +//! when there's work the interrupt will trigger and run the executor. +//! +//! Sample output below. Note that high priority ticks can interrupt everything else, and +//! medium priority computations can interrupt low priority computations, making them to appear +//! to take significantly longer time. +//! +//! ```not_rust +//! [med] Starting long computation +//! [med] done in 992 ms +//! [high] tick! +//! [low] Starting long computation +//! [med] Starting long computation +//! [high] tick! +//! [high] tick! +//! [med] done in 993 ms +//! [med] Starting long computation +//! [high] tick! +//! [high] tick! +//! [med] done in 993 ms +//! [low] done in 3972 ms +//! [med] Starting long computation +//! [high] tick! +//! [high] tick! +//! [med] done in 993 ms +//! ``` +//! +//! For comparison, try changing the code so all 3 tasks get spawned on the low priority executor. +//! You will get an output like the following. Note that no computation is ever interrupted. +//! +//! ```not_rust +//! [high] tick! +//! [med] Starting long computation +//! [med] done in 496 ms +//! [low] Starting long computation +//! [low] done in 992 ms +//! [med] Starting long computation +//! [med] done in 496 ms +//! [high] tick! +//! [low] Starting long computation +//! [low] done in 992 ms +//! [high] tick! +//! [med] Starting long computation +//! [med] done in 496 ms +//! [high] tick! +//! ``` +//! + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use cortex_m_rt::entry; +use defmt::{info, unwrap}; +use embassy_executor::{Executor, InterruptExecutor}; +use embassy_rp::interrupt; +use embassy_rp::interrupt::{InterruptExt, Priority}; +use embassy_time::{Duration, Instant, Timer, TICK_HZ}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::task] +async fn run_high() { + loop { + info!(" [high] tick!"); + Timer::after(Duration::from_ticks(673740)).await; + } +} + +#[embassy_executor::task] +async fn run_med() { + loop { + let start = Instant::now(); + info!(" [med] Starting long computation"); + + // Spin-wait to simulate a long CPU computation + cortex_m::asm::delay(125_000_000); // ~1 second + + let end = Instant::now(); + let ms = end.duration_since(start).as_ticks() * 1000 / TICK_HZ; + info!(" [med] done in {} ms", ms); + + Timer::after(Duration::from_ticks(53421)).await; + } +} + +#[embassy_executor::task] +async fn run_low() { + loop { + let start = Instant::now(); + info!("[low] Starting long computation"); + + // Spin-wait to simulate a long CPU computation + cortex_m::asm::delay(250_000_000); // ~2 seconds + + let end = Instant::now(); + let ms = end.duration_since(start).as_ticks() * 1000 / TICK_HZ; + info!("[low] done in {} ms", ms); + + Timer::after(Duration::from_ticks(82983)).await; + } +} + +static EXECUTOR_HIGH: InterruptExecutor = InterruptExecutor::new(); +static EXECUTOR_MED: InterruptExecutor = InterruptExecutor::new(); +static EXECUTOR_LOW: StaticCell = StaticCell::new(); + +#[interrupt] +unsafe fn SWI_IRQ_1() { + EXECUTOR_HIGH.on_interrupt() +} + +#[interrupt] +unsafe fn SWI_IRQ_0() { + EXECUTOR_MED.on_interrupt() +} + +#[entry] +fn main() -> ! { + info!("Hello World!"); + + let _p = embassy_rp::init(Default::default()); + + // High-priority executor: SWI_IRQ_1, priority level 2 + interrupt::SWI_IRQ_1.set_priority(Priority::P2); + let spawner = EXECUTOR_HIGH.start(interrupt::SWI_IRQ_1); + unwrap!(spawner.spawn(run_high())); + + // Medium-priority executor: SWI_IRQ_0, priority level 3 + interrupt::SWI_IRQ_0.set_priority(Priority::P3); + let spawner = EXECUTOR_MED.start(interrupt::SWI_IRQ_0); + unwrap!(spawner.spawn(run_med())); + + // Low priority executor: runs in thread mode, using WFE/SEV + let executor = EXECUTOR_LOW.init(Executor::new()); + executor.run(|spawner| { + unwrap!(spawner.spawn(run_low())); + }); +} diff --git a/examples/rp/src/bin/pio_async.rs b/examples/rp/src/bin/pio_async.rs new file mode 100644 index 000000000..c001d6440 --- /dev/null +++ b/examples/rp/src/bin/pio_async.rs @@ -0,0 +1,135 @@ +//! This example shows powerful PIO module in the RP2040 chip. + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +use defmt::info; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{Common, Config, InterruptHandler, Irq, Pio, PioPin, ShiftDirection, StateMachine}; +use embassy_rp::relocate::RelocatedProgram; +use fixed::traits::ToFixed; +use fixed_macro::types::U56F8; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +fn setup_pio_task_sm0<'a>(pio: &mut Common<'a, PIO0>, sm: &mut StateMachine<'a, PIO0, 0>, pin: impl PioPin) { + // Setup sm0 + + // Send data serially to pin + let prg = pio_proc::pio_asm!( + ".origin 16", + "set pindirs, 1", + ".wrap_target", + "out pins,1 [19]", + ".wrap", + ); + + let relocated = RelocatedProgram::new(&prg.program); + let mut cfg = Config::default(); + cfg.use_program(&pio.load_program(&relocated), &[]); + let out_pin = pio.make_pio_pin(pin); + cfg.set_out_pins(&[&out_pin]); + cfg.set_set_pins(&[&out_pin]); + cfg.clock_divider = (U56F8!(125_000_000) / 20 / 200).to_fixed(); + cfg.shift_out.auto_fill = true; + sm.set_config(&cfg); +} + +#[embassy_executor::task] +async fn pio_task_sm0(mut sm: StateMachine<'static, PIO0, 0>) { + sm.set_enable(true); + + let mut v = 0x0f0caffa; + loop { + sm.tx().wait_push(v).await; + v ^= 0xffff; + info!("Pushed {:032b} to FIFO", v); + } +} + +fn setup_pio_task_sm1<'a>(pio: &mut Common<'a, PIO0>, sm: &mut StateMachine<'a, PIO0, 1>) { + // Setupm sm1 + + // Read 0b10101 repeatedly until ISR is full + let prg = pio_proc::pio_asm!( + // + ".origin 8", + "set x, 0x15", + ".wrap_target", + "in x, 5 [31]", + ".wrap", + ); + + let relocated = RelocatedProgram::new(&prg.program); + let mut cfg = Config::default(); + cfg.use_program(&pio.load_program(&relocated), &[]); + cfg.clock_divider = (U56F8!(125_000_000) / 2000).to_fixed(); + cfg.shift_in.auto_fill = true; + cfg.shift_in.direction = ShiftDirection::Right; + sm.set_config(&cfg); +} + +#[embassy_executor::task] +async fn pio_task_sm1(mut sm: StateMachine<'static, PIO0, 1>) { + sm.set_enable(true); + loop { + let rx = sm.rx().wait_pull().await; + info!("Pulled {:032b} from FIFO", rx); + } +} + +fn setup_pio_task_sm2<'a>(pio: &mut Common<'a, PIO0>, sm: &mut StateMachine<'a, PIO0, 2>) { + // Setup sm2 + + // Repeatedly trigger IRQ 3 + let prg = pio_proc::pio_asm!( + ".origin 0", + ".wrap_target", + "set x,10", + "delay:", + "jmp x-- delay [15]", + "irq 3 [15]", + ".wrap", + ); + let relocated = RelocatedProgram::new(&prg.program); + let mut cfg = Config::default(); + cfg.use_program(&pio.load_program(&relocated), &[]); + cfg.clock_divider = (U56F8!(125_000_000) / 2000).to_fixed(); + sm.set_config(&cfg); +} + +#[embassy_executor::task] +async fn pio_task_sm2(mut irq: Irq<'static, PIO0, 3>, mut sm: StateMachine<'static, PIO0, 2>) { + sm.set_enable(true); + loop { + irq.wait().await; + info!("IRQ trigged"); + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let pio = p.PIO0; + + let Pio { + mut common, + irq3, + mut sm0, + mut sm1, + mut sm2, + .. + } = Pio::new(pio, Irqs); + + setup_pio_task_sm0(&mut common, &mut sm0, p.PIN_0); + setup_pio_task_sm1(&mut common, &mut sm1); + setup_pio_task_sm2(&mut common, &mut sm2); + spawner.spawn(pio_task_sm0(sm0)).unwrap(); + spawner.spawn(pio_task_sm1(sm1)).unwrap(); + spawner.spawn(pio_task_sm2(irq3, sm2)).unwrap(); +} diff --git a/examples/rp/src/bin/pio_dma.rs b/examples/rp/src/bin/pio_dma.rs new file mode 100644 index 000000000..9ab72e1f3 --- /dev/null +++ b/examples/rp/src/bin/pio_dma.rs @@ -0,0 +1,86 @@ +//! This example shows powerful PIO module in the RP2040 chip. + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +use defmt::info; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{Config, InterruptHandler, Pio, ShiftConfig, ShiftDirection}; +use embassy_rp::relocate::RelocatedProgram; +use embassy_rp::{bind_interrupts, Peripheral}; +use fixed::traits::ToFixed; +use fixed_macro::types::U56F8; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +fn swap_nibbles(v: u32) -> u32 { + let v = (v & 0x0f0f_0f0f) << 4 | (v & 0xf0f0_f0f0) >> 4; + let v = (v & 0x00ff_00ff) << 8 | (v & 0xff00_ff00) >> 8; + (v & 0x0000_ffff) << 16 | (v & 0xffff_0000) >> 16 +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let pio = p.PIO0; + let Pio { + mut common, + sm0: mut sm, + .. + } = Pio::new(pio, Irqs); + + let prg = pio_proc::pio_asm!( + ".origin 0", + "set pindirs,1", + ".wrap_target", + "set y,7", + "loop:", + "out x,4", + "in x,4", + "jmp y--, loop", + ".wrap", + ); + + let relocated = RelocatedProgram::new(&prg.program); + let mut cfg = Config::default(); + cfg.use_program(&common.load_program(&relocated), &[]); + cfg.clock_divider = (U56F8!(125_000_000) / U56F8!(10_000)).to_fixed(); + cfg.shift_in = ShiftConfig { + auto_fill: true, + threshold: 32, + direction: ShiftDirection::Left, + }; + cfg.shift_out = ShiftConfig { + auto_fill: true, + threshold: 32, + direction: ShiftDirection::Right, + }; + + sm.set_config(&cfg); + sm.set_enable(true); + + let mut dma_out_ref = p.DMA_CH0.into_ref(); + let mut dma_in_ref = p.DMA_CH1.into_ref(); + let mut dout = [0x12345678u32; 29]; + for i in 1..dout.len() { + dout[i] = (dout[i - 1] & 0x0fff_ffff) * 13 + 7; + } + let mut din = [0u32; 29]; + loop { + let (rx, tx) = sm.rx_tx(); + join( + tx.dma_push(dma_out_ref.reborrow(), &dout), + rx.dma_pull(dma_in_ref.reborrow(), &mut din), + ) + .await; + for i in 0..din.len() { + assert_eq!(din[i], swap_nibbles(dout[i])); + } + info!("Swapped {} words", dout.len()); + } +} diff --git a/examples/rp/src/bin/pio_hd44780.rs b/examples/rp/src/bin/pio_hd44780.rs new file mode 100644 index 000000000..8aedd24b6 --- /dev/null +++ b/examples/rp/src/bin/pio_hd44780.rs @@ -0,0 +1,244 @@ +//! This example shows powerful PIO module in the RP2040 chip to communicate with a HD44780 display. +//! See (https://www.sparkfun.com/datasheets/LCD/HD44780.pdf) + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use core::fmt::Write; + +use embassy_executor::Spawner; +use embassy_rp::dma::{AnyChannel, Channel}; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{ + Config, Direction, FifoJoin, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine, +}; +use embassy_rp::pwm::{self, Pwm}; +use embassy_rp::relocate::RelocatedProgram; +use embassy_rp::{bind_interrupts, into_ref, Peripheral, PeripheralRef}; +use embassy_time::{Duration, Instant, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(pub struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // this test assumes a 2x16 HD44780 display attached as follow: + // rs = PIN0 + // rw = PIN1 + // e = PIN2 + // db4 = PIN3 + // db5 = PIN4 + // db6 = PIN5 + // db7 = PIN6 + // additionally a pwm signal for a bias voltage charge pump is provided on pin 15, + // allowing direct connection of the display to the RP2040 without level shifters. + let p = embassy_rp::init(Default::default()); + + let _pwm = Pwm::new_output_b(p.PWM_CH7, p.PIN_15, { + let mut c = pwm::Config::default(); + c.divider = 125.into(); + c.top = 100; + c.compare_b = 50; + c + }); + + let mut hd = HD44780::new( + p.PIO0, Irqs, p.DMA_CH3, p.PIN_0, p.PIN_1, p.PIN_2, p.PIN_3, p.PIN_4, p.PIN_5, p.PIN_6, + ) + .await; + + loop { + struct Buf([u8; N], usize); + impl Write for Buf { + fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> { + for b in s.as_bytes() { + if self.1 >= N { + return Err(core::fmt::Error); + } + self.0[self.1] = *b; + self.1 += 1; + } + Ok(()) + } + } + let mut buf = Buf([0; 16], 0); + write!(buf, "up {}s", Instant::now().as_micros() as f32 / 1e6).unwrap(); + hd.add_line(&buf.0[0..buf.1]).await; + Timer::after(Duration::from_secs(1)).await; + } +} + +pub struct HD44780<'l> { + dma: PeripheralRef<'l, AnyChannel>, + sm: StateMachine<'l, PIO0, 0>, + + buf: [u8; 40], +} + +impl<'l> HD44780<'l> { + pub async fn new( + pio: impl Peripheral

+ 'l, + irq: Irqs, + dma: impl Peripheral

+ 'l, + rs: impl PioPin, + rw: impl PioPin, + e: impl PioPin, + db4: impl PioPin, + db5: impl PioPin, + db6: impl PioPin, + db7: impl PioPin, + ) -> HD44780<'l> { + into_ref!(dma); + + let Pio { + mut common, + mut irq0, + mut sm0, + .. + } = Pio::new(pio, irq); + + // takes command words ( <0:4>) + let prg = pio_proc::pio_asm!( + r#" + .side_set 1 opt + .origin 20 + + loop: + out x, 24 + delay: + jmp x--, delay + out pins, 4 side 1 + out null, 4 side 0 + jmp !osre, loop + irq 0 + "#, + ); + + let rs = common.make_pio_pin(rs); + let rw = common.make_pio_pin(rw); + let e = common.make_pio_pin(e); + let db4 = common.make_pio_pin(db4); + let db5 = common.make_pio_pin(db5); + let db6 = common.make_pio_pin(db6); + let db7 = common.make_pio_pin(db7); + + sm0.set_pin_dirs(Direction::Out, &[&rs, &rw, &e, &db4, &db5, &db6, &db7]); + + let relocated = RelocatedProgram::new(&prg.program); + let mut cfg = Config::default(); + cfg.use_program(&common.load_program(&relocated), &[&e]); + cfg.clock_divider = 125u8.into(); + cfg.set_out_pins(&[&db4, &db5, &db6, &db7]); + cfg.shift_out = ShiftConfig { + auto_fill: true, + direction: ShiftDirection::Left, + threshold: 32, + }; + cfg.fifo_join = FifoJoin::TxOnly; + sm0.set_config(&cfg); + + sm0.set_enable(true); + // init to 8 bit thrice + sm0.tx().push((50000 << 8) | 0x30); + sm0.tx().push((5000 << 8) | 0x30); + sm0.tx().push((200 << 8) | 0x30); + // init 4 bit + sm0.tx().push((200 << 8) | 0x20); + // set font and lines + sm0.tx().push((50 << 8) | 0x20); + sm0.tx().push(0b1100_0000); + + irq0.wait().await; + sm0.set_enable(false); + + // takes command sequences ( , data...) + // many side sets are only there to free up a delay bit! + let prg = pio_proc::pio_asm!( + r#" + .origin 27 + .side_set 1 + + .wrap_target + pull side 0 + out x 1 side 0 ; !rs + out y 7 side 0 ; #data - 1 + + ; rs/rw to e: >= 60ns + ; e high time: >= 500ns + ; e low time: >= 500ns + ; read data valid after e falling: ~5ns + ; write data hold after e falling: ~10ns + + loop: + pull side 0 + jmp !x data side 0 + command: + set pins 0b00 side 0 + jmp shift side 0 + data: + set pins 0b01 side 0 + shift: + out pins 4 side 1 [9] + nop side 0 [9] + out pins 4 side 1 [9] + mov osr null side 0 [7] + out pindirs 4 side 0 + set pins 0b10 side 0 + busy: + nop side 1 [9] + jmp pin more side 0 [9] + mov osr ~osr side 1 [9] + nop side 0 [4] + out pindirs 4 side 0 + jmp y-- loop side 0 + .wrap + more: + nop side 1 [9] + jmp busy side 0 [9] + "# + ); + + let relocated = RelocatedProgram::new(&prg.program); + let mut cfg = Config::default(); + cfg.use_program(&common.load_program(&relocated), &[&e]); + cfg.clock_divider = 8u8.into(); // ~64ns/insn + cfg.set_jmp_pin(&db7); + cfg.set_set_pins(&[&rs, &rw]); + cfg.set_out_pins(&[&db4, &db5, &db6, &db7]); + cfg.shift_out.direction = ShiftDirection::Left; + cfg.fifo_join = FifoJoin::TxOnly; + sm0.set_config(&cfg); + + sm0.set_enable(true); + + // display on and cursor on and blinking, reset display + sm0.tx().dma_push(dma.reborrow(), &[0x81u8, 0x0f, 1]).await; + + Self { + dma: dma.map_into(), + sm: sm0, + buf: [0x20; 40], + } + } + + pub async fn add_line(&mut self, s: &[u8]) { + // move cursor to 0:0, prepare 16 characters + self.buf[..3].copy_from_slice(&[0x80, 0x80, 15]); + // move line 2 up + self.buf.copy_within(22..38, 3); + // move cursor to 1:0, prepare 16 characters + self.buf[19..22].copy_from_slice(&[0x80, 0xc0, 15]); + // file line 2 with spaces + self.buf[22..38].fill(0x20); + // copy input line + let len = s.len().min(16); + self.buf[22..22 + len].copy_from_slice(&s[0..len]); + // set cursor to 1:15 + self.buf[38..].copy_from_slice(&[0x80, 0xcf]); + + self.sm.tx().dma_push(self.dma.reborrow(), &self.buf).await; + } +} diff --git a/examples/rp/src/bin/pio_ws2812.rs b/examples/rp/src/bin/pio_ws2812.rs new file mode 100644 index 000000000..3de2bd48d --- /dev/null +++ b/examples/rp/src/bin/pio_ws2812.rs @@ -0,0 +1,160 @@ +//! This example shows powerful PIO module in the RP2040 chip to communicate with WS2812 LED modules. +//! See (https://www.sparkfun.com/categories/tags/ws2812) + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::dma::{AnyChannel, Channel}; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{ + Common, Config, FifoJoin, Instance, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine, +}; +use embassy_rp::relocate::RelocatedProgram; +use embassy_rp::{bind_interrupts, clocks, into_ref, Peripheral, PeripheralRef}; +use embassy_time::{Duration, Timer}; +use fixed::types::U24F8; +use fixed_macro::fixed; +use smart_leds::RGB8; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +pub struct Ws2812<'d, P: Instance, const S: usize, const N: usize> { + dma: PeripheralRef<'d, AnyChannel>, + sm: StateMachine<'d, P, S>, +} + +impl<'d, P: Instance, const S: usize, const N: usize> Ws2812<'d, P, S, N> { + pub fn new( + pio: &mut Common<'d, P>, + mut sm: StateMachine<'d, P, S>, + dma: impl Peripheral

+ 'd, + pin: impl PioPin, + ) -> Self { + into_ref!(dma); + + // Setup sm0 + + // prepare the PIO program + let side_set = pio::SideSet::new(false, 1, false); + let mut a: pio::Assembler<32> = pio::Assembler::new_with_side_set(side_set); + + const T1: u8 = 2; // start bit + const T2: u8 = 5; // data bit + const T3: u8 = 3; // stop bit + const CYCLES_PER_BIT: u32 = (T1 + T2 + T3) as u32; + + let mut wrap_target = a.label(); + let mut wrap_source = a.label(); + let mut do_zero = a.label(); + a.set_with_side_set(pio::SetDestination::PINDIRS, 1, 0); + a.bind(&mut wrap_target); + // Do stop bit + a.out_with_delay_and_side_set(pio::OutDestination::X, 1, T3 - 1, 0); + // Do start bit + a.jmp_with_delay_and_side_set(pio::JmpCondition::XIsZero, &mut do_zero, T1 - 1, 1); + // Do data bit = 1 + a.jmp_with_delay_and_side_set(pio::JmpCondition::Always, &mut wrap_target, T2 - 1, 1); + a.bind(&mut do_zero); + // Do data bit = 0 + a.nop_with_delay_and_side_set(T2 - 1, 0); + a.bind(&mut wrap_source); + + let prg = a.assemble_with_wrap(wrap_source, wrap_target); + let mut cfg = Config::default(); + + // Pin config + let out_pin = pio.make_pio_pin(pin); + cfg.set_out_pins(&[&out_pin]); + cfg.set_set_pins(&[&out_pin]); + + let relocated = RelocatedProgram::new(&prg); + cfg.use_program(&pio.load_program(&relocated), &[&out_pin]); + + // Clock config, measured in kHz to avoid overflows + // TODO CLOCK_FREQ should come from embassy_rp + let clock_freq = U24F8::from_num(clocks::clk_sys_freq() / 1000); + let ws2812_freq = fixed!(800: U24F8); + let bit_freq = ws2812_freq * CYCLES_PER_BIT; + cfg.clock_divider = clock_freq / bit_freq; + + // FIFO config + cfg.fifo_join = FifoJoin::TxOnly; + cfg.shift_out = ShiftConfig { + auto_fill: true, + threshold: 24, + direction: ShiftDirection::Left, + }; + + sm.set_config(&cfg); + sm.set_enable(true); + + Self { + dma: dma.map_into(), + sm, + } + } + + pub async fn write(&mut self, colors: &[RGB8; N]) { + // Precompute the word bytes from the colors + let mut words = [0u32; N]; + for i in 0..N { + let word = (u32::from(colors[i].g) << 24) | (u32::from(colors[i].r) << 16) | (u32::from(colors[i].b) << 8); + words[i] = word; + } + + // DMA transfer + self.sm.tx().dma_push(self.dma.reborrow(), &words).await; + } +} + +/// Input a value 0 to 255 to get a color value +/// The colours are a transition r - g - b - back to r. +fn wheel(mut wheel_pos: u8) -> RGB8 { + wheel_pos = 255 - wheel_pos; + if wheel_pos < 85 { + return (255 - wheel_pos * 3, 0, wheel_pos * 3).into(); + } + if wheel_pos < 170 { + wheel_pos -= 85; + return (0, wheel_pos * 3, 255 - wheel_pos * 3).into(); + } + wheel_pos -= 170; + (wheel_pos * 3, 255 - wheel_pos * 3, 0).into() +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Start"); + let p = embassy_rp::init(Default::default()); + + let Pio { mut common, sm0, .. } = Pio::new(p.PIO0, Irqs); + + // This is the number of leds in the string. Helpfully, the sparkfun thing plus and adafruit + // feather boards for the 2040 both have one built in. + const NUM_LEDS: usize = 1; + let mut data = [RGB8::default(); NUM_LEDS]; + + // For the thing plus, use pin 8 + // For the feather, use pin 16 + let mut ws2812 = Ws2812::new(&mut common, sm0, p.DMA_CH0, p.PIN_16); + + // Loop forever making RGB values and pushing them out to the WS2812. + loop { + for j in 0..(256 * 5) { + debug!("New Colors:"); + for i in 0..NUM_LEDS { + data[i] = wheel((((i * 256) as u16 / NUM_LEDS as u16 + j as u16) & 255) as u8); + debug!("R: {} G: {} B: {}", data[i].r, data[i].g, data[i].b); + } + ws2812.write(&data).await; + + Timer::after(Duration::from_micros(5)).await; + } + } +} diff --git a/examples/rp/src/bin/pwm.rs b/examples/rp/src/bin/pwm.rs new file mode 100644 index 000000000..9d919287c --- /dev/null +++ b/examples/rp/src/bin/pwm.rs @@ -0,0 +1,30 @@ +//! This example shows how to use PWM (Pulse Width Modulation) in the RP2040 chip. +//! +//! The LED on the RP Pico W board is connected differently. Add a LED and resistor to another pin. + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::pwm::{Config, Pwm}; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + let mut c: Config = Default::default(); + c.top = 0x8000; + c.compare_b = 8; + let mut pwm = Pwm::new_output_b(p.PWM_CH4, p.PIN_25, c.clone()); + + loop { + info!("current LED duty cycle: {}/32768", c.compare_b); + Timer::after(Duration::from_secs(1)).await; + c.compare_b = c.compare_b.rotate_left(4); + pwm.set_config(&c); + } +} diff --git a/examples/rp/src/bin/rtc.rs b/examples/rp/src/bin/rtc.rs new file mode 100644 index 000000000..15aa8243f --- /dev/null +++ b/examples/rp/src/bin/rtc.rs @@ -0,0 +1,46 @@ +//! This example shows how to use RTC (Real Time Clock) in the RP2040 chip. + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::rtc::{DateTime, DayOfWeek, Rtc}; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Wait for 20s"); + + let mut rtc = Rtc::new(p.RTC); + + if !rtc.is_running() { + info!("Start RTC"); + let now = DateTime { + year: 2000, + month: 1, + day: 1, + day_of_week: DayOfWeek::Saturday, + hour: 0, + minute: 0, + second: 0, + }; + rtc.set_datetime(now).unwrap(); + } + + Timer::after(Duration::from_millis(20000)).await; + + if let Ok(dt) = rtc.now() { + info!( + "Now: {}-{:02}-{:02} {}:{:02}:{:02}", + dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second, + ); + } + + info!("Reboot."); + Timer::after(Duration::from_millis(200)).await; + cortex_m::peripheral::SCB::sys_reset(); +} diff --git a/examples/rp/src/bin/spi.rs b/examples/rp/src/bin/spi.rs index 88003ee17..602348f7a 100644 --- a/examples/rp/src/bin/spi.rs +++ b/examples/rp/src/bin/spi.rs @@ -1,3 +1,7 @@ +//! This example shows how to use SPI (Serial Peripheral Interface) in the RP2040 chip. +//! +//! Example for resistive touch sensor in Waveshare Pico-ResTouch + #![no_std] #![no_main] #![feature(type_alias_impl_trait)] @@ -24,7 +28,7 @@ async fn main(_spawner: Spawner) { // create SPI let mut config = spi::Config::default(); config.frequency = 2_000_000; - let mut spi = Spi::new(p.SPI1, clk, mosi, miso, config); + let mut spi = Spi::new_blocking(p.SPI1, clk, mosi, miso, config); // Configure CS let mut cs = Output::new(touch_cs, Level::Low); diff --git a/examples/rp/src/bin/spi_async.rs b/examples/rp/src/bin/spi_async.rs new file mode 100644 index 000000000..328074e8b --- /dev/null +++ b/examples/rp/src/bin/spi_async.rs @@ -0,0 +1,32 @@ +//! This example shows how to use SPI (Serial Peripheral Interface) in the RP2040 chip. +//! No specific hardware is specified in this example. If you connect pin 11 and 12 you should get the same data back. + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::spi::{Config, Spi}; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + let miso = p.PIN_12; + let mosi = p.PIN_11; + let clk = p.PIN_10; + + let mut spi = Spi::new(p.SPI1, clk, mosi, miso, p.DMA_CH0, p.DMA_CH1, Config::default()); + + loop { + let tx_buf = [1_u8, 2, 3, 4, 5, 6]; + let mut rx_buf = [0_u8; 6]; + spi.transfer(&mut rx_buf, &tx_buf).await.unwrap(); + info!("{:?}", rx_buf); + Timer::after(Duration::from_secs(1)).await; + } +} diff --git a/examples/rp/src/bin/spi_display.rs b/examples/rp/src/bin/spi_display.rs index f0e54d87f..26c258e1c 100644 --- a/examples/rp/src/bin/spi_display.rs +++ b/examples/rp/src/bin/spi_display.rs @@ -1,3 +1,8 @@ +//! This example shows how to use SPI (Serial Peripheral Interface) in the RP2040 chip. +//! +//! Example written for a display using the ST7789 chip. Possibly the Waveshare Pico-ResTouch +//! (https://www.waveshare.com/wiki/Pico-ResTouch-LCD-2.8) + #![no_std] #![no_main] #![feature(type_alias_impl_trait)] @@ -5,10 +10,13 @@ use core::cell::RefCell; use defmt::*; +use embassy_embedded_hal::shared_bus::blocking::spi::SpiDeviceWithConfig; use embassy_executor::Spawner; use embassy_rp::gpio::{Level, Output}; use embassy_rp::spi; -use embassy_rp::spi::Spi; +use embassy_rp::spi::{Blocking, Spi}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_sync::blocking_mutex::Mutex; use embassy_time::Delay; use embedded_graphics::image::{Image, ImageRawLE}; use embedded_graphics::mono_font::ascii::FONT_10X20; @@ -21,10 +29,9 @@ use st7789::{Orientation, ST7789}; use {defmt_rtt as _, panic_probe as _}; use crate::my_display_interface::SPIDeviceInterface; -use crate::shared_spi::SpiDeviceWithCs; use crate::touch::Touch; -//const DISPLAY_FREQ: u32 = 64_000_000; +const DISPLAY_FREQ: u32 = 64_000_000; const TOUCH_FREQ: u32 = 200_000; #[embassy_executor::main] @@ -43,15 +50,20 @@ async fn main(_spawner: Spawner) { //let touch_irq = p.PIN_17; // create SPI - let mut config = spi::Config::default(); - config.frequency = TOUCH_FREQ; // use the lowest freq - config.phase = spi::Phase::CaptureOnSecondTransition; - config.polarity = spi::Polarity::IdleHigh; + let mut display_config = spi::Config::default(); + display_config.frequency = DISPLAY_FREQ; + display_config.phase = spi::Phase::CaptureOnSecondTransition; + display_config.polarity = spi::Polarity::IdleHigh; + let mut touch_config = spi::Config::default(); + touch_config.frequency = TOUCH_FREQ; + touch_config.phase = spi::Phase::CaptureOnSecondTransition; + touch_config.polarity = spi::Polarity::IdleHigh; - let spi_bus = RefCell::new(Spi::new(p.SPI1, clk, mosi, miso, config)); + let spi: Spi<'_, _, Blocking> = Spi::new_blocking(p.SPI1, clk, mosi, miso, touch_config.clone()); + let spi_bus: Mutex = Mutex::new(RefCell::new(spi)); - let display_spi = SpiDeviceWithCs::new(&spi_bus, Output::new(display_cs, Level::High)); - let touch_spi = SpiDeviceWithCs::new(&spi_bus, Output::new(touch_cs, Level::High)); + let display_spi = SpiDeviceWithConfig::new(&spi_bus, Output::new(display_cs, Level::High), display_config); + let touch_spi = SpiDeviceWithConfig::new(&spi_bus, Output::new(touch_cs, Level::High), touch_config); let mut touch = Touch::new(touch_spi); @@ -103,85 +115,9 @@ async fn main(_spawner: Spawner) { } } -mod shared_spi { - use core::cell::RefCell; - use core::fmt::Debug; - - use embedded_hal_1::digital::blocking::OutputPin; - use embedded_hal_1::spi; - use embedded_hal_1::spi::blocking::SpiDevice; - - #[derive(Copy, Clone, Eq, PartialEq, Debug)] - pub enum SpiDeviceWithCsError { - #[allow(unused)] // will probably use in the future when adding a flush() to SpiBus - Spi(BUS), - Cs(CS), - } - - impl spi::Error for SpiDeviceWithCsError - where - BUS: spi::Error + Debug, - CS: Debug, - { - fn kind(&self) -> spi::ErrorKind { - match self { - Self::Spi(e) => e.kind(), - Self::Cs(_) => spi::ErrorKind::Other, - } - } - } - - pub struct SpiDeviceWithCs<'a, BUS, CS> { - bus: &'a RefCell, - cs: CS, - } - - impl<'a, BUS, CS> SpiDeviceWithCs<'a, BUS, CS> { - pub fn new(bus: &'a RefCell, cs: CS) -> Self { - Self { bus, cs } - } - } - - impl<'a, BUS, CS> spi::ErrorType for SpiDeviceWithCs<'a, BUS, CS> - where - BUS: spi::ErrorType, - CS: OutputPin, - { - type Error = SpiDeviceWithCsError; - } - - impl<'a, BUS, CS> SpiDevice for SpiDeviceWithCs<'a, BUS, CS> - where - BUS: spi::blocking::SpiBusFlush, - CS: OutputPin, - { - type Bus = BUS; - - fn transaction( - &mut self, - f: impl FnOnce(&mut Self::Bus) -> Result, - ) -> Result { - let mut bus = self.bus.borrow_mut(); - self.cs.set_low().map_err(SpiDeviceWithCsError::Cs)?; - - let f_res = f(&mut bus); - - // On failure, it's important to still flush and deassert CS. - let flush_res = bus.flush(); - let cs_res = self.cs.set_high(); - - let f_res = f_res.map_err(SpiDeviceWithCsError::Spi)?; - flush_res.map_err(SpiDeviceWithCsError::Spi)?; - cs_res.map_err(SpiDeviceWithCsError::Cs)?; - - Ok(f_res) - } - } -} - /// Driver for the XPT2046 resistive touchscreen sensor mod touch { - use embedded_hal_1::spi::blocking::{SpiBus, SpiBusRead, SpiBusWrite, SpiDevice}; + use embedded_hal_1::spi::{Operation, SpiDevice}; struct Calibration { x1: i32, @@ -208,7 +144,6 @@ mod touch { impl Touch where SPI: SpiDevice, - SPI::Bus: SpiBus, { pub fn new(spi: SPI) -> Self { Self { spi } @@ -218,13 +153,12 @@ mod touch { let mut x = [0; 2]; let mut y = [0; 2]; self.spi - .transaction(|bus| { - bus.write(&[0x90])?; - bus.read(&mut x)?; - bus.write(&[0xd0])?; - bus.read(&mut y)?; - Ok(()) - }) + .transaction(&mut [ + Operation::Write(&[0x90]), + Operation::Read(&mut x), + Operation::Write(&[0xd0]), + Operation::Read(&mut y), + ]) .unwrap(); let x = (u16::from_be_bytes(x) >> 3) as i32; @@ -245,8 +179,8 @@ mod touch { mod my_display_interface { use display_interface::{DataFormat, DisplayError, WriteOnlyDataCommand}; - use embedded_hal_1::digital::blocking::OutputPin; - use embedded_hal_1::spi::blocking::{SpiBusWrite, SpiDevice}; + use embedded_hal_1::digital::OutputPin; + use embedded_hal_1::spi::SpiDevice; /// SPI display interface. /// @@ -259,7 +193,6 @@ mod my_display_interface { impl SPIDeviceInterface where SPI: SpiDevice, - SPI::Bus: SpiBusWrite, DC: OutputPin, { /// Create new SPI interface for communciation with a display driver @@ -271,41 +204,26 @@ mod my_display_interface { impl WriteOnlyDataCommand for SPIDeviceInterface where SPI: SpiDevice, - SPI::Bus: SpiBusWrite, DC: OutputPin, { fn send_commands(&mut self, cmds: DataFormat<'_>) -> Result<(), DisplayError> { - let r = self.spi.transaction(|bus| { - // 1 = data, 0 = command - if let Err(_) = self.dc.set_low() { - return Ok(Err(DisplayError::DCError)); - } + // 1 = data, 0 = command + self.dc.set_low().map_err(|_| DisplayError::DCError)?; - // Send words over SPI - send_u8(bus, cmds)?; - - Ok(Ok(())) - }); - r.map_err(|_| DisplayError::BusWriteError)? + send_u8(&mut self.spi, cmds).map_err(|_| DisplayError::BusWriteError)?; + Ok(()) } fn send_data(&mut self, buf: DataFormat<'_>) -> Result<(), DisplayError> { - let r = self.spi.transaction(|bus| { - // 1 = data, 0 = command - if let Err(_) = self.dc.set_high() { - return Ok(Err(DisplayError::DCError)); - } + // 1 = data, 0 = command + self.dc.set_high().map_err(|_| DisplayError::DCError)?; - // Send words over SPI - send_u8(bus, buf)?; - - Ok(Ok(())) - }); - r.map_err(|_| DisplayError::BusWriteError)? + send_u8(&mut self.spi, buf).map_err(|_| DisplayError::BusWriteError)?; + Ok(()) } } - fn send_u8(spi: &mut T, words: DataFormat<'_>) -> Result<(), T::Error> { + fn send_u8(spi: &mut T, words: DataFormat<'_>) -> Result<(), T::Error> { match words { DataFormat::U8(slice) => spi.write(slice), DataFormat::U16(slice) => { diff --git a/examples/rp/src/bin/uart.rs b/examples/rp/src/bin/uart.rs index c63b31cae..451c3c396 100644 --- a/examples/rp/src/bin/uart.rs +++ b/examples/rp/src/bin/uart.rs @@ -1,3 +1,9 @@ +//! This example shows how to use UART (Universal asynchronous receiver-transmitter) in the RP2040 chip. +//! +//! No specific hardware is specified in this example. Only output on pin 0 is tested. +//! The Raspberry Pi Debug Probe (https://www.raspberrypi.com/products/debug-probe/) could be used +//! with its UART port. + #![no_std] #![no_main] #![feature(type_alias_impl_trait)] @@ -10,7 +16,7 @@ use {defmt_rtt as _, panic_probe as _}; async fn main(_spawner: Spawner) { let p = embassy_rp::init(Default::default()); let config = uart::Config::default(); - let mut uart = uart::Uart::new_with_rtscts(p.UART0, p.PIN_0, p.PIN_1, p.PIN_2, p.PIN_3, config); + let mut uart = uart::Uart::new_with_rtscts_blocking(p.UART0, p.PIN_0, p.PIN_1, p.PIN_3, p.PIN_2, config); uart.blocking_write("Hello World!\r\n".as_bytes()).unwrap(); loop { diff --git a/examples/rp/src/bin/uart_buffered_split.rs b/examples/rp/src/bin/uart_buffered_split.rs new file mode 100644 index 000000000..735201718 --- /dev/null +++ b/examples/rp/src/bin/uart_buffered_split.rs @@ -0,0 +1,57 @@ +//! This example shows how to use UART (Universal asynchronous receiver-transmitter) in the RP2040 chip. +//! +//! No specific hardware is specified in this example. If you connect pin 0 and 1 you should get the same data back. +//! The Raspberry Pi Debug Probe (https://www.raspberrypi.com/products/debug-probe/) could be used +//! with its UART port. + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::UART0; +use embassy_rp::uart::{BufferedInterruptHandler, BufferedUart, BufferedUartRx, Config}; +use embassy_time::{Duration, Timer}; +use embedded_io::asynch::{Read, Write}; +use static_cell::make_static; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + UART0_IRQ => BufferedInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let (tx_pin, rx_pin, uart) = (p.PIN_0, p.PIN_1, p.UART0); + + let tx_buf = &mut make_static!([0u8; 16])[..]; + let rx_buf = &mut make_static!([0u8; 16])[..]; + let uart = BufferedUart::new(uart, Irqs, tx_pin, rx_pin, tx_buf, rx_buf, Config::default()); + let (rx, mut tx) = uart.split(); + + unwrap!(spawner.spawn(reader(rx))); + + info!("Writing..."); + loop { + let data = [ + 1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, + 29, 30, 31, + ]; + info!("TX {:?}", data); + tx.write_all(&data).await.unwrap(); + Timer::after(Duration::from_secs(1)).await; + } +} + +#[embassy_executor::task] +async fn reader(mut rx: BufferedUartRx<'static, UART0>) { + info!("Reading..."); + loop { + let mut buf = [0; 31]; + rx.read_exact(&mut buf).await.unwrap(); + info!("RX {:?}", buf); + } +} diff --git a/examples/rp/src/bin/uart_unidir.rs b/examples/rp/src/bin/uart_unidir.rs new file mode 100644 index 000000000..c1515a911 --- /dev/null +++ b/examples/rp/src/bin/uart_unidir.rs @@ -0,0 +1,51 @@ +//! This example shows how to use UART (Universal asynchronous receiver-transmitter) in the RP2040 chip. +//! +//! Test TX-only and RX-only on two different UARTs. You need to connect GPIO0 to GPIO5 for +//! this to work +//! The Raspberry Pi Debug Probe (https://www.raspberrypi.com/products/debug-probe/) could be used +//! with its UART port. + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::UART1; +use embassy_rp::uart::{Async, Config, InterruptHandler, UartRx, UartTx}; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + UART1_IRQ => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + let mut uart_tx = UartTx::new(p.UART0, p.PIN_0, p.DMA_CH0, Config::default()); + let uart_rx = UartRx::new(p.UART1, p.PIN_5, Irqs, p.DMA_CH1, Config::default()); + + unwrap!(spawner.spawn(reader(uart_rx))); + + info!("Writing..."); + loop { + let data = [1u8, 2, 3, 4, 5, 6, 7, 8]; + info!("TX {:?}", data); + uart_tx.write(&data).await.unwrap(); + Timer::after(Duration::from_secs(1)).await; + } +} + +#[embassy_executor::task] +async fn reader(mut rx: UartRx<'static, UART1, Async>) { + info!("Reading..."); + loop { + // read a total of 4 transmissions (32 / 8) and then print the result + let mut buf = [0; 32]; + rx.read(&mut buf).await.unwrap(); + info!("RX {:?}", buf); + } +} diff --git a/examples/rp/src/bin/usb_ethernet.rs b/examples/rp/src/bin/usb_ethernet.rs new file mode 100644 index 000000000..0a08f667e --- /dev/null +++ b/examples/rp/src/bin/usb_ethernet.rs @@ -0,0 +1,155 @@ +//! This example shows how to use USB (Universal Serial Bus) in the RP2040 chip. +//! +//! This is a CDC-NCM class implementation, aka Ethernet over USB. + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Stack, StackResources}; +use embassy_rp::peripherals::USB; +use embassy_rp::usb::{Driver, InterruptHandler}; +use embassy_rp::{bind_interrupts, peripherals}; +use embassy_usb::class::cdc_ncm::embassy_net::{Device, Runner, State as NetState}; +use embassy_usb::class::cdc_ncm::{CdcNcmClass, State}; +use embassy_usb::{Builder, Config, UsbDevice}; +use embedded_io::asynch::Write; +use static_cell::make_static; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USBCTRL_IRQ => InterruptHandler; +}); + +type MyDriver = Driver<'static, peripherals::USB>; + +const MTU: usize = 1514; + +#[embassy_executor::task] +async fn usb_task(mut device: UsbDevice<'static, MyDriver>) -> ! { + device.run().await +} + +#[embassy_executor::task] +async fn usb_ncm_task(class: Runner<'static, MyDriver, MTU>) -> ! { + class.run().await +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + + // Create the driver, from the HAL. + let driver = Driver::new(p.USB, Irqs); + + // Create embassy-usb Config + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-Ethernet example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Required for Windows support. + config.composite_with_iads = true; + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + + // Create embassy-usb DeviceBuilder using the driver and config. + let mut builder = Builder::new( + driver, + config, + &mut make_static!([0; 256])[..], + &mut make_static!([0; 256])[..], + &mut make_static!([0; 256])[..], + &mut make_static!([0; 128])[..], + ); + + // Our MAC addr. + let our_mac_addr = [0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC]; + // Host's MAC addr. This is the MAC the host "thinks" its USB-to-ethernet adapter has. + let host_mac_addr = [0x88, 0x88, 0x88, 0x88, 0x88, 0x88]; + + // Create classes on the builder. + let class = CdcNcmClass::new(&mut builder, make_static!(State::new()), host_mac_addr, 64); + + // Build the builder. + let usb = builder.build(); + + unwrap!(spawner.spawn(usb_task(usb))); + + let (runner, device) = class.into_embassy_net_device::(make_static!(NetState::new()), our_mac_addr); + unwrap!(spawner.spawn(usb_ncm_task(runner))); + + let config = embassy_net::Config::dhcpv4(Default::default()); + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), + //}); + + // Generate random seed + let seed = 1234; // guaranteed random, chosen by a fair dice roll + + // Init network stack + let stack = &*make_static!(Stack::new( + device, + config, + make_static!(StackResources::<2>::new()), + seed + )); + + unwrap!(spawner.spawn(net_task(stack))); + + // And now we can use it! + + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut buf = [0; 4096]; + + loop { + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); + + info!("Listening on TCP:1234..."); + if let Err(e) = socket.accept(1234).await { + warn!("accept error: {:?}", e); + continue; + } + + info!("Received connection from {:?}", socket.remote_endpoint()); + + loop { + let n = match socket.read(&mut buf).await { + Ok(0) => { + warn!("read EOF"); + break; + } + Ok(n) => n, + Err(e) => { + warn!("read error: {:?}", e); + break; + } + }; + + info!("rxd {:02x}", &buf[..n]); + + match socket.write_all(&buf[..n]).await { + Ok(()) => {} + Err(e) => { + warn!("write error: {:?}", e); + break; + } + }; + } + } +} diff --git a/examples/rp/src/bin/usb_hid_keyboard.rs b/examples/rp/src/bin/usb_hid_keyboard.rs new file mode 100644 index 000000000..99af1f02f --- /dev/null +++ b/examples/rp/src/bin/usb_hid_keyboard.rs @@ -0,0 +1,188 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use core::sync::atomic::{AtomicBool, Ordering}; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_rp::bind_interrupts; +use embassy_rp::gpio::{Input, Pull}; +use embassy_rp::peripherals::USB; +use embassy_rp::usb::{Driver, InterruptHandler}; +use embassy_usb::class::hid::{HidReaderWriter, ReportId, RequestHandler, State}; +use embassy_usb::control::OutResponse; +use embassy_usb::{Builder, Config, Handler}; +use usbd_hid::descriptor::{KeyboardReport, SerializedDescriptor}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USBCTRL_IRQ => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + // Create the driver, from the HAL. + let driver = Driver::new(p.USB, Irqs); + + // Create embassy-usb Config + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("HID keyboard example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut device_descriptor = [0; 256]; + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + // You can also add a Microsoft OS descriptor. + // let mut msos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + let request_handler = MyRequestHandler {}; + let mut device_handler = MyDeviceHandler::new(); + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut device_descriptor, + &mut config_descriptor, + &mut bos_descriptor, + // &mut msos_descriptor, + &mut control_buf, + ); + + builder.handler(&mut device_handler); + + // Create classes on the builder. + let config = embassy_usb::class::hid::Config { + report_descriptor: KeyboardReport::desc(), + request_handler: Some(&request_handler), + poll_ms: 60, + max_packet_size: 64, + }; + let hid = HidReaderWriter::<_, 1, 8>::new(&mut builder, &mut state, config); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Set up the signal pin that will be used to trigger the keyboard. + let mut signal_pin = Input::new(p.PIN_16, Pull::None); + + let (reader, mut writer) = hid.split(); + + // Do stuff with the class! + let in_fut = async { + loop { + info!("Waiting for HIGH on pin 16"); + signal_pin.wait_for_high().await; + info!("HIGH DETECTED"); + // Create a report with the A key pressed. (no shift modifier) + let report = KeyboardReport { + keycodes: [4, 0, 0, 0, 0, 0], + leds: 0, + modifier: 0, + reserved: 0, + }; + // Send the report. + match writer.write_serialize(&report).await { + Ok(()) => {} + Err(e) => warn!("Failed to send report: {:?}", e), + }; + signal_pin.wait_for_low().await; + info!("LOW DETECTED"); + let report = KeyboardReport { + keycodes: [0, 0, 0, 0, 0, 0], + leds: 0, + modifier: 0, + reserved: 0, + }; + match writer.write_serialize(&report).await { + Ok(()) => {} + Err(e) => warn!("Failed to send report: {:?}", e), + }; + } + }; + + let out_fut = async { + reader.run(false, &request_handler).await; + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, join(in_fut, out_fut)).await; +} + +struct MyRequestHandler {} + +impl RequestHandler for MyRequestHandler { + fn get_report(&self, id: ReportId, _buf: &mut [u8]) -> Option { + info!("Get report for {:?}", id); + None + } + + fn set_report(&self, id: ReportId, data: &[u8]) -> OutResponse { + info!("Set report for {:?}: {=[u8]}", id, data); + OutResponse::Accepted + } + + fn set_idle_ms(&self, id: Option, dur: u32) { + info!("Set idle rate for {:?} to {:?}", id, dur); + } + + fn get_idle_ms(&self, id: Option) -> Option { + info!("Get idle rate for {:?}", id); + None + } +} + +struct MyDeviceHandler { + configured: AtomicBool, +} + +impl MyDeviceHandler { + fn new() -> Self { + MyDeviceHandler { + configured: AtomicBool::new(false), + } + } +} + +impl Handler for MyDeviceHandler { + fn enabled(&mut self, enabled: bool) { + self.configured.store(false, Ordering::Relaxed); + if enabled { + info!("Device enabled"); + } else { + info!("Device disabled"); + } + } + + fn reset(&mut self) { + self.configured.store(false, Ordering::Relaxed); + info!("Bus reset, the Vbus current limit is 100mA"); + } + + fn addressed(&mut self, addr: u8) { + self.configured.store(false, Ordering::Relaxed); + info!("USB address set to: {}", addr); + } + + fn configured(&mut self, configured: bool) { + self.configured.store(configured, Ordering::Relaxed); + if configured { + info!("Device configured, it may now draw up to the configured current limit from Vbus.") + } else { + info!("Device is no longer configured, the Vbus current limit is 100mA."); + } + } +} diff --git a/examples/rp/src/bin/usb_logger.rs b/examples/rp/src/bin/usb_logger.rs new file mode 100644 index 000000000..9c5e6897d --- /dev/null +++ b/examples/rp/src/bin/usb_logger.rs @@ -0,0 +1,37 @@ +//! This example shows how to use USB (Universal Serial Bus) in the RP2040 chip. +//! +//! This creates the possibility to send log::info/warn/error/debug! to USB serial port. + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::USB; +use embassy_rp::usb::{Driver, InterruptHandler}; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USBCTRL_IRQ => InterruptHandler; +}); + +#[embassy_executor::task] +async fn logger_task(driver: Driver<'static, USB>) { + embassy_usb_logger::run!(1024, log::LevelFilter::Info, driver); +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let driver = Driver::new(p.USB, Irqs); + spawner.spawn(logger_task(driver)).unwrap(); + + let mut counter = 0; + loop { + counter += 1; + log::info!("Tick {}", counter); + Timer::after(Duration::from_secs(1)).await; + } +} diff --git a/examples/rp/src/bin/usb_serial.rs b/examples/rp/src/bin/usb_serial.rs new file mode 100644 index 000000000..164e2052d --- /dev/null +++ b/examples/rp/src/bin/usb_serial.rs @@ -0,0 +1,109 @@ +//! This example shows how to use USB (Universal Serial Bus) in the RP2040 chip. +//! +//! This creates a USB serial port that echos. + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::{info, panic}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::USB; +use embassy_rp::usb::{Driver, Instance, InterruptHandler}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::{Builder, Config}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USBCTRL_IRQ => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello there!"); + + let p = embassy_rp::init(Default::default()); + + // Create the driver, from the HAL. + let driver = Driver::new(p.USB, Irqs); + + // Create embassy-usb Config + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut device_descriptor = [0; 256]; + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut device_descriptor, + &mut config_descriptor, + &mut bos_descriptor, + &mut control_buf, + ); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} diff --git a/examples/rp/src/bin/watchdog.rs b/examples/rp/src/bin/watchdog.rs new file mode 100644 index 000000000..fe5eaf926 --- /dev/null +++ b/examples/rp/src/bin/watchdog.rs @@ -0,0 +1,52 @@ +//! This example shows how to use Watchdog in the RP2040 chip. +//! +//! It does not work with the RP Pico W board. See wifi_blinky.rs or connect external LED and resistor. + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_rp::gpio; +use embassy_rp::watchdog::*; +use embassy_time::{Duration, Timer}; +use gpio::{Level, Output}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello world!"); + + let mut watchdog = Watchdog::new(p.WATCHDOG); + let mut led = Output::new(p.PIN_25, Level::Low); + + // Set the LED high for 2 seconds so we know when we're about to start the watchdog + led.set_high(); + Timer::after(Duration::from_secs(2)).await; + + // Set to watchdog to reset if it's not fed within 1.05 seconds, and start it + watchdog.start(Duration::from_millis(1_050)); + info!("Started the watchdog timer"); + + // Blink once a second for 5 seconds, feed the watchdog timer once a second to avoid a reset + for _ in 1..=5 { + led.set_low(); + Timer::after(Duration::from_millis(500)).await; + led.set_high(); + Timer::after(Duration::from_millis(500)).await; + info!("Feeding watchdog"); + watchdog.feed(); + } + + info!("Stopped feeding, device will reset in 1.05 seconds"); + // Blink 10 times per second, not feeding the watchdog. + // The processor should reset in 1.05 seconds. + loop { + led.set_low(); + Timer::after(Duration::from_millis(100)).await; + led.set_high(); + Timer::after(Duration::from_millis(100)).await; + } +} diff --git a/examples/rp/src/bin/wifi_ap_tcp_server.rs b/examples/rp/src/bin/wifi_ap_tcp_server.rs new file mode 100644 index 000000000..e3e393445 --- /dev/null +++ b/examples/rp/src/bin/wifi_ap_tcp_server.rs @@ -0,0 +1,139 @@ +//! This example uses the RP Pico W board Wifi chip (cyw43). +//! Creates an Access point Wifi network and creates a TCP endpoint on port 1234. + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#![feature(async_fn_in_trait)] +#![allow(incomplete_features)] + +use core::str::from_utf8; + +use cyw43_pio::PioSpi; +use defmt::*; +use embassy_executor::Spawner; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Config, Stack, StackResources}; +use embassy_rp::bind_interrupts; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::{DMA_CH0, PIN_23, PIN_25, PIO0}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_time::Duration; +use embedded_io::asynch::Write; +use static_cell::make_static; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::task] +async fn wifi_task( + runner: cyw43::Runner<'static, Output<'static, PIN_23>, PioSpi<'static, PIN_25, PIO0, 0, DMA_CH0>>, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + info!("Hello World!"); + + let p = embassy_rp::init(Default::default()); + + let fw = include_bytes!("../../../../cyw43-firmware/43439A0.bin"); + let clm = include_bytes!("../../../../cyw43-firmware/43439A0_clm.bin"); + + // To make flashing faster for development, you may want to flash the firmwares independently + // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: + // probe-rs download 43439A0.bin --format bin --chip RP2040 --base-address 0x10100000 + // probe-rs download 43439A0_clm.bin --format bin --chip RP2040 --base-address 0x10140000 + //let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 224190) }; + //let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) }; + + let pwr = Output::new(p.PIN_23, Level::Low); + let cs = Output::new(p.PIN_25, Level::High); + let mut pio = Pio::new(p.PIO0, Irqs); + let spi = PioSpi::new(&mut pio.common, pio.sm0, pio.irq0, cs, p.PIN_24, p.PIN_29, p.DMA_CH0); + + let state = make_static!(cyw43::State::new()); + let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + unwrap!(spawner.spawn(wifi_task(runner))); + + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; + + // Use a link-local address for communication without DHCP server + let config = Config::ipv4_static(embassy_net::StaticConfigV4 { + address: embassy_net::Ipv4Cidr::new(embassy_net::Ipv4Address::new(169, 254, 1, 1), 16), + dns_servers: heapless::Vec::new(), + gateway: None, + }); + + // Generate random seed + let seed = 0x0123_4567_89ab_cdef; // chosen by fair dice roll. guarenteed to be random. + + // Init network stack + let stack = &*make_static!(Stack::new( + net_device, + config, + make_static!(StackResources::<2>::new()), + seed + )); + + unwrap!(spawner.spawn(net_task(stack))); + + //control.start_ap_open("cyw43", 5).await; + control.start_ap_wpa2("cyw43", "password", 5).await; + + // And now we can use it! + + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut buf = [0; 4096]; + + loop { + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + + control.gpio_set(0, false).await; + info!("Listening on TCP:1234..."); + if let Err(e) = socket.accept(1234).await { + warn!("accept error: {:?}", e); + continue; + } + + info!("Received connection from {:?}", socket.remote_endpoint()); + control.gpio_set(0, true).await; + + loop { + let n = match socket.read(&mut buf).await { + Ok(0) => { + warn!("read EOF"); + break; + } + Ok(n) => n, + Err(e) => { + warn!("read error: {:?}", e); + break; + } + }; + + info!("rxd {}", from_utf8(&buf[..n]).unwrap()); + + match socket.write_all(&buf[..n]).await { + Ok(()) => {} + Err(e) => { + warn!("write error: {:?}", e); + break; + } + }; + } + } +} diff --git a/examples/rp/src/bin/wifi_blinky.rs b/examples/rp/src/bin/wifi_blinky.rs new file mode 100644 index 000000000..33d43788c --- /dev/null +++ b/examples/rp/src/bin/wifi_blinky.rs @@ -0,0 +1,68 @@ +//! This example test the RP Pico W on board LED. +//! +//! It does not work with the RP Pico board. See blinky.rs. + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use cyw43_pio::PioSpi; +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::{DMA_CH0, PIN_23, PIN_25, PIO0}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_time::{Duration, Timer}; +use static_cell::make_static; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::task] +async fn wifi_task( + runner: cyw43::Runner<'static, Output<'static, PIN_23>, PioSpi<'static, PIN_25, PIO0, 0, DMA_CH0>>, +) -> ! { + runner.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let fw = include_bytes!("../../../../cyw43-firmware/43439A0.bin"); + let clm = include_bytes!("../../../../cyw43-firmware/43439A0_clm.bin"); + + // To make flashing faster for development, you may want to flash the firmwares independently + // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: + // probe-rs download 43439A0.bin --format bin --chip RP2040 --base-address 0x10100000 + // probe-rs download 43439A0_clm.bin --format bin --chip RP2040 --base-address 0x10140000 + //let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 224190) }; + //let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) }; + + let pwr = Output::new(p.PIN_23, Level::Low); + let cs = Output::new(p.PIN_25, Level::High); + let mut pio = Pio::new(p.PIO0, Irqs); + let spi = PioSpi::new(&mut pio.common, pio.sm0, pio.irq0, cs, p.PIN_24, p.PIN_29, p.DMA_CH0); + + let state = make_static!(cyw43::State::new()); + let (_net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + unwrap!(spawner.spawn(wifi_task(runner))); + + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; + + let delay = Duration::from_secs(1); + loop { + info!("led on!"); + control.gpio_set(0, true).await; + Timer::after(delay).await; + + info!("led off!"); + control.gpio_set(0, false).await; + Timer::after(delay).await; + } +} diff --git a/examples/rp/src/bin/wifi_scan.rs b/examples/rp/src/bin/wifi_scan.rs new file mode 100644 index 000000000..743fab617 --- /dev/null +++ b/examples/rp/src/bin/wifi_scan.rs @@ -0,0 +1,75 @@ +//! This example uses the RP Pico W board Wifi chip (cyw43). +//! Scans Wifi for ssid names. + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#![feature(async_fn_in_trait)] +#![allow(incomplete_features)] + +use core::str; + +use cyw43_pio::PioSpi; +use defmt::*; +use embassy_executor::Spawner; +use embassy_net::Stack; +use embassy_rp::bind_interrupts; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::{DMA_CH0, PIN_23, PIN_25, PIO0}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use static_cell::make_static; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::task] +async fn wifi_task( + runner: cyw43::Runner<'static, Output<'static, PIN_23>, PioSpi<'static, PIN_25, PIO0, 0, DMA_CH0>>, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + info!("Hello World!"); + + let p = embassy_rp::init(Default::default()); + + let fw = include_bytes!("../../../../cyw43-firmware/43439A0.bin"); + let clm = include_bytes!("../../../../cyw43-firmware/43439A0_clm.bin"); + + // To make flashing faster for development, you may want to flash the firmwares independently + // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: + // probe-rs download 43439A0.bin --format bin --chip RP2040 --base-address 0x10100000 + // probe-rs download 43439A0_clm.bin --format bin --chip RP2040 --base-address 0x10140000 + //let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 224190) }; + //let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) }; + + let pwr = Output::new(p.PIN_23, Level::Low); + let cs = Output::new(p.PIN_25, Level::High); + let mut pio = Pio::new(p.PIO0, Irqs); + let spi = PioSpi::new(&mut pio.common, pio.sm0, pio.irq0, cs, p.PIN_24, p.PIN_29, p.DMA_CH0); + + let state = make_static!(cyw43::State::new()); + let (_net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + unwrap!(spawner.spawn(wifi_task(runner))); + + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; + + let mut scanner = control.scan().await; + while let Some(bss) = scanner.next().await { + if let Ok(ssid_str) = str::from_utf8(&bss.ssid) { + info!("scanned {} == {:x}", ssid_str, bss.bssid); + } + } +} diff --git a/examples/rp/src/bin/wifi_tcp_server.rs b/examples/rp/src/bin/wifi_tcp_server.rs new file mode 100644 index 000000000..0223a3636 --- /dev/null +++ b/examples/rp/src/bin/wifi_tcp_server.rs @@ -0,0 +1,149 @@ +//! This example uses the RP Pico W board Wifi chip (cyw43). +//! Connects to specified Wifi network and creates a TCP endpoint on port 1234. + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#![feature(async_fn_in_trait)] +#![allow(incomplete_features)] + +use core::str::from_utf8; + +use cyw43_pio::PioSpi; +use defmt::*; +use embassy_executor::Spawner; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Config, Stack, StackResources}; +use embassy_rp::bind_interrupts; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::{DMA_CH0, PIN_23, PIN_25, PIO0}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_time::Duration; +use embedded_io::asynch::Write; +use static_cell::make_static; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +const WIFI_NETWORK: &str = "EmbassyTest"; +const WIFI_PASSWORD: &str = "V8YxhKt5CdIAJFud"; + +#[embassy_executor::task] +async fn wifi_task( + runner: cyw43::Runner<'static, Output<'static, PIN_23>, PioSpi<'static, PIN_25, PIO0, 0, DMA_CH0>>, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + info!("Hello World!"); + + let p = embassy_rp::init(Default::default()); + + let fw = include_bytes!("../../../../cyw43-firmware/43439A0.bin"); + let clm = include_bytes!("../../../../cyw43-firmware/43439A0_clm.bin"); + + // To make flashing faster for development, you may want to flash the firmwares independently + // at hardcoded addresses, instead of baking them into the program with `include_bytes!`: + // probe-rs download 43439A0.bin --format bin --chip RP2040 --base-address 0x10100000 + // probe-rs download 43439A0_clm.bin --format bin --chip RP2040 --base-address 0x10140000 + //let fw = unsafe { core::slice::from_raw_parts(0x10100000 as *const u8, 224190) }; + //let clm = unsafe { core::slice::from_raw_parts(0x10140000 as *const u8, 4752) }; + + let pwr = Output::new(p.PIN_23, Level::Low); + let cs = Output::new(p.PIN_25, Level::High); + let mut pio = Pio::new(p.PIO0, Irqs); + let spi = PioSpi::new(&mut pio.common, pio.sm0, pio.irq0, cs, p.PIN_24, p.PIN_29, p.DMA_CH0); + + let state = make_static!(cyw43::State::new()); + let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + unwrap!(spawner.spawn(wifi_task(runner))); + + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; + + let config = Config::dhcpv4(Default::default()); + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(192, 168, 69, 1)), + //}); + + // Generate random seed + let seed = 0x0123_4567_89ab_cdef; // chosen by fair dice roll. guarenteed to be random. + + // Init network stack + let stack = &*make_static!(Stack::new( + net_device, + config, + make_static!(StackResources::<2>::new()), + seed + )); + + unwrap!(spawner.spawn(net_task(stack))); + + loop { + //control.join_open(WIFI_NETWORK).await; + match control.join_wpa2(WIFI_NETWORK, WIFI_PASSWORD).await { + Ok(_) => break, + Err(err) => { + info!("join failed with status={}", err.status); + } + } + } + + // And now we can use it! + + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut buf = [0; 4096]; + + loop { + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + + control.gpio_set(0, false).await; + info!("Listening on TCP:1234..."); + if let Err(e) = socket.accept(1234).await { + warn!("accept error: {:?}", e); + continue; + } + + info!("Received connection from {:?}", socket.remote_endpoint()); + control.gpio_set(0, true).await; + + loop { + let n = match socket.read(&mut buf).await { + Ok(0) => { + warn!("read EOF"); + break; + } + Ok(n) => n, + Err(e) => { + warn!("read error: {:?}", e); + break; + } + }; + + info!("rxd {}", from_utf8(&buf[..n]).unwrap()); + + match socket.write_all(&buf[..n]).await { + Ok(()) => {} + Err(e) => { + warn!("write error: {:?}", e); + break; + } + }; + } + } +} diff --git a/examples/std/Cargo.toml b/examples/std/Cargo.toml index c7cec6b19..92933ab50 100644 --- a/examples/std/Cargo.toml +++ b/examples/std/Cargo.toml @@ -2,22 +2,24 @@ edition = "2021" name = "embassy-std-examples" version = "0.1.0" +license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["log"] } -embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["log", "std", "nightly", "integrated-timers"] } -embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["log", "std", "nightly"] } -embassy-net = { version = "0.1.0", path = "../../embassy-net", features=[ "std", "log", "medium-ethernet", "tcp", "udp", "dhcpv4", "pool-16"] } -embedded-io = { version = "0.3.0", features = ["async", "std", "futures"] } +embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["log"] } +embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-std", "executor-thread", "log", "nightly", "integrated-timers"] } +embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["log", "std", "nightly"] } +embassy-net = { version = "0.1.0", path = "../../embassy-net", features=[ "std", "nightly", "log", "medium-ethernet", "tcp", "udp", "dns", "dhcpv4", "unstable-traits", "proto-ipv6"] } +embassy-net-driver = { version = "0.1.0", path = "../../embassy-net-driver" } +embedded-io = { version = "0.4.0", features = ["async", "std", "futures"] } critical-section = { version = "1.1", features = ["std"] } async-io = "1.6.0" env_logger = "0.9.0" futures = { version = "0.3.17" } log = "0.4.14" -nix = "0.22.1" +nix = "0.26.2" libc = "0.2.101" clap = { version = "3.0.0-beta.5", features = ["derive"] } rand_core = { version = "0.6.3", features = ["std"] } heapless = { version = "0.7.5", default-features = false } -static_cell = "1.0" +static_cell = { version = "1.1", features = ["nightly"]} diff --git a/examples/std/README.md b/examples/std/README.md new file mode 100644 index 000000000..adc795928 --- /dev/null +++ b/examples/std/README.md @@ -0,0 +1,23 @@ + +## Running the `embassy-net` examples + +First, create the tap0 interface. You only need to do this once. + +```sh +sudo ip tuntap add name tap0 mode tap user $USER +sudo ip link set tap0 up +sudo ip addr add 192.168.69.100/24 dev tap0 +sudo ip -6 addr add fe80::100/64 dev tap0 +sudo ip -6 addr add fdaa::100/64 dev tap0 +sudo ip -6 route add fe80::/64 dev tap0 +sudo ip -6 route add fdaa::/64 dev tap0 +``` + +Second, have something listening there. For example `nc -l 8000` + +Then run the example located in the `examples` folder: + +```sh +cd $EMBASSY_ROOT/examples/std/ +cargo run --bin net -- --static-ip +``` \ No newline at end of file diff --git a/examples/std/src/bin/net.rs b/examples/std/src/bin/net.rs index 9b1450b72..3aadb029d 100644 --- a/examples/std/src/bin/net.rs +++ b/examples/std/src/bin/net.rs @@ -1,28 +1,22 @@ #![feature(type_alias_impl_trait)] +use std::default::Default; + use clap::Parser; use embassy_executor::{Executor, Spawner}; use embassy_net::tcp::TcpSocket; -use embassy_net::{ConfigStrategy, Ipv4Address, Ipv4Cidr, Stack, StackResources}; +use embassy_net::{Config, Ipv4Address, Ipv4Cidr, Stack, StackResources}; +use embassy_time::Duration; use embedded_io::asynch::Write; use heapless::Vec; use log::*; use rand_core::{OsRng, RngCore}; -use static_cell::StaticCell; +use static_cell::{make_static, StaticCell}; #[path = "../tuntap.rs"] mod tuntap; use crate::tuntap::TunTapDevice; - -macro_rules! singleton { - ($val:expr) => {{ - type T = impl Sized; - static STATIC_CELL: StaticCell = StaticCell::new(); - STATIC_CELL.init_with(move || $val) - }}; -} - #[derive(Parser)] #[clap(version = "1.0")] struct Opts { @@ -48,13 +42,13 @@ async fn main_task(spawner: Spawner) { // Choose between dhcp or static ip let config = if opts.static_ip { - ConfigStrategy::Static(embassy_net::Config { + Config::ipv4_static(embassy_net::StaticConfigV4 { address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24), dns_servers: Vec::new(), gateway: Some(Ipv4Address::new(192, 168, 69, 1)), }) } else { - ConfigStrategy::Dhcp + Config::dhcpv4(Default::default()) }; // Generate random seed @@ -63,10 +57,10 @@ async fn main_task(spawner: Spawner) { let seed = u64::from_le_bytes(seed); // Init network stack - let stack = &*singleton!(Stack::new( + let stack = &*make_static!(Stack::new( device, config, - singleton!(StackResources::<1, 2, 8>::new()), + make_static!(StackResources::<3>::new()), seed )); @@ -78,7 +72,7 @@ async fn main_task(spawner: Spawner) { let mut tx_buffer = [0; 4096]; let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); - socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10))); + socket.set_timeout(Some(Duration::from_secs(10))); let remote_endpoint = (Ipv4Address::new(192, 168, 69, 100), 8000); info!("connecting to {:?}...", remote_endpoint); diff --git a/examples/std/src/bin/net_dns.rs b/examples/std/src/bin/net_dns.rs new file mode 100644 index 000000000..65b5a2cd9 --- /dev/null +++ b/examples/std/src/bin/net_dns.rs @@ -0,0 +1,94 @@ +#![feature(type_alias_impl_trait)] + +use std::default::Default; + +use clap::Parser; +use embassy_executor::{Executor, Spawner}; +use embassy_net::dns::DnsQueryType; +use embassy_net::{Config, Ipv4Address, Ipv4Cidr, Stack, StackResources}; +use heapless::Vec; +use log::*; +use rand_core::{OsRng, RngCore}; +use static_cell::{make_static, StaticCell}; + +#[path = "../tuntap.rs"] +mod tuntap; + +use crate::tuntap::TunTapDevice; +#[derive(Parser)] +#[clap(version = "1.0")] +struct Opts { + /// TAP device name + #[clap(long, default_value = "tap0")] + tap: String, + /// use a static IP instead of DHCP + #[clap(long)] + static_ip: bool, +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack) -> ! { + stack.run().await +} + +#[embassy_executor::task] +async fn main_task(spawner: Spawner) { + let opts: Opts = Opts::parse(); + + // Init network device + let device = TunTapDevice::new(&opts.tap).unwrap(); + + // Choose between dhcp or static ip + let config = if opts.static_ip { + Config::ipv4_static(embassy_net::StaticConfigV4 { + address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 1), 24), + dns_servers: Vec::from_slice(&[Ipv4Address::new(8, 8, 4, 4).into(), Ipv4Address::new(8, 8, 8, 8).into()]) + .unwrap(), + gateway: Some(Ipv4Address::new(192, 168, 69, 100)), + }) + } else { + Config::dhcpv4(Default::default()) + }; + + // Generate random seed + let mut seed = [0; 8]; + OsRng.fill_bytes(&mut seed); + let seed = u64::from_le_bytes(seed); + + // Init network stack + let stack: &Stack<_> = &*make_static!(Stack::new( + device, + config, + make_static!(StackResources::<3>::new()), + seed + )); + + // Launch network task + spawner.spawn(net_task(stack)).unwrap(); + + let host = "example.com"; + info!("querying host {:?}...", host); + match stack.dns_query(host, DnsQueryType::A).await { + Ok(r) => { + info!("query response: {:?}", r); + } + Err(e) => { + warn!("query error: {:?}", e); + } + }; +} + +static EXECUTOR: StaticCell = StaticCell::new(); + +fn main() { + env_logger::builder() + .filter_level(log::LevelFilter::Debug) + .filter_module("async_io", log::LevelFilter::Info) + .format_timestamp_nanos() + .init(); + + let executor = EXECUTOR.init(Executor::new()); + executor.run(|spawner| { + spawner.spawn(main_task(spawner)).unwrap(); + }); +} diff --git a/examples/std/src/bin/net_udp.rs b/examples/std/src/bin/net_udp.rs index 392a97f0d..3fc46156c 100644 --- a/examples/std/src/bin/net_udp.rs +++ b/examples/std/src/bin/net_udp.rs @@ -2,26 +2,17 @@ use clap::Parser; use embassy_executor::{Executor, Spawner}; -use embassy_net::udp::UdpSocket; -use embassy_net::{ConfigStrategy, Ipv4Address, Ipv4Cidr, PacketMetadata, Stack, StackResources}; +use embassy_net::udp::{PacketMetadata, UdpSocket}; +use embassy_net::{Config, Ipv4Address, Ipv4Cidr, Stack, StackResources}; use heapless::Vec; use log::*; use rand_core::{OsRng, RngCore}; -use static_cell::StaticCell; +use static_cell::{make_static, StaticCell}; #[path = "../tuntap.rs"] mod tuntap; use crate::tuntap::TunTapDevice; - -macro_rules! singleton { - ($val:expr) => {{ - type T = impl Sized; - static STATIC_CELL: StaticCell = StaticCell::new(); - STATIC_CELL.init_with(move || $val) - }}; -} - #[derive(Parser)] #[clap(version = "1.0")] struct Opts { @@ -47,13 +38,13 @@ async fn main_task(spawner: Spawner) { // Choose between dhcp or static ip let config = if opts.static_ip { - ConfigStrategy::Static(embassy_net::Config { + Config::ipv4_static(embassy_net::StaticConfigV4 { address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24), dns_servers: Vec::new(), gateway: Some(Ipv4Address::new(192, 168, 69, 1)), }) } else { - ConfigStrategy::Dhcp + Config::dhcpv4(Default::default()) }; // Generate random seed @@ -62,10 +53,10 @@ async fn main_task(spawner: Spawner) { let seed = u64::from_le_bytes(seed); // Init network stack - let stack = &*singleton!(Stack::new( + let stack = &*make_static!(Stack::new( device, config, - singleton!(StackResources::<1, 2, 8>::new()), + make_static!(StackResources::<3>::new()), seed )); diff --git a/examples/std/src/bin/tcp_accept.rs b/examples/std/src/bin/tcp_accept.rs new file mode 100644 index 000000000..df09986ac --- /dev/null +++ b/examples/std/src/bin/tcp_accept.rs @@ -0,0 +1,129 @@ +#![feature(type_alias_impl_trait)] + +use core::fmt::Write as _; +use std::default::Default; + +use clap::Parser; +use embassy_executor::{Executor, Spawner}; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Config, Ipv4Address, Ipv4Cidr, Stack, StackResources}; +use embassy_time::{Duration, Timer}; +use embedded_io::asynch::Write as _; +use heapless::Vec; +use log::*; +use rand_core::{OsRng, RngCore}; +use static_cell::{make_static, StaticCell}; + +#[path = "../tuntap.rs"] +mod tuntap; + +use crate::tuntap::TunTapDevice; +#[derive(Parser)] +#[clap(version = "1.0")] +struct Opts { + /// TAP device name + #[clap(long, default_value = "tap0")] + tap: String, + /// use a static IP instead of DHCP + #[clap(long)] + static_ip: bool, +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack) -> ! { + stack.run().await +} + +#[derive(Default)] +struct StrWrite(pub heapless::Vec); + +impl core::fmt::Write for StrWrite { + fn write_str(&mut self, s: &str) -> Result<(), core::fmt::Error> { + self.0.extend_from_slice(s.as_bytes()).unwrap(); + Ok(()) + } +} + +#[embassy_executor::task] +async fn main_task(spawner: Spawner) { + let opts: Opts = Opts::parse(); + + // Init network device + let device = TunTapDevice::new(&opts.tap).unwrap(); + + // Choose between dhcp or static ip + let config = if opts.static_ip { + Config::ipv4_static(embassy_net::StaticConfigV4 { + address: Ipv4Cidr::new(Ipv4Address::new(192, 168, 69, 2), 24), + dns_servers: Vec::new(), + gateway: Some(Ipv4Address::new(192, 168, 69, 1)), + }) + } else { + Config::dhcpv4(Default::default()) + }; + + // Generate random seed + let mut seed = [0; 8]; + OsRng.fill_bytes(&mut seed); + let seed = u64::from_le_bytes(seed); + + // Init network stack + let stack = &*make_static!(Stack::new( + device, + config, + make_static!(StackResources::<3>::new()), + seed + )); + + // Launch network task + spawner.spawn(net_task(stack)).unwrap(); + + // Then we can use it! + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + + loop { + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + info!("Listening on TCP:9999..."); + if let Err(_) = socket.accept(9999).await { + warn!("accept error"); + continue; + } + + info!("Accepted a connection"); + + // Write some quick output + for i in 1..=5 { + let mut w = StrWrite::default(); + write!(w, "{}! ", i).unwrap(); + let r = socket.write_all(&w.0).await; + if let Err(e) = r { + warn!("write error: {:?}", e); + return; + } + + Timer::after(Duration::from_millis(500)).await; + } + info!("Closing the connection"); + socket.abort(); + info!("Flushing the RST out..."); + _ = socket.flush().await; + info!("Finished with the socket"); + } +} + +static EXECUTOR: StaticCell = StaticCell::new(); + +fn main() { + env_logger::builder() + .filter_level(log::LevelFilter::Debug) + .filter_module("async_io", log::LevelFilter::Info) + .format_timestamp_nanos() + .init(); + + let executor = EXECUTOR.init(Executor::new()); + executor.run(|spawner| { + spawner.spawn(main_task(spawner)).unwrap(); + }); +} diff --git a/examples/std/src/tuntap.rs b/examples/std/src/tuntap.rs index a0cace7f7..d918a2e62 100644 --- a/examples/std/src/tuntap.rs +++ b/examples/std/src/tuntap.rs @@ -1,8 +1,10 @@ use std::io; use std::io::{Read, Write}; use std::os::unix::io::{AsRawFd, RawFd}; +use std::task::Context; use async_io::Async; +use embassy_net_driver::{self, Capabilities, Driver, LinkState}; use log::*; pub const SIOCGIFMTU: libc::c_ulong = 0x8921; @@ -125,54 +127,35 @@ impl io::Write for TunTap { pub struct TunTapDevice { device: Async, - waker: Option, } impl TunTapDevice { pub fn new(name: &str) -> io::Result { Ok(Self { device: Async::new(TunTap::new(name)?)?, - waker: None, }) } } -use core::task::Waker; -use std::task::Context; +impl Driver for TunTapDevice { + type RxToken<'a> = RxToken where Self: 'a; + type TxToken<'a> = TxToken<'a> where Self: 'a; -use embassy_net::{Device, DeviceCapabilities, LinkState, Packet, PacketBox, PacketBoxExt, PacketBuf}; - -impl Device for TunTapDevice { - fn is_transmit_ready(&mut self) -> bool { - true - } - - fn transmit(&mut self, pkt: PacketBuf) { - // todo handle WouldBlock - match self.device.get_mut().write(&pkt) { - Ok(_) => {} - Err(e) if e.kind() == io::ErrorKind::WouldBlock => { - info!("transmit WouldBlock"); - } - Err(e) => panic!("transmit error: {:?}", e), - } - } - - fn receive(&mut self) -> Option { - let mut pkt = PacketBox::new(Packet::new()).unwrap(); + fn receive(&mut self, cx: &mut Context) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + let mut buf = vec![0; self.device.get_ref().mtu]; loop { - match self.device.get_mut().read(&mut pkt[..]) { + match self.device.get_mut().read(&mut buf) { Ok(n) => { - return Some(pkt.slice(0..n)); + buf.truncate(n); + return Some(( + RxToken { buffer: buf }, + TxToken { + device: &mut self.device, + }, + )); } Err(e) if e.kind() == io::ErrorKind::WouldBlock => { - let ready = if let Some(w) = self.waker.as_ref() { - let mut cx = Context::from_waker(w); - self.device.poll_readable(&mut cx).is_ready() - } else { - false - }; - if !ready { + if !self.device.poll_readable(cx).is_ready() { return None; } } @@ -181,37 +164,19 @@ impl Device for TunTapDevice { } } - fn register_waker(&mut self, w: &Waker) { - match self.waker { - // Optimization: If both the old and new Wakers wake the same task, we can simply - // keep the old waker, skipping the clone. (In most executor implementations, - // cloning a waker is somewhat expensive, comparable to cloning an Arc). - Some(ref w2) if (w2.will_wake(w)) => {} - _ => { - // clone the new waker and store it - if let Some(old_waker) = core::mem::replace(&mut self.waker, Some(w.clone())) { - // We had a waker registered for another task. Wake it, so the other task can - // reregister itself if it's still interested. - // - // If two tasks are waiting on the same thing concurrently, this will cause them - // to wake each other in a loop fighting over this WakerRegistration. This wastes - // CPU but things will still work. - // - // If the user wants to have two tasks waiting on the same thing they should use - // a more appropriate primitive that can store multiple wakers. - old_waker.wake() - } - } - } + fn transmit(&mut self, _cx: &mut Context) -> Option> { + Some(TxToken { + device: &mut self.device, + }) } - fn capabilities(&self) -> DeviceCapabilities { - let mut caps = DeviceCapabilities::default(); + fn capabilities(&self) -> Capabilities { + let mut caps = Capabilities::default(); caps.max_transmission_unit = self.device.get_ref().mtu; caps } - fn link_state(&mut self) -> LinkState { + fn link_state(&mut self, _cx: &mut Context) -> LinkState { LinkState::Up } @@ -219,3 +184,41 @@ impl Device for TunTapDevice { [0x02, 0x03, 0x04, 0x05, 0x06, 0x07] } } + +#[doc(hidden)] +pub struct RxToken { + buffer: Vec, +} + +impl embassy_net_driver::RxToken for RxToken { + fn consume(mut self, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + f(&mut self.buffer) + } +} + +#[doc(hidden)] +pub struct TxToken<'a> { + device: &'a mut Async, +} + +impl<'a> embassy_net_driver::TxToken for TxToken<'a> { + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + let mut buffer = vec![0; len]; + let result = f(&mut buffer); + + // todo handle WouldBlock with async + match self.device.get_mut().write(&buffer) { + Ok(_) => {} + Err(e) if e.kind() == io::ErrorKind::WouldBlock => info!("transmit WouldBlock"), + Err(e) => panic!("transmit error: {:?}", e), + } + + result + } +} diff --git a/examples/stm32c0/.cargo/config.toml b/examples/stm32c0/.cargo/config.toml new file mode 100644 index 000000000..29a8be7e1 --- /dev/null +++ b/examples/stm32c0/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace STM32G071C8Rx with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --speed 100 --chip STM32c031c6tx" + +[build] +target = "thumbv6m-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/examples/stm32c0/Cargo.toml b/examples/stm32c0/Cargo.toml new file mode 100644 index 000000000..26837abef --- /dev/null +++ b/examples/stm32c0/Cargo.toml @@ -0,0 +1,21 @@ +[package] +edition = "2021" +name = "embassy-stm32c0-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "time-driver-any", "stm32c031c6", "memory-x", "unstable-pac", "exti"] } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +panic-probe = { version = "0.3", features = ["print-defmt"] } +futures = { version = "0.3.17", default-features = false, features = ["async-await"] } +heapless = { version = "0.7.5", default-features = false } diff --git a/examples/stm32c0/build.rs b/examples/stm32c0/build.rs new file mode 100644 index 000000000..8cd32d7ed --- /dev/null +++ b/examples/stm32c0/build.rs @@ -0,0 +1,5 @@ +fn main() { + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/examples/stm32c0/src/bin/blinky.rs b/examples/stm32c0/src/bin/blinky.rs new file mode 100644 index 000000000..8a65b0692 --- /dev/null +++ b/examples/stm32c0/src/bin/blinky.rs @@ -0,0 +1,27 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let mut led = Output::new(p.PA5, Level::High, Speed::Low); + + loop { + info!("high"); + led.set_high(); + Timer::after(Duration::from_millis(300)).await; + + info!("low"); + led.set_low(); + Timer::after(Duration::from_millis(300)).await; + } +} diff --git a/examples/stm32c0/src/bin/button.rs b/examples/stm32c0/src/bin/button.rs new file mode 100644 index 000000000..72a3f5cbf --- /dev/null +++ b/examples/stm32c0/src/bin/button.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use cortex_m_rt::entry; +use defmt::*; +use embassy_stm32::gpio::{Input, Pull}; +use {defmt_rtt as _, panic_probe as _}; + +#[entry] +fn main() -> ! { + info!("Hello World!"); + + let p = embassy_stm32::init(Default::default()); + + let button = Input::new(p.PC13, Pull::Up); + + loop { + if button.is_high() { + info!("high"); + } else { + info!("low"); + } + } +} diff --git a/examples/stm32c0/src/bin/button_exti.rs b/examples/stm32c0/src/bin/button_exti.rs new file mode 100644 index 000000000..ef32d4c4a --- /dev/null +++ b/examples/stm32c0/src/bin/button_exti.rs @@ -0,0 +1,27 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::gpio::{Input, Pull}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let button = Input::new(p.PC13, Pull::Up); + let mut button = ExtiInput::new(button, p.EXTI13); + + info!("Press the USER button..."); + + loop { + button.wait_for_falling_edge().await; + info!("Pressed!"); + button.wait_for_rising_edge().await; + info!("Released!"); + } +} diff --git a/examples/stm32f0/.cargo/config.toml b/examples/stm32f0/.cargo/config.toml index d1b1cd0bf..def4c8c92 100644 --- a/examples/stm32f0/.cargo/config.toml +++ b/examples/stm32f0/.cargo/config.toml @@ -1,5 +1,5 @@ [target.thumbv6m-none-eabi] -runner = 'probe-run --chip STM32F030F4Px' +runner = 'probe-rs run --chip STM32F091RCTX' [build] target = "thumbv6m-none-eabi" diff --git a/examples/stm32f0/Cargo.toml b/examples/stm32f0/Cargo.toml index cd2995d2c..b7b5eaa99 100644 --- a/examples/stm32f0/Cargo.toml +++ b/examples/stm32f0/Cargo.toml @@ -2,17 +2,18 @@ name = "embassy-stm32f0-examples" version = "0.1.0" edition = "2021" +license = "MIT OR Apache-2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" defmt = "0.3" -defmt-rtt = "0.3" +defmt-rtt = "0.4" panic-probe = "0.3" -embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } -embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-32768hz"] } -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "memory-x", "stm32f030f4", "time-driver-any"] } - +embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "memory-x", "stm32f091rc", "time-driver-any", "exti", "unstable-pac"] } +static_cell = { version = "1.1", features = ["nightly"]} diff --git a/examples/stm32f0/src/bin/adc.rs b/examples/stm32f0/src/bin/adc.rs new file mode 100644 index 000000000..8ed9f98f8 --- /dev/null +++ b/examples/stm32f0/src/bin/adc.rs @@ -0,0 +1,35 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::adc::{Adc, SampleTime}; +use embassy_time::{Delay, Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let mut adc = Adc::new(p.ADC, &mut Delay); + adc.set_sample_time(SampleTime::Cycles71_5); + let mut pin = p.PA1; + + let mut vrefint = adc.enable_vref(&mut Delay); + let vrefint_sample = adc.read_internal(&mut vrefint); + let convert_to_millivolts = |sample| { + // From https://www.st.com/resource/en/datasheet/stm32f031c6.pdf + // 6.3.4 Embedded reference voltage + const VREFINT_MV: u32 = 1230; // mV + + (u32::from(sample) * VREFINT_MV / u32::from(vrefint_sample)) as u16 + }; + + loop { + let v = adc.read(&mut pin); + info!("--> {} - {} mV", v, convert_to_millivolts(v)); + Timer::after(Duration::from_millis(100)).await; + } +} diff --git a/examples/stm32f0/src/bin/blinky.rs b/examples/stm32f0/src/bin/blinky.rs new file mode 100644 index 000000000..9f923399c --- /dev/null +++ b/examples/stm32f0/src/bin/blinky.rs @@ -0,0 +1,28 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +// main is itself an async function. +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + //PA5 is the onboard LED on the Nucleo F091RC + let mut led = Output::new(p.PA5, Level::High, Speed::Low); + + loop { + info!("high"); + led.set_high(); + Timer::after(Duration::from_millis(300)).await; + + info!("low"); + led.set_low(); + Timer::after(Duration::from_millis(300)).await; + } +} diff --git a/examples/stm32f0/src/bin/button_controlled_blink.rs b/examples/stm32f0/src/bin/button_controlled_blink.rs new file mode 100644 index 000000000..f362c53f5 --- /dev/null +++ b/examples/stm32f0/src/bin/button_controlled_blink.rs @@ -0,0 +1,64 @@ +//! This example showcases how to create task + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use core::sync::atomic::{AtomicU32, Ordering}; + +use defmt::info; +use embassy_executor::Spawner; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::gpio::{AnyPin, Input, Level, Output, Pin, Pull, Speed}; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +static BLINK_MS: AtomicU32 = AtomicU32::new(0); + +#[embassy_executor::task] +async fn led_task(led: AnyPin) { + // Configure the LED pin as a push pull output and obtain handler. + // On the Nucleo F091RC there's an on-board LED connected to pin PA5. + let mut led = Output::new(led, Level::Low, Speed::Low); + + loop { + let del = BLINK_MS.load(Ordering::Relaxed); + info!("Value of del is {}", del); + Timer::after(Duration::from_millis(del.into())).await; + info!("LED toggling"); + led.toggle(); + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + // Initialize and create handle for devicer peripherals + let p = embassy_stm32::init(Default::default()); + + // Configure the button pin and obtain handler. + // On the Nucleo F091RC there is a button connected to pin PC13. + let button = Input::new(p.PC13, Pull::None); + let mut button = ExtiInput::new(button, p.EXTI13); + + // Create and initialize a delay variable to manage delay loop + let mut del_var = 2000; + + // Blink duration value to global context + BLINK_MS.store(del_var, Ordering::Relaxed); + + // Spawn LED blinking task + spawner.spawn(led_task(p.PA5.degrade())).unwrap(); + + loop { + // Check if button got pressed + button.wait_for_rising_edge().await; + info!("rising_edge"); + del_var = del_var - 200; + // If updated delay value drops below 200 then reset it back to starting value + if del_var < 200 { + del_var = 2000; + } + // Updated delay value to global context + BLINK_MS.store(del_var, Ordering::Relaxed); + } +} diff --git a/examples/stm32f0/src/bin/button_exti.rs b/examples/stm32f0/src/bin/button_exti.rs new file mode 100644 index 000000000..40c0d5848 --- /dev/null +++ b/examples/stm32f0/src/bin/button_exti.rs @@ -0,0 +1,27 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::gpio::{Input, Pull}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // Initialize and create handle for devicer peripherals + let p = embassy_stm32::init(Default::default()); + // Configure the button pin and obtain handler. + // On the Nucleo F091RC there is a button connected to pin PC13. + let button = Input::new(p.PC13, Pull::Down); + let mut button = ExtiInput::new(button, p.EXTI13); + + info!("Press the USER button..."); + loop { + button.wait_for_falling_edge().await; + info!("Pressed!"); + button.wait_for_rising_edge().await; + info!("Released!"); + } +} diff --git a/examples/stm32f0/src/bin/multiprio.rs b/examples/stm32f0/src/bin/multiprio.rs new file mode 100644 index 000000000..988ffeef1 --- /dev/null +++ b/examples/stm32f0/src/bin/multiprio.rs @@ -0,0 +1,145 @@ +//! This example showcases how to create multiple Executor instances to run tasks at +//! different priority levels. +//! +//! Low priority executor runs in thread mode (not interrupt), and uses `sev` for signaling +//! there's work in the queue, and `wfe` for waiting for work. +//! +//! Medium and high priority executors run in two interrupts with different priorities. +//! Signaling work is done by pending the interrupt. No "waiting" needs to be done explicitly, since +//! when there's work the interrupt will trigger and run the executor. +//! +//! Sample output below. Note that high priority ticks can interrupt everything else, and +//! medium priority computations can interrupt low priority computations, making them to appear +//! to take significantly longer time. +//! +//! ```not_rust +//! [med] Starting long computation +//! [med] done in 992 ms +//! [high] tick! +//! [low] Starting long computation +//! [med] Starting long computation +//! [high] tick! +//! [high] tick! +//! [med] done in 993 ms +//! [med] Starting long computation +//! [high] tick! +//! [high] tick! +//! [med] done in 993 ms +//! [low] done in 3972 ms +//! [med] Starting long computation +//! [high] tick! +//! [high] tick! +//! [med] done in 993 ms +//! ``` +//! +//! For comparison, try changing the code so all 3 tasks get spawned on the low priority executor. +//! You will get an output like the following. Note that no computation is ever interrupted. +//! +//! ```not_rust +//! [high] tick! +//! [med] Starting long computation +//! [med] done in 496 ms +//! [low] Starting long computation +//! [low] done in 992 ms +//! [med] Starting long computation +//! [med] done in 496 ms +//! [high] tick! +//! [low] Starting long computation +//! [low] done in 992 ms +//! [high] tick! +//! [med] Starting long computation +//! [med] done in 496 ms +//! [high] tick! +//! ``` +//! + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use cortex_m_rt::entry; +use defmt::*; +use embassy_executor::{Executor, InterruptExecutor}; +use embassy_stm32::interrupt; +use embassy_stm32::interrupt::{InterruptExt, Priority}; +use embassy_time::{Duration, Instant, Timer}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::task] +async fn run_high() { + loop { + // info!(" [high] tick!"); + Timer::after(Duration::from_ticks(27374)).await; + } +} + +#[embassy_executor::task] +async fn run_med() { + loop { + let start = Instant::now(); + info!(" [med] Starting long computation"); + + // Spin-wait to simulate a long CPU computation + cortex_m::asm::delay(8_000_000); // ~1 second + + let end = Instant::now(); + let ms = end.duration_since(start).as_ticks() / 33; + info!(" [med] done in {} ms", ms); + + Timer::after(Duration::from_ticks(23421)).await; + } +} + +#[embassy_executor::task] +async fn run_low() { + loop { + let start = Instant::now(); + info!("[low] Starting long computation"); + + // Spin-wait to simulate a long CPU computation + cortex_m::asm::delay(16_000_000); // ~2 seconds + + let end = Instant::now(); + let ms = end.duration_since(start).as_ticks() / 33; + info!("[low] done in {} ms", ms); + + Timer::after(Duration::from_ticks(32983)).await; + } +} + +static EXECUTOR_HIGH: InterruptExecutor = InterruptExecutor::new(); +static EXECUTOR_MED: InterruptExecutor = InterruptExecutor::new(); +static EXECUTOR_LOW: StaticCell = StaticCell::new(); + +#[interrupt] +unsafe fn USART1() { + EXECUTOR_HIGH.on_interrupt() +} + +#[interrupt] +unsafe fn USART2() { + EXECUTOR_MED.on_interrupt() +} + +#[entry] +fn main() -> ! { + // Initialize and create handle for devicer peripherals + let _p = embassy_stm32::init(Default::default()); + + // High-priority executor: USART1, priority level 6 + interrupt::USART1.set_priority(Priority::P6); + let spawner = EXECUTOR_HIGH.start(interrupt::USART1); + unwrap!(spawner.spawn(run_high())); + + // Medium-priority executor: USART2, priority level 7 + interrupt::USART2.set_priority(Priority::P7); + let spawner = EXECUTOR_MED.start(interrupt::USART2); + unwrap!(spawner.spawn(run_med())); + + // Low priority executor: runs in thread mode, using WFE/SEV + let executor = EXECUTOR_LOW.init(Executor::new()); + executor.run(|spawner| { + unwrap!(spawner.spawn(run_low())); + }); +} diff --git a/examples/stm32f0/src/bin/wdg.rs b/examples/stm32f0/src/bin/wdg.rs new file mode 100644 index 000000000..a44b17528 --- /dev/null +++ b/examples/stm32f0/src/bin/wdg.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::wdg::IndependentWatchdog; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // Initialize and create handle for devicer peripherals + let p = embassy_stm32::init(Default::default()); + // Configure the independent watchdog timer + let mut wdg = IndependentWatchdog::new(p.IWDG, 20_000_00); + + info!("Watchdog start"); + wdg.unleash(); + + loop { + Timer::after(Duration::from_secs(1)).await; + wdg.pet(); + } +} diff --git a/examples/stm32f1/.cargo/config.toml b/examples/stm32f1/.cargo/config.toml index e61e739fe..ce6fef11b 100644 --- a/examples/stm32f1/.cargo/config.toml +++ b/examples/stm32f1/.cargo/config.toml @@ -1,6 +1,6 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -# replace STM32F103C8 with your chip as listed in `probe-run --list-chips` -runner = "probe-run --chip STM32F103C8" +# replace STM32F103C8 with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32F103C8" [build] target = "thumbv7m-none-eabi" diff --git a/examples/stm32f1/Cargo.toml b/examples/stm32f1/Cargo.toml index 8660e743d..29cad5b67 100644 --- a/examples/stm32f1/Cargo.toml +++ b/examples/stm32f1/Cargo.toml @@ -2,19 +2,20 @@ edition = "2021" name = "embassy-stm32f1-examples" version = "0.1.0" +license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } -embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-32768hz"] } -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32f103c8", "unstable-pac", "memory-x", "time-driver-any"] } +embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32f103c8", "unstable-pac", "memory-x", "time-driver-any", "unstable-traits" ] } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } -embassy-usb-serial = { version = "0.1.0", path = "../../embassy-usb-serial", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } defmt = "0.3" -defmt-rtt = "0.3" +defmt-rtt = "0.4" -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" panic-probe = { version = "0.3", features = ["print-defmt"] } diff --git a/examples/stm32f1/src/bin/adc.rs b/examples/stm32f1/src/bin/adc.rs index 2d6b4a0e9..ed59e2799 100644 --- a/examples/stm32f1/src/bin/adc.rs +++ b/examples/stm32f1/src/bin/adc.rs @@ -16,11 +16,19 @@ async fn main(_spawner: Spawner) { let mut adc = Adc::new(p.ADC1, &mut Delay); let mut pin = p.PB1; - let mut vref = adc.enable_vref(&mut Delay); - adc.calibrate(&mut vref); + let mut vrefint = adc.enable_vref(&mut Delay); + let vrefint_sample = adc.read(&mut vrefint); + let convert_to_millivolts = |sample| { + // From http://www.st.com/resource/en/datasheet/CD00161566.pdf + // 5.3.4 Embedded reference voltage + const VREFINT_MV: u32 = 1200; // mV + + (u32::from(sample) * VREFINT_MV / u32::from(vrefint_sample)) as u16 + }; + loop { let v = adc.read(&mut pin); - info!("--> {} - {} mV", v, adc.to_millivolts(v)); + info!("--> {} - {} mV", v, convert_to_millivolts(v)); Timer::after(Duration::from_millis(100)).await; } } diff --git a/examples/stm32f1/src/bin/usb_serial.rs b/examples/stm32f1/src/bin/usb_serial.rs index a9c46068f..663099ff7 100644 --- a/examples/stm32f1/src/bin/usb_serial.rs +++ b/examples/stm32f1/src/bin/usb_serial.rs @@ -4,17 +4,21 @@ use defmt::{panic, *}; use embassy_executor::Spawner; +use embassy_futures::join::join; use embassy_stm32::gpio::{Level, Output, Speed}; use embassy_stm32::time::Hertz; use embassy_stm32::usb::{Driver, Instance}; -use embassy_stm32::{interrupt, Config}; +use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; use embassy_time::{Duration, Timer}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; use embassy_usb::driver::EndpointError; use embassy_usb::Builder; -use embassy_usb_serial::{CdcAcmClass, State}; -use futures::future::join; use {defmt_rtt as _, panic_probe as _}; +bind_interrupts!(struct Irqs { + USB_LP_CAN1_RX0 => usb::InterruptHandler; +}); + #[embassy_executor::main] async fn main(_spawner: Spawner) { let mut config = Config::default(); @@ -35,8 +39,7 @@ async fn main(_spawner: Spawner) { } // Create the driver, from the HAL. - let irq = interrupt::take!(USB_LP_CAN1_RX0); - let driver = Driver::new(p.USB, irq, p.PA12, p.PA11); + let driver = Driver::new(p.USB, Irqs, p.PA12, p.PA11); // Create embassy-usb Config let config = embassy_usb::Config::new(0xc0de, 0xcafe); @@ -58,7 +61,6 @@ async fn main(_spawner: Spawner) { &mut config_descriptor, &mut bos_descriptor, &mut control_buf, - None, ); // Create classes on the builder. diff --git a/examples/stm32f2/.cargo/config.toml b/examples/stm32f2/.cargo/config.toml index 197fadf92..1198fcab8 100644 --- a/examples/stm32f2/.cargo/config.toml +++ b/examples/stm32f2/.cargo/config.toml @@ -1,6 +1,6 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -# replace STM32F207ZGTx with your chip as listed in `probe-run --list-chips` -runner = "probe-run --chip STM32F207ZGTx" +# replace STM32F207ZGTx with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32F207ZGTx" [build] target = "thumbv7m-none-eabi" diff --git a/examples/stm32f2/Cargo.toml b/examples/stm32f2/Cargo.toml index b4bff4d85..652210c7f 100644 --- a/examples/stm32f2/Cargo.toml +++ b/examples/stm32f2/Cargo.toml @@ -2,17 +2,18 @@ edition = "2021" name = "embassy-stm32f2-examples" version = "0.1.0" +license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } -embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-32768hz"] } +embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32f207zg", "unstable-pac", "memory-x", "time-driver-any", "exti"] } defmt = "0.3" -defmt-rtt = "0.3" +defmt-rtt = "0.4" -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" panic-probe = { version = "0.3", features = ["print-defmt"] } diff --git a/examples/stm32f3/.cargo/config.toml b/examples/stm32f3/.cargo/config.toml index d4bcd263d..cb8a7c5af 100644 --- a/examples/stm32f3/.cargo/config.toml +++ b/examples/stm32f3/.cargo/config.toml @@ -1,6 +1,6 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -# replace STM32F429ZITx with your chip as listed in `probe-run --list-chips` -runner = "probe-run --chip STM32F303ZETx" +# replace STM32F429ZITx with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32F303ZETx" [build] target = "thumbv7em-none-eabihf" diff --git a/examples/stm32f3/Cargo.toml b/examples/stm32f3/Cargo.toml index d152b145f..489d0ff4c 100644 --- a/examples/stm32f3/Cargo.toml +++ b/examples/stm32f3/Cargo.toml @@ -2,20 +2,20 @@ edition = "2021" name = "embassy-stm32f3-examples" version = "0.1.0" +license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } -embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-32768hz"] } +embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32f303ze", "unstable-pac", "memory-x", "time-driver-any", "exti"] } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } -embassy-usb-serial = { version = "0.1.0", path = "../../embassy-usb-serial", features = ["defmt"] } -embassy-usb-hid = { version = "0.1.0", path = "../../embassy-usb-hid", features = ["defmt"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } defmt = "0.3" -defmt-rtt = "0.3" +defmt-rtt = "0.4" -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" panic-probe = { version = "0.3", features = ["print-defmt"] } @@ -23,4 +23,4 @@ futures = { version = "0.3.17", default-features = false, features = ["async-awa heapless = { version = "0.7.5", default-features = false } nb = "1.0.0" embedded-storage = "0.3.0" -static_cell = "1.0" +static_cell = { version = "1.1", features = ["nightly"]} diff --git a/examples/stm32f3/src/bin/flash.rs b/examples/stm32f3/src/bin/flash.rs index 2cf24dbd3..236fb36c1 100644 --- a/examples/stm32f3/src/bin/flash.rs +++ b/examples/stm32f3/src/bin/flash.rs @@ -5,7 +5,6 @@ use defmt::{info, unwrap}; use embassy_executor::Spawner; use embassy_stm32::flash::Flash; -use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] @@ -15,27 +14,27 @@ async fn main(_spawner: Spawner) { const ADDR: u32 = 0x26000; - let mut f = Flash::unlock(p.FLASH); + let mut f = Flash::new_blocking(p.FLASH).into_blocking_regions().bank1_region; info!("Reading..."); let mut buf = [0u8; 8]; - unwrap!(f.read(ADDR, &mut buf)); + unwrap!(f.blocking_read(ADDR, &mut buf)); info!("Read: {=[u8]:x}", buf); info!("Erasing..."); - unwrap!(f.erase(ADDR, ADDR + 2048)); + unwrap!(f.blocking_erase(ADDR, ADDR + 2048)); info!("Reading..."); let mut buf = [0u8; 8]; - unwrap!(f.read(ADDR, &mut buf)); + unwrap!(f.blocking_read(ADDR, &mut buf)); info!("Read after erase: {=[u8]:x}", buf); info!("Writing..."); - unwrap!(f.write(ADDR, &[1, 2, 3, 4, 5, 6, 7, 8])); + unwrap!(f.blocking_write(ADDR, &[1, 2, 3, 4, 5, 6, 7, 8])); info!("Reading..."); let mut buf = [0u8; 8]; - unwrap!(f.read(ADDR, &mut buf)); + unwrap!(f.blocking_read(ADDR, &mut buf)); info!("Read: {=[u8]:x}", buf); assert_eq!(&buf[..], &[1, 2, 3, 4, 5, 6, 7, 8]); } diff --git a/examples/stm32f3/src/bin/multiprio.rs b/examples/stm32f3/src/bin/multiprio.rs index 9e8228a4b..80bf59deb 100644 --- a/examples/stm32f3/src/bin/multiprio.rs +++ b/examples/stm32f3/src/bin/multiprio.rs @@ -59,9 +59,9 @@ use cortex_m_rt::entry; use defmt::*; -use embassy_stm32::executor::{Executor, InterruptExecutor}; +use embassy_executor::{Executor, InterruptExecutor}; use embassy_stm32::interrupt; -use embassy_stm32::interrupt::InterruptExt; +use embassy_stm32::interrupt::{InterruptExt, Priority}; use embassy_time::{Duration, Instant, Timer}; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -108,28 +108,34 @@ async fn run_low() { } } -static EXECUTOR_HIGH: StaticCell> = StaticCell::new(); -static EXECUTOR_MED: StaticCell> = StaticCell::new(); +static EXECUTOR_HIGH: InterruptExecutor = InterruptExecutor::new(); +static EXECUTOR_MED: InterruptExecutor = InterruptExecutor::new(); static EXECUTOR_LOW: StaticCell = StaticCell::new(); +#[interrupt] +unsafe fn UART4() { + EXECUTOR_HIGH.on_interrupt() +} + +#[interrupt] +unsafe fn UART5() { + EXECUTOR_MED.on_interrupt() +} + #[entry] fn main() -> ! { info!("Hello World!"); let _p = embassy_stm32::init(Default::default()); - // High-priority executor: SWI1_EGU1, priority level 6 - let irq = interrupt::take!(UART4); - irq.set_priority(interrupt::Priority::P6); - let executor = EXECUTOR_HIGH.init(InterruptExecutor::new(irq)); - let spawner = executor.start(); + // High-priority executor: UART4, priority level 6 + interrupt::UART4.set_priority(Priority::P6); + let spawner = EXECUTOR_HIGH.start(interrupt::UART4); unwrap!(spawner.spawn(run_high())); - // Medium-priority executor: SWI0_EGU0, priority level 7 - let irq = interrupt::take!(UART5); - irq.set_priority(interrupt::Priority::P7); - let executor = EXECUTOR_MED.init(InterruptExecutor::new(irq)); - let spawner = executor.start(); + // Medium-priority executor: UART5, priority level 7 + interrupt::UART5.set_priority(Priority::P7); + let spawner = EXECUTOR_MED.start(interrupt::UART5); unwrap!(spawner.spawn(run_med())); // Low priority executor: runs in thread mode, using WFE/SEV diff --git a/examples/stm32f3/src/bin/usart_dma.rs b/examples/stm32f3/src/bin/usart_dma.rs index 3bc5a287f..85f01a69e 100644 --- a/examples/stm32f3/src/bin/usart_dma.rs +++ b/examples/stm32f3/src/bin/usart_dma.rs @@ -8,16 +8,21 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::dma::NoDma; use embassy_stm32::usart::{Config, Uart}; +use embassy_stm32::{bind_interrupts, peripherals, usart}; use heapless::String; use {defmt_rtt as _, panic_probe as _}; +bind_interrupts!(struct Irqs { + USART1 => usart::InterruptHandler; +}); + #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); info!("Hello World!"); let config = Config::default(); - let mut usart = Uart::new(p.USART1, p.PE1, p.PE0, p.DMA1_CH4, NoDma, config); + let mut usart = Uart::new(p.USART1, p.PE1, p.PE0, Irqs, p.DMA1_CH4, NoDma, config); for n in 0u32.. { let mut s: String<128> = String::new(); diff --git a/examples/stm32f3/src/bin/usb_serial.rs b/examples/stm32f3/src/bin/usb_serial.rs index d3702fc35..f15f333b7 100644 --- a/examples/stm32f3/src/bin/usb_serial.rs +++ b/examples/stm32f3/src/bin/usb_serial.rs @@ -4,17 +4,21 @@ use defmt::{panic, *}; use embassy_executor::Spawner; +use embassy_futures::join::join; use embassy_stm32::gpio::{Level, Output, Speed}; use embassy_stm32::time::mhz; use embassy_stm32::usb::{Driver, Instance}; -use embassy_stm32::{interrupt, Config}; +use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; use embassy_time::{Duration, Timer}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; use embassy_usb::driver::EndpointError; use embassy_usb::Builder; -use embassy_usb_serial::{CdcAcmClass, State}; -use futures::future::join; use {defmt_rtt as _, panic_probe as _}; +bind_interrupts!(struct Irqs { + USB_LP_CAN_RX0 => usb::InterruptHandler; +}); + #[embassy_executor::main] async fn main(_spawner: Spawner) { let mut config = Config::default(); @@ -33,8 +37,7 @@ async fn main(_spawner: Spawner) { dp_pullup.set_high(); // Create the driver, from the HAL. - let irq = interrupt::take!(USB_LP_CAN_RX0); - let driver = Driver::new(p.USB, irq, p.PA12, p.PA11); + let driver = Driver::new(p.USB, Irqs, p.PA12, p.PA11); // Create embassy-usb Config let config = embassy_usb::Config::new(0xc0de, 0xcafe); @@ -55,7 +58,6 @@ async fn main(_spawner: Spawner) { &mut config_descriptor, &mut bos_descriptor, &mut control_buf, - None, ); // Create classes on the builder. diff --git a/examples/stm32f4/.cargo/config.toml b/examples/stm32f4/.cargo/config.toml index 4d4363c05..16efa8e6f 100644 --- a/examples/stm32f4/.cargo/config.toml +++ b/examples/stm32f4/.cargo/config.toml @@ -1,6 +1,6 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -# replace STM32F429ZITx with your chip as listed in `probe-run --list-chips` -runner = "probe-run --chip STM32F429ZITx" +# replace STM32F429ZITx with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32F429ZITx" [build] target = "thumbv7em-none-eabi" diff --git a/examples/stm32f4/Cargo.toml b/examples/stm32f4/Cargo.toml index 9bfdda92d..c1c821364 100644 --- a/examples/stm32f4/Cargo.toml +++ b/examples/stm32f4/Cargo.toml @@ -2,28 +2,31 @@ edition = "2021" name = "embassy-stm32f4-examples" version = "0.1.0" - +license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } -embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "unstable-traits", "tick-32768hz"] } -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "unstable-traits", "defmt", "stm32f429zi", "unstable-pac", "memory-x", "time-driver-any", "exti"] } +embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers", "arch-cortex-m", "executor-thread", "executor-interrupt"] } +embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "unstable-traits", "tick-hz-32_768"] } +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "unstable-traits", "defmt", "stm32f429zi", "unstable-pac", "memory-x", "time-driver-any", "exti", "embedded-sdmmc", "chrono"] } +embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "nightly"] } defmt = "0.3" -defmt-rtt = "0.3" +defmt-rtt = "0.4" -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" -embedded-io = "0.3.0" +embedded-io = "0.4.0" panic-probe = { version = "0.3", features = ["print-defmt"] } futures = { version = "0.3.17", default-features = false, features = ["async-await"] } heapless = { version = "0.7.5", default-features = false } nb = "1.0.0" embedded-storage = "0.3.0" micromath = "2.0.0" -static_cell = "1.0" +static_cell = { version = "1.1", features = ["nightly"]} +chrono = { version = "^0.4", default-features = false} -usb-device = "0.2" -usbd-serial = "0.1.1" +[profile.release] +debug = 2 diff --git a/examples/stm32f4/src/bin/adc.rs b/examples/stm32f4/src/bin/adc.rs index 871185074..1c9a0b35d 100644 --- a/examples/stm32f4/src/bin/adc.rs +++ b/examples/stm32f4/src/bin/adc.rs @@ -2,9 +2,10 @@ #![no_main] #![feature(type_alias_impl_trait)] +use cortex_m::prelude::_embedded_hal_blocking_delay_DelayUs; use defmt::*; use embassy_executor::Spawner; -use embassy_stm32::adc::Adc; +use embassy_stm32::adc::{Adc, Temperature, VrefInt}; use embassy_time::{Delay, Duration, Timer}; use {defmt_rtt as _, panic_probe as _}; @@ -13,12 +14,55 @@ async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); info!("Hello World!"); - let mut adc = Adc::new(p.ADC1, &mut Delay); + let mut delay = Delay; + let mut adc = Adc::new(p.ADC1, &mut delay); let mut pin = p.PC1; + let mut vrefint = adc.enable_vrefint(); + let mut temp = adc.enable_temperature(); + + // Startup delay can be combined to the maximum of either + delay.delay_us(Temperature::start_time_us().max(VrefInt::start_time_us())); + + let vrefint_sample = adc.read_internal(&mut vrefint); + + let convert_to_millivolts = |sample| { + // From http://www.st.com/resource/en/datasheet/DM00071990.pdf + // 6.3.24 Reference voltage + const VREFINT_MV: u32 = 1210; // mV + + (u32::from(sample) * VREFINT_MV / u32::from(vrefint_sample)) as u16 + }; + + let convert_to_celcius = |sample| { + // From http://www.st.com/resource/en/datasheet/DM00071990.pdf + // 6.3.22 Temperature sensor characteristics + const V25: i32 = 760; // mV + const AVG_SLOPE: f32 = 2.5; // mV/C + + let sample_mv = convert_to_millivolts(sample) as i32; + + (sample_mv - V25) as f32 / AVG_SLOPE + 25.0 + }; + + info!("VrefInt: {}", vrefint_sample); + const MAX_ADC_SAMPLE: u16 = (1 << 12) - 1; + info!("VCCA: {} mV", convert_to_millivolts(MAX_ADC_SAMPLE)); + loop { + // Read pin let v = adc.read(&mut pin); - info!("--> {} - {} mV", v, adc.to_millivolts(v)); + info!("PC1: {} ({} mV)", v, convert_to_millivolts(v)); + + // Read internal temperature + let v = adc.read_internal(&mut temp); + let celcius = convert_to_celcius(v); + info!("Internal temp: {} ({} C)", v, celcius); + + // Read internal voltage reference + let v = adc.read_internal(&mut vrefint); + info!("VrefInt: {}", v); + Timer::after(Duration::from_millis(100)).await; } } diff --git a/examples/stm32f4/src/bin/can.rs b/examples/stm32f4/src/bin/can.rs index e8377b9a1..08bed88db 100644 --- a/examples/stm32f4/src/bin/can.rs +++ b/examples/stm32f4/src/bin/can.rs @@ -2,16 +2,25 @@ #![no_main] #![feature(type_alias_impl_trait)] -use cortex_m_rt::entry; use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::bind_interrupts; use embassy_stm32::can::bxcan::filter::Mask32; use embassy_stm32::can::bxcan::{Fifo, Frame, StandardId}; -use embassy_stm32::can::Can; +use embassy_stm32::can::{Can, Rx0InterruptHandler, Rx1InterruptHandler, SceInterruptHandler, TxInterruptHandler}; use embassy_stm32::gpio::{Input, Pull}; +use embassy_stm32::peripherals::CAN1; use {defmt_rtt as _, panic_probe as _}; -#[entry] -fn main() -> ! { +bind_interrupts!(struct Irqs { + CAN1_RX0 => Rx0InterruptHandler; + CAN1_RX1 => Rx1InterruptHandler; + CAN1_SCE => SceInterruptHandler; + CAN1_TX => TxInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { info!("Hello World!"); let mut p = embassy_stm32::init(Default::default()); @@ -23,11 +32,14 @@ fn main() -> ! { let rx_pin = Input::new(&mut p.PA11, Pull::Up); core::mem::forget(rx_pin); - let mut can = Can::new(p.CAN1, p.PA11, p.PA12); + let mut can = Can::new(p.CAN1, p.PA11, p.PA12, Irqs); - can.modify_filters().enable_bank(0, Fifo::Fifo0, Mask32::accept_all()); + can.as_mut() + .modify_filters() + .enable_bank(0, Fifo::Fifo0, Mask32::accept_all()); - can.modify_config() + can.as_mut() + .modify_config() .set_bit_timing(0x001c0003) // http://www.bittiming.can-wiki.info/ .set_loopback(true) // Receive own frames .set_silent(true) @@ -36,9 +48,8 @@ fn main() -> ! { let mut i: u8 = 0; loop { let tx_frame = Frame::new_data(unwrap!(StandardId::new(i as _)), [i]); - unwrap!(nb::block!(can.transmit(&tx_frame))); - while !can.is_transmitter_idle() {} - let rx_frame = unwrap!(nb::block!(can.receive())); + can.write(&tx_frame).await; + let (_, rx_frame) = can.read().await.unwrap(); info!("loopback frame {=u8}", unwrap!(rx_frame.data())[0]); i += 1; } diff --git a/examples/stm32f4/src/bin/dac.rs b/examples/stm32f4/src/bin/dac.rs index d97ae7082..3a6216712 100644 --- a/examples/stm32f4/src/bin/dac.rs +++ b/examples/stm32f4/src/bin/dac.rs @@ -4,7 +4,8 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_stm32::dac::{Channel, Dac, Value}; +use embassy_stm32::dac::{DacCh1, DacChannel, Value}; +use embassy_stm32::dma::NoDma; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] @@ -12,12 +13,12 @@ async fn main(_spawner: Spawner) -> ! { let p = embassy_stm32::init(Default::default()); info!("Hello World, dude!"); - let mut dac = Dac::new_1ch(p.DAC, p.PA4); + let mut dac = DacCh1::new(p.DAC, NoDma, p.PA4); loop { for v in 0..=255 { - unwrap!(dac.set(Channel::Ch1, Value::Bit8(to_sine_wave(v)))); - unwrap!(dac.trigger(Channel::Ch1)); + unwrap!(dac.set(Value::Bit8(to_sine_wave(v)))); + dac.trigger(); } } } diff --git a/examples/stm32f4/src/bin/eth.rs b/examples/stm32f4/src/bin/eth.rs new file mode 100644 index 000000000..d0b164393 --- /dev/null +++ b/examples/stm32f4/src/bin/eth.rs @@ -0,0 +1,111 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Ipv4Address, Stack, StackResources}; +use embassy_stm32::eth::generic_smi::GenericSMI; +use embassy_stm32::eth::{Ethernet, PacketQueue}; +use embassy_stm32::peripherals::ETH; +use embassy_stm32::rng::Rng; +use embassy_stm32::time::mhz; +use embassy_stm32::{bind_interrupts, eth, Config}; +use embassy_time::{Duration, Timer}; +use embedded_io::asynch::Write; +use static_cell::make_static; +use {defmt_rtt as _, panic_probe as _}; +bind_interrupts!(struct Irqs { + ETH => eth::InterruptHandler; +}); + +type Device = Ethernet<'static, ETH, GenericSMI>; + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) -> ! { + let mut config = Config::default(); + config.rcc.sys_ck = Some(mhz(200)); + let p = embassy_stm32::init(config); + + info!("Hello World!"); + + // Generate random seed. + let mut rng = Rng::new(p.RNG); + let mut seed = [0; 8]; + let _ = rng.async_fill_bytes(&mut seed).await; + let seed = u64::from_le_bytes(seed); + + let mac_addr = [0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF]; + + let device = Ethernet::new( + make_static!(PacketQueue::<16, 16>::new()), + p.ETH, + Irqs, + p.PA1, + p.PA2, + p.PC1, + p.PA7, + p.PC4, + p.PC5, + p.PG13, + p.PB13, + p.PG11, + GenericSMI::new(), + mac_addr, + 0, + ); + + let config = embassy_net::Config::dhcpv4(Default::default()); + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), + //}); + + // Init network stack + let stack = &*make_static!(Stack::new( + device, + config, + make_static!(StackResources::<2>::new()), + seed + )); + + // Launch network task + unwrap!(spawner.spawn(net_task(&stack))); + + info!("Network task initialized"); + + // Then we can use it! + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + + loop { + let mut socket = TcpSocket::new(&stack, &mut rx_buffer, &mut tx_buffer); + + socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); + + let remote_endpoint = (Ipv4Address::new(10, 42, 0, 1), 8000); + info!("connecting..."); + let r = socket.connect(remote_endpoint).await; + if let Err(e) = r { + info!("connect error: {:?}", e); + continue; + } + info!("connected!"); + let buf = [0; 1024]; + loop { + let r = socket.write_all(&buf).await; + if let Err(e) = r { + info!("write error: {:?}", e); + continue; + } + Timer::after(Duration::from_secs(1)).await; + } + } +} diff --git a/examples/stm32f4/src/bin/flash.rs b/examples/stm32f4/src/bin/flash.rs index 393d61e86..93c54e943 100644 --- a/examples/stm32f4/src/bin/flash.rs +++ b/examples/stm32f4/src/bin/flash.rs @@ -4,8 +4,7 @@ use defmt::{info, unwrap}; use embassy_executor::Spawner; -use embassy_stm32::flash::Flash; -use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; +use embassy_stm32::flash::{Blocking, Flash}; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] @@ -13,7 +12,9 @@ async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); info!("Hello Flash!"); - let mut f = Flash::unlock(p.FLASH); + // Once can also call `into_regions()` to get access to NorFlash implementations + // for each of the unique characteristics. + let mut f = Flash::new_blocking(p.FLASH); // Sector 5 test_flash(&mut f, 128 * 1024, 128 * 1024); @@ -25,7 +26,7 @@ async fn main(_spawner: Spawner) { test_flash(&mut f, (2048 - 128) * 1024, 128 * 1024); } -fn test_flash(f: &mut Flash, offset: u32, size: u32) { +fn test_flash(f: &mut Flash<'_, Blocking>, offset: u32, size: u32) { info!("Testing offset: {=u32:#X}, size: {=u32:#X}", offset, size); info!("Reading..."); @@ -34,7 +35,7 @@ fn test_flash(f: &mut Flash, offset: u32, size: u32) { info!("Read: {=[u8]:x}", buf); info!("Erasing..."); - unwrap!(f.erase(offset, offset + size)); + unwrap!(f.blocking_erase(offset, offset + size)); info!("Reading..."); let mut buf = [0u8; 32]; @@ -42,7 +43,7 @@ fn test_flash(f: &mut Flash, offset: u32, size: u32) { info!("Read after erase: {=[u8]:x}", buf); info!("Writing..."); - unwrap!(f.write( + unwrap!(f.blocking_write( offset, &[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, diff --git a/examples/stm32f4/src/bin/flash_async.rs b/examples/stm32f4/src/bin/flash_async.rs new file mode 100644 index 000000000..6c9689d9c --- /dev/null +++ b/examples/stm32f4/src/bin/flash_async.rs @@ -0,0 +1,85 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +use embassy_stm32::bind_interrupts; +use embassy_stm32::flash::{Flash, InterruptHandler}; +use embassy_stm32::gpio::{AnyPin, Level, Output, Pin, Speed}; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + FLASH => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello Flash!"); + + let mut f = Flash::new(p.FLASH, Irqs); + + // Led should blink uninterrupted during ~2sec erase operation + spawner.spawn(blinky(p.PB7.degrade())).unwrap(); + + // Test on bank 2 in order not to stall CPU. + test_flash(&mut f, 1024 * 1024, 128 * 1024).await; +} + +#[embassy_executor::task] +async fn blinky(p: AnyPin) { + let mut led = Output::new(p, Level::High, Speed::Low); + + loop { + info!("high"); + led.set_high(); + Timer::after(Duration::from_millis(300)).await; + + info!("low"); + led.set_low(); + Timer::after(Duration::from_millis(300)).await; + } +} + +async fn test_flash<'a>(f: &mut Flash<'a>, offset: u32, size: u32) { + info!("Testing offset: {=u32:#X}, size: {=u32:#X}", offset, size); + + info!("Reading..."); + let mut buf = [0u8; 32]; + unwrap!(f.read(offset, &mut buf)); + info!("Read: {=[u8]:x}", buf); + + info!("Erasing..."); + unwrap!(f.erase(offset, offset + size).await); + + info!("Reading..."); + let mut buf = [0u8; 32]; + unwrap!(f.read(offset, &mut buf)); + info!("Read after erase: {=[u8]:x}", buf); + + info!("Writing..."); + unwrap!( + f.write( + offset, + &[ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, + 29, 30, 31, 32 + ] + ) + .await + ); + + info!("Reading..."); + let mut buf = [0u8; 32]; + unwrap!(f.read(offset, &mut buf)); + info!("Read: {=[u8]:x}", buf); + assert_eq!( + &buf[..], + &[ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32 + ] + ); +} diff --git a/examples/stm32f4/src/bin/i2c.rs b/examples/stm32f4/src/bin/i2c.rs new file mode 100644 index 000000000..a92957325 --- /dev/null +++ b/examples/stm32f4/src/bin/i2c.rs @@ -0,0 +1,48 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::dma::NoDma; +use embassy_stm32::i2c::{Error, I2c, TimeoutI2c}; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, i2c, peripherals}; +use embassy_time::Duration; +use {defmt_rtt as _, panic_probe as _}; + +const ADDRESS: u8 = 0x5F; +const WHOAMI: u8 = 0x0F; + +bind_interrupts!(struct Irqs { + I2C2_EV => i2c::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello world!"); + let p = embassy_stm32::init(Default::default()); + + let mut i2c = I2c::new( + p.I2C2, + p.PB10, + p.PB11, + Irqs, + NoDma, + NoDma, + Hertz(100_000), + Default::default(), + ); + + // I2C bus can freeze if SCL line is shorted or due to a broken device that clock stretches for too long. + // TimeoutI2c allows recovering from such errors by throwing `Error::Timeout` after a given delay. + let mut timeout_i2c = TimeoutI2c::new(&mut i2c, Duration::from_millis(1000)); + + let mut data = [0u8; 1]; + + match timeout_i2c.blocking_write_read(ADDRESS, &[WHOAMI], &mut data) { + Ok(()) => info!("Whoami: {}", data[0]), + Err(Error::Timeout) => error!("Operation timed out"), + Err(e) => error!("I2c Error: {:?}", e), + } +} diff --git a/examples/stm32f4/src/bin/i2s_dma.rs b/examples/stm32f4/src/bin/i2s_dma.rs new file mode 100644 index 000000000..e8d7b5f77 --- /dev/null +++ b/examples/stm32f4/src/bin/i2s_dma.rs @@ -0,0 +1,36 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use core::fmt::Write; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::i2s::{Config, I2S}; +use embassy_stm32::time::Hertz; +use heapless::String; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let mut i2s = I2S::new( + p.SPI2, + p.PC3, // sd + p.PB12, // ws + p.PB10, // ck + p.PC6, // mck + p.DMA1_CH4, + p.DMA1_CH3, + Hertz(1_000_000), + Config::default(), + ); + + for n in 0u32.. { + let mut write: String<128> = String::new(); + core::write!(&mut write, "Hello DMA World {}!\r\n", n).unwrap(); + i2s.write(&mut write.as_bytes()).await.ok(); + } +} diff --git a/examples/stm32f4/src/bin/mco.rs b/examples/stm32f4/src/bin/mco.rs new file mode 100644 index 000000000..2b9ceebc3 --- /dev/null +++ b/examples/stm32f4/src/bin/mco.rs @@ -0,0 +1,30 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::rcc::{Mco, Mco1Source, Mco2Source, McoClock}; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let _mco1 = Mco::new(p.MCO1, p.PA8, Mco1Source::Hsi, McoClock::DIV1); + let _mco2 = Mco::new(p.MCO2, p.PC9, Mco2Source::Pll, McoClock::DIV4); + let mut led = Output::new(p.PB7, Level::High, Speed::Low); + + loop { + info!("high"); + led.set_high(); + Timer::after(Duration::from_millis(300)).await; + + info!("low"); + led.set_low(); + Timer::after(Duration::from_millis(300)).await; + } +} diff --git a/examples/stm32f4/src/bin/multiprio.rs b/examples/stm32f4/src/bin/multiprio.rs index 9e8228a4b..80bf59deb 100644 --- a/examples/stm32f4/src/bin/multiprio.rs +++ b/examples/stm32f4/src/bin/multiprio.rs @@ -59,9 +59,9 @@ use cortex_m_rt::entry; use defmt::*; -use embassy_stm32::executor::{Executor, InterruptExecutor}; +use embassy_executor::{Executor, InterruptExecutor}; use embassy_stm32::interrupt; -use embassy_stm32::interrupt::InterruptExt; +use embassy_stm32::interrupt::{InterruptExt, Priority}; use embassy_time::{Duration, Instant, Timer}; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -108,28 +108,34 @@ async fn run_low() { } } -static EXECUTOR_HIGH: StaticCell> = StaticCell::new(); -static EXECUTOR_MED: StaticCell> = StaticCell::new(); +static EXECUTOR_HIGH: InterruptExecutor = InterruptExecutor::new(); +static EXECUTOR_MED: InterruptExecutor = InterruptExecutor::new(); static EXECUTOR_LOW: StaticCell = StaticCell::new(); +#[interrupt] +unsafe fn UART4() { + EXECUTOR_HIGH.on_interrupt() +} + +#[interrupt] +unsafe fn UART5() { + EXECUTOR_MED.on_interrupt() +} + #[entry] fn main() -> ! { info!("Hello World!"); let _p = embassy_stm32::init(Default::default()); - // High-priority executor: SWI1_EGU1, priority level 6 - let irq = interrupt::take!(UART4); - irq.set_priority(interrupt::Priority::P6); - let executor = EXECUTOR_HIGH.init(InterruptExecutor::new(irq)); - let spawner = executor.start(); + // High-priority executor: UART4, priority level 6 + interrupt::UART4.set_priority(Priority::P6); + let spawner = EXECUTOR_HIGH.start(interrupt::UART4); unwrap!(spawner.spawn(run_high())); - // Medium-priority executor: SWI0_EGU0, priority level 7 - let irq = interrupt::take!(UART5); - irq.set_priority(interrupt::Priority::P7); - let executor = EXECUTOR_MED.init(InterruptExecutor::new(irq)); - let spawner = executor.start(); + // Medium-priority executor: UART5, priority level 7 + interrupt::UART5.set_priority(Priority::P7); + let spawner = EXECUTOR_MED.start(interrupt::UART5); unwrap!(spawner.spawn(run_med())); // Low priority executor: runs in thread mode, using WFE/SEV diff --git a/examples/stm32f4/src/bin/pwm_complementary.rs b/examples/stm32f4/src/bin/pwm_complementary.rs new file mode 100644 index 000000000..a8a68ed6e --- /dev/null +++ b/examples/stm32f4/src/bin/pwm_complementary.rs @@ -0,0 +1,52 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::pwm::complementary_pwm::{ComplementaryPwm, ComplementaryPwmPin}; +use embassy_stm32::pwm::simple_pwm::PwmPin; +use embassy_stm32::pwm::Channel; +use embassy_stm32::time::khz; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let ch1 = PwmPin::new_ch1(p.PE9); + let ch1n = ComplementaryPwmPin::new_ch1(p.PA7); + let mut pwm = ComplementaryPwm::new( + p.TIM1, + Some(ch1), + Some(ch1n), + None, + None, + None, + None, + None, + None, + khz(10), + ); + + let max = pwm.get_max_duty(); + pwm.set_dead_time(max / 1024); + + pwm.enable(Channel::Ch1); + + info!("PWM initialized"); + info!("PWM max duty {}", max); + + loop { + pwm.set_duty(Channel::Ch1, 0); + Timer::after(Duration::from_millis(300)).await; + pwm.set_duty(Channel::Ch1, max / 4); + Timer::after(Duration::from_millis(300)).await; + pwm.set_duty(Channel::Ch1, max / 2); + Timer::after(Duration::from_millis(300)).await; + pwm.set_duty(Channel::Ch1, max - 1); + Timer::after(Duration::from_millis(300)).await; + } +} diff --git a/examples/stm32f4/src/bin/rtc.rs b/examples/stm32f4/src/bin/rtc.rs new file mode 100644 index 000000000..0eca58203 --- /dev/null +++ b/examples/stm32f4/src/bin/rtc.rs @@ -0,0 +1,30 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use chrono::{NaiveDate, NaiveDateTime}; +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::rtc::{Rtc, RtcConfig}; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let now = NaiveDate::from_ymd_opt(2020, 5, 15) + .unwrap() + .and_hms_opt(10, 30, 15) + .unwrap(); + + let mut rtc = Rtc::new(p.RTC, RtcConfig::default()); + + rtc.set_datetime(now.into()).expect("datetime not set"); + + // In reality the delay would be much longer + Timer::after(Duration::from_millis(20000)).await; + + let _then: NaiveDateTime = rtc.now().unwrap().into(); +} diff --git a/examples/stm32f4/src/bin/sdmmc.rs b/examples/stm32f4/src/bin/sdmmc.rs index 0edd8a61a..6ec7d0fec 100644 --- a/examples/stm32f4/src/bin/sdmmc.rs +++ b/examples/stm32f4/src/bin/sdmmc.rs @@ -4,23 +4,30 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_stm32::sdmmc::Sdmmc; +use embassy_stm32::sdmmc::{DataBlock, Sdmmc}; use embassy_stm32::time::mhz; -use embassy_stm32::{interrupt, Config}; +use embassy_stm32::{bind_interrupts, peripherals, sdmmc, Config}; use {defmt_rtt as _, panic_probe as _}; +/// This is a safeguard to not overwrite any data on the SD card. +/// If you don't care about SD card contents, set this to `true` to test writes. +const ALLOW_WRITES: bool = false; + +bind_interrupts!(struct Irqs { + SDIO => sdmmc::InterruptHandler; +}); + #[embassy_executor::main] -async fn main(_spawner: Spawner) -> ! { +async fn main(_spawner: Spawner) { let mut config = Config::default(); config.rcc.sys_ck = Some(mhz(48)); + config.rcc.pll48 = true; let p = embassy_stm32::init(config); info!("Hello World!"); - let irq = interrupt::take!(SDIO); - let mut sdmmc = Sdmmc::new_4bit( p.SDIO, - irq, + Irqs, p.DMA2_CH3, p.PC12, p.PD2, @@ -34,11 +41,51 @@ async fn main(_spawner: Spawner) -> ! { // Should print 400kHz for initialization info!("Configured clock: {}", sdmmc.clock().0); - unwrap!(sdmmc.init_card(mhz(25)).await); + let mut err = None; + loop { + match sdmmc.init_card(mhz(24)).await { + Ok(_) => break, + Err(e) => { + if err != Some(e) { + info!("waiting for card error, retrying: {:?}", e); + err = Some(e); + } + } + } + } let card = unwrap!(sdmmc.card()); info!("Card: {:#?}", Debug2Format(card)); + info!("Clock: {}", sdmmc.clock()); - loop {} + // Arbitrary block index + let block_idx = 16; + + // SDMMC uses `DataBlock` instead of `&[u8]` to ensure 4 byte alignment required by the hardware. + let mut block = DataBlock([0u8; 512]); + + sdmmc.read_block(block_idx, &mut block).await.unwrap(); + info!("Read: {=[u8]:X}...{=[u8]:X}", block[..8], block[512 - 8..]); + + if !ALLOW_WRITES { + info!("Writing is disabled."); + loop {} + } + + info!("Filling block with 0x55"); + block.fill(0x55); + sdmmc.write_block(block_idx, &block).await.unwrap(); + info!("Write done"); + + sdmmc.read_block(block_idx, &mut block).await.unwrap(); + info!("Read: {=[u8]:X}...{=[u8]:X}", block[..8], block[512 - 8..]); + + info!("Filling block with 0xAA"); + block.fill(0xAA); + sdmmc.write_block(block_idx, &block).await.unwrap(); + info!("Write done"); + + sdmmc.read_block(block_idx, &mut block).await.unwrap(); + info!("Read: {=[u8]:X}...{=[u8]:X}", block[..8], block[512 - 8..]); } diff --git a/examples/stm32f4/src/bin/usart.rs b/examples/stm32f4/src/bin/usart.rs index 90ad882b8..7680fe845 100644 --- a/examples/stm32f4/src/bin/usart.rs +++ b/examples/stm32f4/src/bin/usart.rs @@ -6,8 +6,13 @@ use cortex_m_rt::entry; use defmt::*; use embassy_stm32::dma::NoDma; use embassy_stm32::usart::{Config, Uart}; +use embassy_stm32::{bind_interrupts, peripherals, usart}; use {defmt_rtt as _, panic_probe as _}; +bind_interrupts!(struct Irqs { + USART3 => usart::InterruptHandler; +}); + #[entry] fn main() -> ! { info!("Hello World!"); @@ -15,7 +20,7 @@ fn main() -> ! { let p = embassy_stm32::init(Default::default()); let config = Config::default(); - let mut usart = Uart::new(p.USART3, p.PD9, p.PD8, NoDma, NoDma, config); + let mut usart = Uart::new(p.USART3, p.PD9, p.PD8, Irqs, NoDma, NoDma, config); unwrap!(usart.blocking_write(b"Hello Embassy World!\r\n")); info!("wrote Hello, starting echo"); diff --git a/examples/stm32f4/src/bin/usart_buffered.rs b/examples/stm32f4/src/bin/usart_buffered.rs index 7bcecbd26..c573dc3a3 100644 --- a/examples/stm32f4/src/bin/usart_buffered.rs +++ b/examples/stm32f4/src/bin/usart_buffered.rs @@ -4,25 +4,25 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_stm32::dma::NoDma; -use embassy_stm32::interrupt; -use embassy_stm32::usart::{BufferedUart, Config, State, Uart}; +use embassy_stm32::usart::{BufferedUart, Config}; +use embassy_stm32::{bind_interrupts, peripherals, usart}; use embedded_io::asynch::BufRead; use {defmt_rtt as _, panic_probe as _}; +bind_interrupts!(struct Irqs { + USART3 => usart::BufferedInterruptHandler; +}); + #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); info!("Hello World!"); let config = Config::default(); - let usart = Uart::new(p.USART3, p.PD9, p.PD8, NoDma, NoDma, config); - let mut state = State::new(); - let irq = interrupt::take!(USART3); let mut tx_buf = [0u8; 32]; let mut rx_buf = [0u8; 32]; - let mut buf_usart = BufferedUart::new(&mut state, usart, irq, &mut tx_buf, &mut rx_buf); + let mut buf_usart = BufferedUart::new(p.USART3, Irqs, p.PD9, p.PD8, &mut tx_buf, &mut rx_buf, config); loop { let buf = buf_usart.fill_buf().await.unwrap(); diff --git a/examples/stm32f4/src/bin/usart_dma.rs b/examples/stm32f4/src/bin/usart_dma.rs index bb41b8b4f..3408ec370 100644 --- a/examples/stm32f4/src/bin/usart_dma.rs +++ b/examples/stm32f4/src/bin/usart_dma.rs @@ -8,16 +8,21 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::dma::NoDma; use embassy_stm32::usart::{Config, Uart}; +use embassy_stm32::{bind_interrupts, peripherals, usart}; use heapless::String; use {defmt_rtt as _, panic_probe as _}; +bind_interrupts!(struct Irqs { + USART3 => usart::InterruptHandler; +}); + #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); info!("Hello World!"); let config = Config::default(); - let mut usart = Uart::new(p.USART3, p.PD9, p.PD8, p.DMA1_CH3, NoDma, config); + let mut usart = Uart::new(p.USART3, p.PD9, p.PD8, Irqs, p.DMA1_CH3, NoDma, config); for n in 0u32.. { let mut s: String<128> = String::new(); diff --git a/examples/stm32f4/src/bin/usb_ethernet.rs b/examples/stm32f4/src/bin/usb_ethernet.rs new file mode 100644 index 000000000..b1f01417c --- /dev/null +++ b/examples/stm32f4/src/bin/usb_ethernet.rs @@ -0,0 +1,164 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Stack, StackResources}; +use embassy_stm32::rng::Rng; +use embassy_stm32::time::mhz; +use embassy_stm32::usb_otg::Driver; +use embassy_stm32::{bind_interrupts, peripherals, usb_otg, Config}; +use embassy_usb::class::cdc_ncm::embassy_net::{Device, Runner, State as NetState}; +use embassy_usb::class::cdc_ncm::{CdcNcmClass, State}; +use embassy_usb::{Builder, UsbDevice}; +use embedded_io::asynch::Write; +use static_cell::make_static; +use {defmt_rtt as _, panic_probe as _}; + +type UsbDriver = Driver<'static, embassy_stm32::peripherals::USB_OTG_FS>; + +const MTU: usize = 1514; + +#[embassy_executor::task] +async fn usb_task(mut device: UsbDevice<'static, UsbDriver>) -> ! { + device.run().await +} + +#[embassy_executor::task] +async fn usb_ncm_task(class: Runner<'static, UsbDriver, MTU>) -> ! { + class.run().await +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + +bind_interrupts!(struct Irqs { + OTG_FS => usb_otg::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + info!("Hello World!"); + + let mut config = Config::default(); + config.rcc.pll48 = true; + config.rcc.sys_ck = Some(mhz(48)); + + let p = embassy_stm32::init(config); + + // Create the driver, from the HAL. + let ep_out_buffer = &mut make_static!([0; 256])[..]; + let mut config = embassy_stm32::usb_otg::Config::default(); + config.vbus_detection = true; + let driver = Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, ep_out_buffer, config); + + // Create embassy-usb Config + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-Ethernet example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // Required for Windows support. + config.composite_with_iads = true; + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + + // Create embassy-usb DeviceBuilder using the driver and config. + let mut builder = Builder::new( + driver, + config, + &mut make_static!([0; 256])[..], + &mut make_static!([0; 256])[..], + &mut make_static!([0; 256])[..], + &mut make_static!([0; 128])[..], + ); + + // Our MAC addr. + let our_mac_addr = [0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC]; + // Host's MAC addr. This is the MAC the host "thinks" its USB-to-ethernet adapter has. + let host_mac_addr = [0x88, 0x88, 0x88, 0x88, 0x88, 0x88]; + + // Create classes on the builder. + let class = CdcNcmClass::new(&mut builder, make_static!(State::new()), host_mac_addr, 64); + + // Build the builder. + let usb = builder.build(); + + unwrap!(spawner.spawn(usb_task(usb))); + + let (runner, device) = class.into_embassy_net_device::(make_static!(NetState::new()), our_mac_addr); + unwrap!(spawner.spawn(usb_ncm_task(runner))); + + let config = embassy_net::Config::dhcpv4(Default::default()); + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), + //}); + + // Generate random seed + let mut rng = Rng::new(p.RNG); + let mut seed = [0; 8]; + unwrap!(rng.async_fill_bytes(&mut seed).await); + let seed = u64::from_le_bytes(seed); + + // Init network stack + let stack = &*make_static!(Stack::new( + device, + config, + make_static!(StackResources::<2>::new()), + seed + )); + + unwrap!(spawner.spawn(net_task(stack))); + + // And now we can use it! + + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; + let mut buf = [0; 4096]; + + loop { + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); + + info!("Listening on TCP:1234..."); + if let Err(e) = socket.accept(1234).await { + warn!("accept error: {:?}", e); + continue; + } + + info!("Received connection from {:?}", socket.remote_endpoint()); + + loop { + let n = match socket.read(&mut buf).await { + Ok(0) => { + warn!("read EOF"); + break; + } + Ok(n) => n, + Err(e) => { + warn!("read error: {:?}", e); + break; + } + }; + + info!("rxd {:02x}", &buf[..n]); + + match socket.write_all(&buf[..n]).await { + Ok(()) => {} + Err(e) => { + warn!("write error: {:?}", e); + break; + } + }; + } + } +} diff --git a/examples/stm32f4/src/bin/usb_serial.rs b/examples/stm32f4/src/bin/usb_serial.rs new file mode 100644 index 000000000..4ff6452ef --- /dev/null +++ b/examples/stm32f4/src/bin/usb_serial.rs @@ -0,0 +1,110 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::{panic, *}; +use embassy_executor::Spawner; +use embassy_stm32::time::mhz; +use embassy_stm32::usb_otg::{Driver, Instance}; +use embassy_stm32::{bind_interrupts, peripherals, usb_otg, Config}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::Builder; +use futures::future::join; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + OTG_FS => usb_otg::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + let mut config = Config::default(); + config.rcc.pll48 = true; + config.rcc.sys_ck = Some(mhz(48)); + + let p = embassy_stm32::init(config); + + // Create the driver, from the HAL. + let mut ep_out_buffer = [0u8; 256]; + let mut config = embassy_stm32::usb_otg::Config::default(); + config.vbus_detection = true; + let driver = Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config); + + // Create embassy-usb Config + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial example"); + config.serial_number = Some("12345678"); + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut device_descriptor = [0; 256]; + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut device_descriptor, + &mut config_descriptor, + &mut bos_descriptor, + &mut control_buf, + ); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} diff --git a/examples/stm32f4/src/bin/wdt.rs b/examples/stm32f4/src/bin/wdt.rs index b2c587fa1..e5d122af7 100644 --- a/examples/stm32f4/src/bin/wdt.rs +++ b/examples/stm32f4/src/bin/wdt.rs @@ -17,9 +17,7 @@ async fn main(_spawner: Spawner) { let mut led = Output::new(p.PB7, Level::High, Speed::Low); let mut wdt = IndependentWatchdog::new(p.IWDG, 1_000_000); - unsafe { - wdt.unleash(); - } + wdt.unleash(); let mut i = 0; @@ -36,9 +34,7 @@ async fn main(_spawner: Spawner) { // MCU should restart in 1 second after the last pet. if i < 5 { info!("Petting watchdog"); - unsafe { - wdt.pet(); - } + wdt.pet(); } i += 1; diff --git a/examples/stm32f7/.cargo/config.toml b/examples/stm32f7/.cargo/config.toml index b07ad158c..9088eea6e 100644 --- a/examples/stm32f7/.cargo/config.toml +++ b/examples/stm32f7/.cargo/config.toml @@ -1,6 +1,6 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -# replace STM32F429ZITx with your chip as listed in `probe-run --list-chips` -runner = "probe-run --chip STM32F767ZITx" +# replace STM32F429ZITx with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32F767ZITx" [build] target = "thumbv7em-none-eabihf" diff --git a/examples/stm32f7/Cargo.toml b/examples/stm32f7/Cargo.toml index a446fe3fb..84d7b79c5 100644 --- a/examples/stm32f7/Cargo.toml +++ b/examples/stm32f7/Cargo.toml @@ -2,19 +2,21 @@ edition = "2021" name = "embassy-stm32f7-examples" version = "0.1.0" +license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } -embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-32768hz"] } -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "net", "stm32f767zi", "unstable-pac", "time-driver-any", "exti"] } -embassy-net = { path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "pool-16"] } -embedded-io = { version = "0.3.0", features = ["async"] } +embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32f767zi", "unstable-pac", "time-driver-any", "exti"] } +embassy-net = { path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet"] } +embedded-io = { version = "0.4.0", features = ["async"] } +embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } defmt = "0.3" -defmt-rtt = "0.3" +defmt-rtt = "0.4" -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" panic-probe = { version = "0.3", features = ["print-defmt"] } @@ -24,4 +26,4 @@ nb = "1.0.0" rand_core = "0.6.3" critical-section = "1.1" embedded-storage = "0.3.0" -static_cell = "1.0" +static_cell = { version = "1.1", features = ["nightly"]} diff --git a/examples/stm32f7/build.rs b/examples/stm32f7/build.rs index c4e15f19c..2b5d412a9 100644 --- a/examples/stm32f7/build.rs +++ b/examples/stm32f7/build.rs @@ -1,9 +1,8 @@ //! adapted from https://github.com/stm32-rs/stm32f7xx-hal/blob/master/build.rs -use std::env; use std::fs::File; use std::io::prelude::*; -use std::io::{self}; use std::path::PathBuf; +use std::{env, io}; #[derive(Debug)] enum Error { diff --git a/examples/stm32f7/src/bin/adc.rs b/examples/stm32f7/src/bin/adc.rs index 80fad8c41..70b3b2a75 100644 --- a/examples/stm32f7/src/bin/adc.rs +++ b/examples/stm32f7/src/bin/adc.rs @@ -16,9 +16,19 @@ async fn main(_spawner: Spawner) { let mut adc = Adc::new(p.ADC1, &mut Delay); let mut pin = p.PA3; + let mut vrefint = adc.enable_vrefint(); + let vrefint_sample = adc.read_internal(&mut vrefint); + let convert_to_millivolts = |sample| { + // From http://www.st.com/resource/en/datasheet/DM00273119.pdf + // 6.3.27 Reference voltage + const VREFINT_MV: u32 = 1210; // mV + + (u32::from(sample) * VREFINT_MV / u32::from(vrefint_sample)) as u16 + }; + loop { let v = adc.read(&mut pin); - info!("--> {} - {} mV", v, adc.to_millivolts(v)); + info!("--> {} - {} mV", v, convert_to_millivolts(v)); Timer::after(Duration::from_millis(100)).await; } } diff --git a/examples/stm32f7/src/bin/can.rs b/examples/stm32f7/src/bin/can.rs new file mode 100644 index 000000000..1b5b377ea --- /dev/null +++ b/examples/stm32f7/src/bin/can.rs @@ -0,0 +1,66 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::bind_interrupts; +use embassy_stm32::can::bxcan::filter::Mask32; +use embassy_stm32::can::bxcan::{Fifo, Frame, StandardId}; +use embassy_stm32::can::{ + Can, CanTx, Rx0InterruptHandler, Rx1InterruptHandler, SceInterruptHandler, TxInterruptHandler, +}; +use embassy_stm32::gpio::{Input, Pull}; +use embassy_stm32::peripherals::CAN3; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + CAN3_RX0 => Rx0InterruptHandler; + CAN3_RX1 => Rx1InterruptHandler; + CAN3_SCE => SceInterruptHandler; + CAN3_TX => TxInterruptHandler; +}); + +#[embassy_executor::task] +pub async fn send_can_message(tx: &'static mut CanTx<'static, 'static, CAN3>) { + loop { + let frame = Frame::new_data(unwrap!(StandardId::new(0 as _)), [0]); + tx.write(&frame).await; + embassy_time::Timer::after(embassy_time::Duration::from_secs(1)).await; + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + info!("Hello World!"); + + let mut p = embassy_stm32::init(Default::default()); + + // The next two lines are a workaround for testing without transceiver. + // To synchronise to the bus the RX input needs to see a high level. + // Use `mem::forget()` to release the borrow on the pin but keep the + // pull-up resistor enabled. + let rx_pin = Input::new(&mut p.PA15, Pull::Up); + core::mem::forget(rx_pin); + + let can: &'static mut Can<'static, CAN3> = static_cell::make_static!(Can::new(p.CAN3, p.PA8, p.PA15, Irqs)); + can.as_mut() + .modify_filters() + .enable_bank(0, Fifo::Fifo0, Mask32::accept_all()); + + can.as_mut() + .modify_config() + .set_bit_timing(0x001c0001) // http://www.bittiming.can-wiki.info/ + .set_loopback(true) + .enable(); + + let (tx, mut rx) = can.split(); + + let tx: &'static mut CanTx<'static, 'static, CAN3> = static_cell::make_static!(tx); + spawner.spawn(send_can_message(tx)).unwrap(); + + loop { + let frame = rx.read().await.unwrap(); + println!("Received: {:?}", frame); + } +} diff --git a/examples/stm32f7/src/bin/eth.rs b/examples/stm32f7/src/bin/eth.rs index 5202edf62..c6b2ba45c 100644 --- a/examples/stm32f7/src/bin/eth.rs +++ b/examples/stm32f7/src/bin/eth.rs @@ -7,26 +7,21 @@ use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; use embassy_net::{Ipv4Address, Stack, StackResources}; use embassy_stm32::eth::generic_smi::GenericSMI; -use embassy_stm32::eth::{Ethernet, State}; +use embassy_stm32::eth::{Ethernet, PacketQueue}; use embassy_stm32::peripherals::ETH; use embassy_stm32::rng::Rng; use embassy_stm32::time::mhz; -use embassy_stm32::{interrupt, Config}; +use embassy_stm32::{bind_interrupts, eth, Config}; use embassy_time::{Duration, Timer}; use embedded_io::asynch::Write; use rand_core::RngCore; -use static_cell::StaticCell; +use static_cell::make_static; use {defmt_rtt as _, panic_probe as _}; +bind_interrupts!(struct Irqs { + ETH => eth::InterruptHandler; +}); -macro_rules! singleton { - ($val:expr) => {{ - type T = impl Sized; - static STATIC_CELL: StaticCell = StaticCell::new(); - STATIC_CELL.init_with(move || $val) - }}; -} - -type Device = Ethernet<'static, ETH, GenericSMI, 4, 4>; +type Device = Ethernet<'static, ETH, GenericSMI>; #[embassy_executor::task] async fn net_task(stack: &'static Stack) -> ! { @@ -47,41 +42,38 @@ async fn main(spawner: Spawner) -> ! { rng.fill_bytes(&mut seed); let seed = u64::from_le_bytes(seed); - let eth_int = interrupt::take!(ETH); let mac_addr = [0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF]; - let device = unsafe { - Ethernet::new( - singleton!(State::new()), - p.ETH, - eth_int, - p.PA1, - p.PA2, - p.PC1, - p.PA7, - p.PC4, - p.PC5, - p.PG13, - p.PB13, - p.PG11, - GenericSMI, - mac_addr, - 0, - ) - }; + let device = Ethernet::new( + make_static!(PacketQueue::<16, 16>::new()), + p.ETH, + Irqs, + p.PA1, + p.PA2, + p.PC1, + p.PA7, + p.PC4, + p.PC5, + p.PG13, + p.PB13, + p.PG11, + GenericSMI::new(), + mac_addr, + 0, + ); - let config = embassy_net::ConfigStrategy::Dhcp; - //let config = embassy_net::ConfigStrategy::Static(embassy_net::Config { + let config = embassy_net::Config::dhcpv4(Default::default()); + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), // dns_servers: Vec::new(), // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), //}); // Init network stack - let stack = &*singleton!(Stack::new( + let stack = &*make_static!(Stack::new( device, config, - singleton!(StackResources::<1, 2, 8>::new()), + make_static!(StackResources::<2>::new()), seed )); @@ -91,13 +83,13 @@ async fn main(spawner: Spawner) -> ! { info!("Network task initialized"); // Then we can use it! - let mut rx_buffer = [0; 1024]; - let mut tx_buffer = [0; 1024]; + let mut rx_buffer = [0; 4096]; + let mut tx_buffer = [0; 4096]; loop { let mut socket = TcpSocket::new(&stack, &mut rx_buffer, &mut tx_buffer); - socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10))); + socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); let remote_endpoint = (Ipv4Address::new(10, 42, 0, 1), 8000); info!("connecting..."); @@ -107,11 +99,12 @@ async fn main(spawner: Spawner) -> ! { continue; } info!("connected!"); + let buf = [0; 1024]; loop { - let r = socket.write_all(b"Hello\n").await; + let r = socket.write_all(&buf).await; if let Err(e) = r { info!("write error: {:?}", e); - return; + continue; } Timer::after(Duration::from_secs(1)).await; } diff --git a/examples/stm32f7/src/bin/flash.rs b/examples/stm32f7/src/bin/flash.rs index c10781d0c..35d3059be 100644 --- a/examples/stm32f7/src/bin/flash.rs +++ b/examples/stm32f7/src/bin/flash.rs @@ -6,7 +6,6 @@ use defmt::{info, unwrap}; use embassy_executor::Spawner; use embassy_stm32::flash::Flash; use embassy_time::{Duration, Timer}; -use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] @@ -14,28 +13,28 @@ async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); info!("Hello Flash!"); - const ADDR: u32 = 0x8_0000; + const ADDR: u32 = 0x8_0000; // This is the offset into the third region, the absolute address is 4x32K + 128K + 0x8_0000. // wait a bit before accessing the flash Timer::after(Duration::from_millis(300)).await; - let mut f = Flash::unlock(p.FLASH); + let mut f = Flash::new_blocking(p.FLASH).into_blocking_regions().bank1_region3; info!("Reading..."); let mut buf = [0u8; 32]; - unwrap!(f.read(ADDR, &mut buf)); + unwrap!(f.blocking_read(ADDR, &mut buf)); info!("Read: {=[u8]:x}", buf); info!("Erasing..."); - unwrap!(f.erase(ADDR, ADDR + 256 * 1024)); + unwrap!(f.blocking_erase(ADDR, ADDR + 256 * 1024)); info!("Reading..."); let mut buf = [0u8; 32]; - unwrap!(f.read(ADDR, &mut buf)); + unwrap!(f.blocking_read(ADDR, &mut buf)); info!("Read after erase: {=[u8]:x}", buf); info!("Writing..."); - unwrap!(f.write( + unwrap!(f.blocking_write( ADDR, &[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, @@ -45,7 +44,7 @@ async fn main(_spawner: Spawner) { info!("Reading..."); let mut buf = [0u8; 32]; - unwrap!(f.read(ADDR, &mut buf)); + unwrap!(f.blocking_read(ADDR, &mut buf)); info!("Read: {=[u8]:x}", buf); assert_eq!( &buf[..], diff --git a/examples/stm32f7/src/bin/sdmmc.rs b/examples/stm32f7/src/bin/sdmmc.rs index 3bf427eca..9d43892a0 100644 --- a/examples/stm32f7/src/bin/sdmmc.rs +++ b/examples/stm32f7/src/bin/sdmmc.rs @@ -6,22 +6,25 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::sdmmc::Sdmmc; use embassy_stm32::time::mhz; -use embassy_stm32::{interrupt, Config}; +use embassy_stm32::{bind_interrupts, peripherals, sdmmc, Config}; use {defmt_rtt as _, panic_probe as _}; +bind_interrupts!(struct Irqs { + SDMMC1 => sdmmc::InterruptHandler; +}); + #[embassy_executor::main] -async fn main(_spawner: Spawner) -> ! { +async fn main(_spawner: Spawner) { let mut config = Config::default(); config.rcc.sys_ck = Some(mhz(200)); + config.rcc.pll48 = true; let p = embassy_stm32::init(config); info!("Hello World!"); - let irq = interrupt::take!(SDMMC1); - let mut sdmmc = Sdmmc::new_4bit( p.SDMMC1, - irq, + Irqs, p.DMA2_CH3, p.PC12, p.PD2, @@ -40,6 +43,4 @@ async fn main(_spawner: Spawner) -> ! { let card = unwrap!(sdmmc.card()); info!("Card: {:#?}", Debug2Format(card)); - - loop {} } diff --git a/examples/stm32f7/src/bin/usart_dma.rs b/examples/stm32f7/src/bin/usart_dma.rs index 07270479c..4700287a7 100644 --- a/examples/stm32f7/src/bin/usart_dma.rs +++ b/examples/stm32f7/src/bin/usart_dma.rs @@ -8,14 +8,19 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::dma::NoDma; use embassy_stm32::usart::{Config, Uart}; +use embassy_stm32::{bind_interrupts, peripherals, usart}; use heapless::String; use {defmt_rtt as _, panic_probe as _}; +bind_interrupts!(struct Irqs { + UART7 => usart::InterruptHandler; +}); + #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); let config = Config::default(); - let mut usart = Uart::new(p.UART7, p.PA8, p.PA15, p.DMA1_CH1, NoDma, config); + let mut usart = Uart::new(p.UART7, p.PA8, p.PA15, Irqs, p.DMA1_CH1, NoDma, config); for n in 0u32.. { let mut s: String<128> = String::new(); diff --git a/examples/stm32f7/src/bin/usb_serial.rs b/examples/stm32f7/src/bin/usb_serial.rs new file mode 100644 index 000000000..a2c76178b --- /dev/null +++ b/examples/stm32f7/src/bin/usb_serial.rs @@ -0,0 +1,111 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::{panic, *}; +use embassy_executor::Spawner; +use embassy_stm32::time::mhz; +use embassy_stm32::usb_otg::{Driver, Instance}; +use embassy_stm32::{bind_interrupts, peripherals, usb_otg, Config}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::Builder; +use futures::future::join; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + OTG_FS => usb_otg::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + let mut config = Config::default(); + config.rcc.hse = Some(mhz(8)); + config.rcc.pll48 = true; + config.rcc.sys_ck = Some(mhz(200)); + + let p = embassy_stm32::init(config); + + // Create the driver, from the HAL. + let mut ep_out_buffer = [0u8; 256]; + let mut config = embassy_stm32::usb_otg::Config::default(); + config.vbus_detection = true; + let driver = Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config); + + // Create embassy-usb Config + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial example"); + config.serial_number = Some("12345678"); + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut device_descriptor = [0; 256]; + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut device_descriptor, + &mut config_descriptor, + &mut bos_descriptor, + &mut control_buf, + ); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} diff --git a/examples/stm32g0/.cargo/config.toml b/examples/stm32g0/.cargo/config.toml index 4f776d68f..35cca5412 100644 --- a/examples/stm32g0/.cargo/config.toml +++ b/examples/stm32g0/.cargo/config.toml @@ -1,6 +1,6 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -# replace STM32G071C8Rx with your chip as listed in `probe-run --list-chips` -runner = "probe-run --chip STM32G071RBTx" +# replace STM32G071C8Rx with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32G071RBTx" [build] target = "thumbv6m-none-eabi" diff --git a/examples/stm32g0/Cargo.toml b/examples/stm32g0/Cargo.toml index 30f2b86f8..c88282d91 100644 --- a/examples/stm32g0/Cargo.toml +++ b/examples/stm32g0/Cargo.toml @@ -2,17 +2,18 @@ edition = "2021" name = "embassy-stm32g0-examples" version = "0.1.0" +license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } -embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-32768hz"] } +embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "time-driver-any", "stm32g071rb", "memory-x", "unstable-pac", "exti"] } defmt = "0.3" -defmt-rtt = "0.3" +defmt-rtt = "0.4" -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" panic-probe = { version = "0.3", features = ["print-defmt"] } diff --git a/examples/stm32g0/src/bin/spi_neopixel.rs b/examples/stm32g0/src/bin/spi_neopixel.rs new file mode 100644 index 000000000..81fdd15cb --- /dev/null +++ b/examples/stm32g0/src/bin/spi_neopixel.rs @@ -0,0 +1,101 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::dma::word::U5; +use embassy_stm32::dma::NoDma; +use embassy_stm32::spi::{Config, Spi}; +use embassy_stm32::time::Hertz; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +const NR_PIXELS: usize = 15; +const BITS_PER_PIXEL: usize = 24; // 24 for rgb, 32 for rgbw +const TOTAL_BITS: usize = NR_PIXELS * BITS_PER_PIXEL; + +struct RGB { + r: u8, + g: u8, + b: u8, +} +impl Default for RGB { + fn default() -> RGB { + RGB { r: 0, g: 0, b: 0 } + } +} +pub struct Ws2812 { + // Note that the U5 type controls the selection of 5 bits to output + bitbuffer: [U5; TOTAL_BITS], +} + +impl Ws2812 { + pub fn new() -> Ws2812 { + Ws2812 { + bitbuffer: [U5(0); TOTAL_BITS], + } + } + fn len(&self) -> usize { + return NR_PIXELS; + } + fn set(&mut self, idx: usize, rgb: RGB) { + self.render_color(idx, 0, rgb.g); + self.render_color(idx, 8, rgb.r); + self.render_color(idx, 16, rgb.b); + } + // transform one color byte into an array of 8 byte. Each byte in the array does represent 1 neopixel bit pattern + fn render_color(&mut self, pixel_idx: usize, offset: usize, color: u8) { + let mut bits = color as usize; + let mut idx = pixel_idx * BITS_PER_PIXEL + offset; + + // render one bit in one spi byte. High time first, then the low time + // clock should be 4 Mhz, 5 bits, each bit is 0.25 us. + // a one bit is send as a pulse of 0.75 high -- 0.50 low + // a zero bit is send as a pulse of 0.50 high -- 0.75 low + // clock frequency for the neopixel is exact 800 khz + // note that the mosi output should have a resistor to ground of 10k, + // to assure that between the bursts the line is low + for _i in 0..8 { + if idx >= TOTAL_BITS { + return; + } + let pattern = match bits & 0x80 { + 0x80 => 0b0000_1110, + _ => 0b000_1100, + }; + bits = bits << 1; + self.bitbuffer[idx] = U5(pattern); + idx += 1; + } + } +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Start test using spi as neopixel driver"); + + let mut spi = Spi::new_txonly_nosck(p.SPI1, p.PB5, p.DMA1_CH3, NoDma, Hertz(4_000_000), Config::default()); + + let mut neopixels = Ws2812::new(); + + loop { + let mut cnt: usize = 0; + for _i in 0..10 { + for idx in 0..neopixels.len() { + let color = match (cnt + idx) % 3 { + 0 => RGB { r: 0x21, g: 0, b: 0 }, + 1 => RGB { r: 0, g: 0x31, b: 0 }, + _ => RGB { r: 0, g: 0, b: 0x41 }, + }; + neopixels.set(idx, color); + } + cnt += 1; + // start sending the neopixel bit patters over spi to the neopixel string + spi.write(&neopixels.bitbuffer).await.ok(); + Timer::after(Duration::from_millis(500)).await; + } + Timer::after(Duration::from_millis(1000)).await; + } +} diff --git a/examples/stm32g4/.cargo/config.toml b/examples/stm32g4/.cargo/config.toml index 99feae119..d28ad069e 100644 --- a/examples/stm32g4/.cargo/config.toml +++ b/examples/stm32g4/.cargo/config.toml @@ -1,6 +1,6 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -# replace STM32G071C8Rx with your chip as listed in `probe-run --list-chips` -runner = "probe-run --chip STM32G484VETx" +# replace STM32G071C8Rx with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32G484VETx" [build] target = "thumbv7em-none-eabi" diff --git a/examples/stm32g4/Cargo.toml b/examples/stm32g4/Cargo.toml index f81df0b70..18bd03c39 100644 --- a/examples/stm32g4/Cargo.toml +++ b/examples/stm32g4/Cargo.toml @@ -2,16 +2,18 @@ edition = "2021" name = "embassy-stm32g4-examples" version = "0.1.0" +license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } -embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-32768hz"] } +embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "time-driver-any", "stm32g491re", "memory-x", "unstable-pac", "exti"] } embassy-hal-common = {version = "0.1.0", path = "../../embassy-hal-common" } +embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } defmt = "0.3" -defmt-rtt = "0.3" +defmt-rtt = "0.4" cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rt = "0.7.0" diff --git a/examples/stm32g4/src/bin/pll.rs b/examples/stm32g4/src/bin/pll.rs new file mode 100644 index 000000000..ef7d4800c --- /dev/null +++ b/examples/stm32g4/src/bin/pll.rs @@ -0,0 +1,35 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::rcc::{ClockSrc, Pll, PllM, PllN, PllR, PllSrc}; +use embassy_stm32::Config; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + + config.rcc.pll = Some(Pll { + source: PllSrc::HSI16, + prediv_m: PllM::Div4, + mul_n: PllN::Mul85, + div_p: None, + div_q: None, + // Main system clock at 170 MHz + div_r: Some(PllR::Div2), + }); + + config.rcc.mux = ClockSrc::PLL; + + let _p = embassy_stm32::init(config); + info!("Hello World!"); + + loop { + Timer::after(Duration::from_millis(1000)).await; + info!("1s elapsed"); + } +} diff --git a/examples/stm32g4/src/bin/pwm.rs b/examples/stm32g4/src/bin/pwm.rs index 017e89e41..8f7842ed7 100644 --- a/examples/stm32g4/src/bin/pwm.rs +++ b/examples/stm32g4/src/bin/pwm.rs @@ -15,8 +15,8 @@ async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); info!("Hello World!"); - let ch1 = PwmPin::new_ch1(p.PA5); - let mut pwm = SimplePwm::new(p.TIM2, Some(ch1), None, None, None, khz(10)); + let ch1 = PwmPin::new_ch1(p.PC0); + let mut pwm = SimplePwm::new(p.TIM1, Some(ch1), None, None, None, khz(10)); let max = pwm.get_max_duty(); pwm.enable(Channel::Ch1); diff --git a/examples/stm32g4/src/bin/usb_serial.rs b/examples/stm32g4/src/bin/usb_serial.rs new file mode 100644 index 000000000..77cfa67d3 --- /dev/null +++ b/examples/stm32g4/src/bin/usb_serial.rs @@ -0,0 +1,120 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::{panic, *}; +use embassy_executor::Spawner; +use embassy_stm32::rcc::{Clock48MhzSrc, ClockSrc, CrsConfig, CrsSyncSource, Pll, PllM, PllN, PllQ, PllR, PllSrc}; +use embassy_stm32::time::Hertz; +use embassy_stm32::usb::{self, Driver, Instance}; +use embassy_stm32::{bind_interrupts, peripherals, Config}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::Builder; +use futures::future::join; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USB_LP => usb::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + + // Change this to `false` to use the HSE clock source for the USB. This example assumes an 8MHz HSE. + const USE_HSI48: bool = true; + + let pllq_div = if USE_HSI48 { None } else { Some(PllQ::Div6) }; + + config.rcc.pll = Some(Pll { + source: PllSrc::HSE(Hertz(8_000_000)), + prediv_m: PllM::Div2, + mul_n: PllN::Mul72, + div_p: None, + div_q: pllq_div, + // Main system clock at 144 MHz + div_r: Some(PllR::Div2), + }); + + config.rcc.mux = ClockSrc::PLL; + + if USE_HSI48 { + // Sets up the Clock Recovery System (CRS) to use the USB SOF to trim the HSI48 oscillator. + config.rcc.clock_48mhz_src = Some(Clock48MhzSrc::Hsi48(Some(CrsConfig { + sync_src: CrsSyncSource::Usb, + }))); + } else { + config.rcc.clock_48mhz_src = Some(Clock48MhzSrc::PllQ); + } + + let p = embassy_stm32::init(config); + + info!("Hello World!"); + + let driver = Driver::new(p.USB, Irqs, p.PA12, p.PA11); + + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-Serial Example"); + config.serial_number = Some("123456"); + + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + let mut device_descriptor = [0; 256]; + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut device_descriptor, + &mut config_descriptor, + &mut bos_descriptor, + &mut control_buf, + ); + + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + let mut usb = builder.build(); + + let usb_fut = usb.run(); + + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} diff --git a/examples/stm32h5/.cargo/config.toml b/examples/stm32h5/.cargo/config.toml new file mode 100644 index 000000000..478146142 --- /dev/null +++ b/examples/stm32h5/.cargo/config.toml @@ -0,0 +1,8 @@ +[target.thumbv8m.main-none-eabihf] +runner = 'probe-rs run --chip STM32H563ZITx' + +[build] +target = "thumbv8m.main-none-eabihf" + +[env] +DEFMT_LOG = "trace" diff --git a/examples/stm32h5/Cargo.toml b/examples/stm32h5/Cargo.toml new file mode 100644 index 000000000..227bc28b4 --- /dev/null +++ b/examples/stm32h5/Cargo.toml @@ -0,0 +1,71 @@ +[package] +edition = "2021" +name = "embassy-stm32h5-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "unstable-traits", "tick-hz-32_768"] } +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32h563zi", "time-driver-any", "exti", "unstable-pac", "unstable-traits"] } +embassy-net = { path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet", "unstable-traits", "proto-ipv6"] } +embedded-io = { version = "0.4.0", features = ["async"] } +embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" +embedded-hal = "0.2.6" +embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11" } +embedded-hal-async = { version = "=0.2.0-alpha.2" } +embedded-nal-async = "0.4.0" +panic-probe = { version = "0.3", features = ["print-defmt"] } +futures = { version = "0.3.17", default-features = false, features = ["async-await"] } +heapless = { version = "0.7.5", default-features = false } +rand_core = "0.6.3" +critical-section = "1.1" +micromath = "2.0.0" +stm32-fmc = "0.2.4" +embedded-storage = "0.3.0" +static_cell = { version = "1.1", features = ["nightly"]} + +# cargo build/run +[profile.dev] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo test +[profile.test] +codegen-units = 1 +debug = 2 +debug-assertions = true # <- +incremental = false +opt-level = 3 # <- +overflow-checks = true # <- + +# cargo build/run --release +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- + +# cargo test --release +[profile.bench] +codegen-units = 1 +debug = 2 +debug-assertions = false # <- +incremental = false +lto = 'fat' +opt-level = 3 # <- +overflow-checks = false # <- diff --git a/examples/stm32h5/build.rs b/examples/stm32h5/build.rs new file mode 100644 index 000000000..8cd32d7ed --- /dev/null +++ b/examples/stm32h5/build.rs @@ -0,0 +1,5 @@ +fn main() { + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/examples/stm32h5/memory.x b/examples/stm32h5/memory.x new file mode 100644 index 000000000..456061509 --- /dev/null +++ b/examples/stm32h5/memory.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x08000000, LENGTH = 0x200000 + RAM : ORIGIN = 0x20000000, LENGTH = 0x50000 +} diff --git a/examples/stm32h5/src/bin/blinky.rs b/examples/stm32h5/src/bin/blinky.rs new file mode 100644 index 000000000..f9bf90d2e --- /dev/null +++ b/examples/stm32h5/src/bin/blinky.rs @@ -0,0 +1,27 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let mut led = Output::new(p.PB0, Level::High, Speed::Low); + + loop { + info!("high"); + led.set_high(); + Timer::after(Duration::from_millis(500)).await; + + info!("low"); + led.set_low(); + Timer::after(Duration::from_millis(500)).await; + } +} diff --git a/examples/stm32h5/src/bin/button_exti.rs b/examples/stm32h5/src/bin/button_exti.rs new file mode 100644 index 000000000..dfe587d41 --- /dev/null +++ b/examples/stm32h5/src/bin/button_exti.rs @@ -0,0 +1,27 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::gpio::{Input, Pull}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let button = Input::new(p.PC13, Pull::Down); + let mut button = ExtiInput::new(button, p.EXTI13); + + info!("Press the USER button..."); + + loop { + button.wait_for_rising_edge().await; + info!("Pressed!"); + button.wait_for_falling_edge().await; + info!("Released!"); + } +} diff --git a/examples/stm32h5/src/bin/eth.rs b/examples/stm32h5/src/bin/eth.rs new file mode 100644 index 000000000..0bff85ed8 --- /dev/null +++ b/examples/stm32h5/src/bin/eth.rs @@ -0,0 +1,131 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Ipv4Address, Stack, StackResources}; +use embassy_stm32::eth::generic_smi::GenericSMI; +use embassy_stm32::eth::{Ethernet, PacketQueue}; +use embassy_stm32::peripherals::ETH; +use embassy_stm32::rcc::{AHBPrescaler, APBPrescaler, Hse, HseMode, Pll, PllSource, Sysclk, VoltageScale}; +use embassy_stm32::rng::Rng; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, eth, Config}; +use embassy_time::{Duration, Timer}; +use embedded_io::asynch::Write; +use rand_core::RngCore; +use static_cell::make_static; +use {defmt_rtt as _, panic_probe as _}; +bind_interrupts!(struct Irqs { + ETH => eth::InterruptHandler; +}); + +type Device = Ethernet<'static, ETH, GenericSMI>; + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) -> ! { + let mut config = Config::default(); + config.rcc.hsi = None; + config.rcc.hsi48 = true; // needed for rng + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::BypassDigital, + }); + config.rcc.pll1 = Some(Pll { + source: PllSource::Hse, + prediv: 2, + mul: 125, + divp: Some(2), + divq: Some(2), + divr: None, + }); + config.rcc.ahb_pre = AHBPrescaler::NotDivided; + config.rcc.apb1_pre = APBPrescaler::NotDivided; + config.rcc.apb2_pre = APBPrescaler::NotDivided; + config.rcc.apb3_pre = APBPrescaler::NotDivided; + config.rcc.sys = Sysclk::Pll1P; + config.rcc.voltage_scale = VoltageScale::Scale0; + let p = embassy_stm32::init(config); + info!("Hello World!"); + + // Generate random seed. + let mut rng = Rng::new(p.RNG); + let mut seed = [0; 8]; + rng.fill_bytes(&mut seed); + let seed = u64::from_le_bytes(seed); + + let mac_addr = [0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF]; + + let device = Ethernet::new( + make_static!(PacketQueue::<4, 4>::new()), + p.ETH, + Irqs, + p.PA1, + p.PA2, + p.PC1, + p.PA7, + p.PC4, + p.PC5, + p.PG13, + p.PB15, + p.PG11, + GenericSMI::new(), + mac_addr, + 0, + ); + + let config = embassy_net::Config::dhcpv4(Default::default()); + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { + // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), + // dns_servers: Vec::new(), + // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), + //}); + + // Init network stack + let stack = &*make_static!(Stack::new( + device, + config, + make_static!(StackResources::<2>::new()), + seed + )); + + // Launch network task + unwrap!(spawner.spawn(net_task(&stack))); + + info!("Network task initialized"); + + // Then we can use it! + let mut rx_buffer = [0; 1024]; + let mut tx_buffer = [0; 1024]; + + loop { + let mut socket = TcpSocket::new(&stack, &mut rx_buffer, &mut tx_buffer); + + socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); + + let remote_endpoint = (Ipv4Address::new(10, 42, 0, 1), 8000); + info!("connecting..."); + let r = socket.connect(remote_endpoint).await; + if let Err(e) = r { + info!("connect error: {:?}", e); + Timer::after(Duration::from_secs(3)).await; + continue; + } + info!("connected!"); + loop { + let r = socket.write_all(b"Hello\n").await; + if let Err(e) = r { + info!("write error: {:?}", e); + continue; + } + Timer::after(Duration::from_secs(1)).await; + } + } +} diff --git a/examples/stm32h5/src/bin/i2c.rs b/examples/stm32h5/src/bin/i2c.rs new file mode 100644 index 000000000..8b6fe71ae --- /dev/null +++ b/examples/stm32h5/src/bin/i2c.rs @@ -0,0 +1,47 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::i2c::{Error, I2c, TimeoutI2c}; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, i2c, peripherals}; +use embassy_time::Duration; +use {defmt_rtt as _, panic_probe as _}; + +const ADDRESS: u8 = 0x5F; +const WHOAMI: u8 = 0x0F; + +bind_interrupts!(struct Irqs { + I2C2_EV => i2c::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello world!"); + let p = embassy_stm32::init(Default::default()); + + let mut i2c = I2c::new( + p.I2C2, + p.PB10, + p.PB11, + Irqs, + p.GPDMA1_CH4, + p.GPDMA1_CH5, + Hertz(100_000), + Default::default(), + ); + + // I2C bus can freeze if SCL line is shorted or due to a broken device that clock stretches for too long. + // TimeoutI2c allows recovering from such errors by throwing `Error::Timeout` after a given delay. + let mut timeout_i2c = TimeoutI2c::new(&mut i2c, Duration::from_millis(1000)); + + let mut data = [0u8; 1]; + + match timeout_i2c.blocking_write_read(ADDRESS, &[WHOAMI], &mut data) { + Ok(()) => info!("Whoami: {}", data[0]), + Err(Error::Timeout) => error!("Operation timed out"), + Err(e) => error!("I2c Error: {:?}", e), + } +} diff --git a/examples/stm32h5/src/bin/rng.rs b/examples/stm32h5/src/bin/rng.rs new file mode 100644 index 000000000..af9be0b62 --- /dev/null +++ b/examples/stm32h5/src/bin/rng.rs @@ -0,0 +1,20 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::rng::Rng; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let mut rng = Rng::new(p.RNG); + + let mut buf = [0u8; 16]; + unwrap!(rng.async_fill_bytes(&mut buf).await); + info!("random bytes: {:02x}", buf); +} diff --git a/examples/stm32h5/src/bin/usart.rs b/examples/stm32h5/src/bin/usart.rs new file mode 100644 index 000000000..0abb94abb --- /dev/null +++ b/examples/stm32h5/src/bin/usart.rs @@ -0,0 +1,46 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use cortex_m_rt::entry; +use defmt::*; +use embassy_executor::Executor; +use embassy_stm32::dma::NoDma; +use embassy_stm32::usart::{Config, Uart}; +use embassy_stm32::{bind_interrupts, peripherals, usart}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + UART7 => usart::InterruptHandler; +}); + +#[embassy_executor::task] +async fn main_task() { + let p = embassy_stm32::init(Default::default()); + + let config = Config::default(); + let mut usart = Uart::new(p.UART7, p.PF6, p.PF7, Irqs, NoDma, NoDma, config); + + unwrap!(usart.blocking_write(b"Hello Embassy World!\r\n")); + info!("wrote Hello, starting echo"); + + let mut buf = [0u8; 1]; + loop { + unwrap!(usart.blocking_read(&mut buf)); + unwrap!(usart.blocking_write(&buf)); + } +} + +static EXECUTOR: StaticCell = StaticCell::new(); + +#[entry] +fn main() -> ! { + info!("Hello World!"); + + let executor = EXECUTOR.init(Executor::new()); + + executor.run(|spawner| { + unwrap!(spawner.spawn(main_task())); + }) +} diff --git a/examples/stm32h5/src/bin/usart_dma.rs b/examples/stm32h5/src/bin/usart_dma.rs new file mode 100644 index 000000000..48264f884 --- /dev/null +++ b/examples/stm32h5/src/bin/usart_dma.rs @@ -0,0 +1,49 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use core::fmt::Write; + +use cortex_m_rt::entry; +use defmt::*; +use embassy_executor::Executor; +use embassy_stm32::dma::NoDma; +use embassy_stm32::usart::{Config, Uart}; +use embassy_stm32::{bind_interrupts, peripherals, usart}; +use heapless::String; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + UART7 => usart::InterruptHandler; +}); + +#[embassy_executor::task] +async fn main_task() { + let p = embassy_stm32::init(Default::default()); + + let config = Config::default(); + let mut usart = Uart::new(p.UART7, p.PF6, p.PF7, Irqs, p.GPDMA1_CH0, NoDma, config); + + for n in 0u32.. { + let mut s: String<128> = String::new(); + core::write!(&mut s, "Hello DMA World {}!\r\n", n).unwrap(); + + usart.write(s.as_bytes()).await.ok(); + + info!("wrote DMA"); + } +} + +static EXECUTOR: StaticCell = StaticCell::new(); + +#[entry] +fn main() -> ! { + info!("Hello World!"); + + let executor = EXECUTOR.init(Executor::new()); + + executor.run(|spawner| { + unwrap!(spawner.spawn(main_task())); + }) +} diff --git a/examples/stm32h5/src/bin/usart_split.rs b/examples/stm32h5/src/bin/usart_split.rs new file mode 100644 index 000000000..debd6f454 --- /dev/null +++ b/examples/stm32h5/src/bin/usart_split.rs @@ -0,0 +1,61 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::dma::NoDma; +use embassy_stm32::peripherals::{GPDMA1_CH1, UART7}; +use embassy_stm32::usart::{Config, Uart, UartRx}; +use embassy_stm32::{bind_interrupts, peripherals, usart}; +use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; +use embassy_sync::channel::Channel; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + UART7 => usart::InterruptHandler; +}); + +#[embassy_executor::task] +async fn writer(mut usart: Uart<'static, UART7, NoDma, NoDma>) { + unwrap!(usart.blocking_write(b"Hello Embassy World!\r\n")); + info!("wrote Hello, starting echo"); + + let mut buf = [0u8; 1]; + loop { + unwrap!(usart.blocking_read(&mut buf)); + unwrap!(usart.blocking_write(&buf)); + } +} + +static CHANNEL: Channel = Channel::new(); + +#[embassy_executor::main] +async fn main(spawner: Spawner) -> ! { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let config = Config::default(); + let mut usart = Uart::new(p.UART7, p.PF6, p.PF7, Irqs, p.GPDMA1_CH0, p.GPDMA1_CH1, config); + unwrap!(usart.blocking_write(b"Type 8 chars to echo!\r\n")); + + let (mut tx, rx) = usart.split(); + + unwrap!(spawner.spawn(reader(rx))); + + loop { + let buf = CHANNEL.recv().await; + info!("writing..."); + unwrap!(tx.write(&buf).await); + } +} + +#[embassy_executor::task] +async fn reader(mut rx: UartRx<'static, UART7, GPDMA1_CH1>) { + let mut buf = [0; 8]; + loop { + info!("reading..."); + unwrap!(rx.read(&mut buf).await); + CHANNEL.send(buf).await; + } +} diff --git a/examples/stm32h5/src/bin/usb_serial.rs b/examples/stm32h5/src/bin/usb_serial.rs new file mode 100644 index 000000000..336eed644 --- /dev/null +++ b/examples/stm32h5/src/bin/usb_serial.rs @@ -0,0 +1,129 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::{panic, *}; +use embassy_executor::Spawner; +use embassy_stm32::rcc::{AHBPrescaler, APBPrescaler, Hse, HseMode, Pll, PllSource, Sysclk, VoltageScale}; +use embassy_stm32::time::Hertz; +use embassy_stm32::usb::{Driver, Instance}; +use embassy_stm32::{bind_interrupts, pac, peripherals, usb, Config}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::Builder; +use futures::future::join; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USB_DRD_FS => usb::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + config.rcc.hsi = None; + config.rcc.hsi48 = true; // needed for usb + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::BypassDigital, + }); + config.rcc.pll1 = Some(Pll { + source: PllSource::Hse, + prediv: 2, + mul: 125, + divp: Some(2), // 250mhz + divq: None, + divr: None, + }); + config.rcc.ahb_pre = AHBPrescaler::Div2; + config.rcc.apb1_pre = APBPrescaler::Div4; + config.rcc.apb2_pre = APBPrescaler::Div2; + config.rcc.apb3_pre = APBPrescaler::Div4; + config.rcc.sys = Sysclk::Pll1P; + config.rcc.voltage_scale = VoltageScale::Scale0; + let p = embassy_stm32::init(config); + + info!("Hello World!"); + + pac::RCC.ccipr4().write(|w| { + w.set_usbsel(pac::rcc::vals::Usbsel::HSI48); + }); + + // Create the driver, from the HAL. + let driver = Driver::new(p.USB, Irqs, p.PA12, p.PA11); + + // Create embassy-usb Config + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial example"); + config.serial_number = Some("12345678"); + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut device_descriptor = [0; 256]; + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut device_descriptor, + &mut config_descriptor, + &mut bos_descriptor, + &mut control_buf, + ); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} diff --git a/examples/stm32h7/.cargo/config.toml b/examples/stm32h7/.cargo/config.toml index d38be23e0..5f680dbce 100644 --- a/examples/stm32h7/.cargo/config.toml +++ b/examples/stm32h7/.cargo/config.toml @@ -1,5 +1,5 @@ [target.thumbv7em-none-eabihf] -runner = 'probe-run --chip STM32H743ZITx' +runner = 'probe-rs run --chip STM32H743ZITx' [build] target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) diff --git a/examples/stm32h7/Cargo.toml b/examples/stm32h7/Cargo.toml index 0f76f3226..768702fa9 100644 --- a/examples/stm32h7/Cargo.toml +++ b/examples/stm32h7/Cargo.toml @@ -2,24 +2,26 @@ edition = "2021" name = "embassy-stm32h7-examples" version = "0.1.0" +license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } -embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "unstable-traits", "tick-32768hz"] } -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32h743bi", "net", "time-driver-any", "exti", "unstable-pac", "unstable-traits"] } -embassy-net = { path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "pool-16", "unstable-traits"] } -embedded-io = { version = "0.3.0", features = ["async"] } +embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "unstable-traits", "tick-hz-32_768"] } +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32h743bi", "time-driver-any", "exti", "unstable-pac", "unstable-traits"] } +embassy-net = { path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet", "unstable-traits", "proto-ipv6"] } +embedded-io = { version = "0.4.0", features = ["async"] } +embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } defmt = "0.3" -defmt-rtt = "0.3" +defmt-rtt = "0.4" -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" -embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.8" } -embedded-hal-async = { version = "0.1.0-alpha.1" } -embedded-nal-async = "0.2.0" +embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11" } +embedded-hal-async = { version = "=0.2.0-alpha.2" } +embedded-nal-async = "0.4.0" panic-probe = { version = "0.3", features = ["print-defmt"] } futures = { version = "0.3.17", default-features = false, features = ["async-await"] } heapless = { version = "0.7.5", default-features = false } @@ -28,7 +30,7 @@ critical-section = "1.1" micromath = "2.0.0" stm32-fmc = "0.2.4" embedded-storage = "0.3.0" -static_cell = "1.0" +static_cell = { version = "1.1", features = ["nightly"]} # cargo build/run [profile.dev] diff --git a/examples/stm32h7/src/bin/camera.rs b/examples/stm32h7/src/bin/camera.rs index 9c443b83a..6f75a0630 100644 --- a/examples/stm32h7/src/bin/camera.rs +++ b/examples/stm32h7/src/bin/camera.rs @@ -8,7 +8,7 @@ use embassy_stm32::gpio::{Level, Output, Speed}; use embassy_stm32::i2c::I2c; use embassy_stm32::rcc::{Mco, Mco1Source, McoClock}; use embassy_stm32::time::{khz, mhz}; -use embassy_stm32::{interrupt, Config}; +use embassy_stm32::{bind_interrupts, i2c, peripherals, Config}; use embassy_time::{Duration, Timer}; use ov7725::*; use {defmt_rtt as _, panic_probe as _}; @@ -18,6 +18,11 @@ const HEIGHT: usize = 100; static mut FRAME: [u32; WIDTH * HEIGHT / 2] = [0u32; WIDTH * HEIGHT / 2]; +bind_interrupts!(struct Irqs { + I2C1_EV => i2c::InterruptHandler; + DCMI => dcmi::InterruptHandler; +}); + #[embassy_executor::main] async fn main(_spawner: Spawner) { let mut config = Config::default(); @@ -34,12 +39,11 @@ async fn main(_spawner: Spawner) { let mco = Mco::new(p.MCO1, p.PA8, Mco1Source::Hsi, McoClock::Divided(3)); let mut led = Output::new(p.PE3, Level::High, Speed::Low); - let i2c_irq = interrupt::take!(I2C1_EV); let cam_i2c = I2c::new( p.I2C1, p.PB8, p.PB9, - i2c_irq, + Irqs, p.DMA1_CH1, p.DMA1_CH2, khz(100), @@ -55,11 +59,9 @@ async fn main(_spawner: Spawner) { defmt::info!("manufacturer: 0x{:x}, pid: 0x{:x}", manufacturer_id, camera_id); - let dcmi_irq = interrupt::take!(DCMI); let config = dcmi::Config::default(); let mut dcmi = Dcmi::new_8bit( - p.DCMI, p.DMA1_CH0, dcmi_irq, p.PC6, p.PC7, p.PE0, p.PE1, p.PE4, p.PD3, p.PE5, p.PE6, p.PB7, p.PA4, p.PA6, - config, + p.DCMI, p.DMA1_CH0, Irqs, p.PC6, p.PC7, p.PE0, p.PE1, p.PE4, p.PD3, p.PE5, p.PE6, p.PB7, p.PA4, p.PA6, config, ); defmt::info!("attempting capture"); diff --git a/examples/stm32h7/src/bin/dac.rs b/examples/stm32h7/src/bin/dac.rs index f12716370..586b4154b 100644 --- a/examples/stm32h7/src/bin/dac.rs +++ b/examples/stm32h7/src/bin/dac.rs @@ -4,7 +4,8 @@ use cortex_m_rt::entry; use defmt::*; -use embassy_stm32::dac::{Channel, Dac, Value}; +use embassy_stm32::dac::{DacCh1, DacChannel, Value}; +use embassy_stm32::dma::NoDma; use embassy_stm32::time::mhz; use embassy_stm32::Config; use {defmt_rtt as _, panic_probe as _}; @@ -19,12 +20,12 @@ fn main() -> ! { config.rcc.pll1.q_ck = Some(mhz(100)); let p = embassy_stm32::init(config); - let mut dac = Dac::new_1ch(p.DAC1, p.PA4); + let mut dac = DacCh1::new(p.DAC1, NoDma, p.PA4); loop { for v in 0..=255 { - unwrap!(dac.set(Channel::Ch1, Value::Bit8(to_sine_wave(v)))); - unwrap!(dac.trigger(Channel::Ch1)); + unwrap!(dac.set(Value::Bit8(to_sine_wave(v)))); + dac.trigger(); } } } diff --git a/examples/stm32h7/src/bin/eth.rs b/examples/stm32h7/src/bin/eth.rs index 4ccc0b5ef..cfafcaed1 100644 --- a/examples/stm32h7/src/bin/eth.rs +++ b/examples/stm32h7/src/bin/eth.rs @@ -7,26 +7,21 @@ use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; use embassy_net::{Ipv4Address, Stack, StackResources}; use embassy_stm32::eth::generic_smi::GenericSMI; -use embassy_stm32::eth::{Ethernet, State}; +use embassy_stm32::eth::{Ethernet, PacketQueue}; use embassy_stm32::peripherals::ETH; use embassy_stm32::rng::Rng; use embassy_stm32::time::mhz; -use embassy_stm32::{interrupt, Config}; +use embassy_stm32::{bind_interrupts, eth, Config}; use embassy_time::{Duration, Timer}; use embedded_io::asynch::Write; use rand_core::RngCore; -use static_cell::StaticCell; +use static_cell::make_static; use {defmt_rtt as _, panic_probe as _}; +bind_interrupts!(struct Irqs { + ETH => eth::InterruptHandler; +}); -macro_rules! singleton { - ($val:expr) => {{ - type T = impl Sized; - static STATIC_CELL: StaticCell = StaticCell::new(); - STATIC_CELL.init_with(move || $val) - }}; -} - -type Device = Ethernet<'static, ETH, GenericSMI, 4, 4>; +type Device = Ethernet<'static, ETH, GenericSMI>; #[embassy_executor::task] async fn net_task(stack: &'static Stack) -> ! { @@ -48,41 +43,38 @@ async fn main(spawner: Spawner) -> ! { rng.fill_bytes(&mut seed); let seed = u64::from_le_bytes(seed); - let eth_int = interrupt::take!(ETH); let mac_addr = [0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF]; - let device = unsafe { - Ethernet::new( - singleton!(State::new()), - p.ETH, - eth_int, - p.PA1, - p.PA2, - p.PC1, - p.PA7, - p.PC4, - p.PC5, - p.PG13, - p.PB13, - p.PG11, - GenericSMI, - mac_addr, - 0, - ) - }; + let device = Ethernet::new( + make_static!(PacketQueue::<16, 16>::new()), + p.ETH, + Irqs, + p.PA1, + p.PA2, + p.PC1, + p.PA7, + p.PC4, + p.PC5, + p.PG13, + p.PB13, + p.PG11, + GenericSMI::new(), + mac_addr, + 0, + ); - let config = embassy_net::ConfigStrategy::Dhcp; - //let config = embassy_net::ConfigStrategy::Static(embassy_net::Config { + let config = embassy_net::Config::dhcpv4(Default::default()); + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), // dns_servers: Vec::new(), // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), //}); // Init network stack - let stack = &*singleton!(Stack::new( + let stack = &*make_static!(Stack::new( device, config, - singleton!(StackResources::<1, 2, 8>::new()), + make_static!(StackResources::<2>::new()), seed )); @@ -98,7 +90,7 @@ async fn main(spawner: Spawner) -> ! { loop { let mut socket = TcpSocket::new(&stack, &mut rx_buffer, &mut tx_buffer); - socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10))); + socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); let remote_endpoint = (Ipv4Address::new(10, 42, 0, 1), 8000); info!("connecting..."); @@ -112,7 +104,7 @@ async fn main(spawner: Spawner) -> ! { let r = socket.write_all(b"Hello\n").await; if let Err(e) = r { info!("write error: {:?}", e); - return; + continue; } Timer::after(Duration::from_secs(1)).await; } diff --git a/examples/stm32h7/src/bin/eth_client.rs b/examples/stm32h7/src/bin/eth_client.rs index 64fd84141..4ed737578 100644 --- a/examples/stm32h7/src/bin/eth_client.rs +++ b/examples/stm32h7/src/bin/eth_client.rs @@ -7,27 +7,22 @@ use embassy_executor::Spawner; use embassy_net::tcp::client::{TcpClient, TcpClientState}; use embassy_net::{Stack, StackResources}; use embassy_stm32::eth::generic_smi::GenericSMI; -use embassy_stm32::eth::{Ethernet, State}; +use embassy_stm32::eth::{Ethernet, PacketQueue}; use embassy_stm32::peripherals::ETH; use embassy_stm32::rng::Rng; use embassy_stm32::time::mhz; -use embassy_stm32::{interrupt, Config}; +use embassy_stm32::{bind_interrupts, eth, Config}; use embassy_time::{Duration, Timer}; use embedded_io::asynch::Write; use embedded_nal_async::{Ipv4Addr, SocketAddr, SocketAddrV4, TcpConnect}; use rand_core::RngCore; -use static_cell::StaticCell; +use static_cell::make_static; use {defmt_rtt as _, panic_probe as _}; +bind_interrupts!(struct Irqs { + ETH => eth::InterruptHandler; +}); -macro_rules! singleton { - ($val:expr) => {{ - type T = impl Sized; - static STATIC_CELL: StaticCell = StaticCell::new(); - STATIC_CELL.init_with(move || $val) - }}; -} - -type Device = Ethernet<'static, ETH, GenericSMI, 4, 4>; +type Device = Ethernet<'static, ETH, GenericSMI>; #[embassy_executor::task] async fn net_task(stack: &'static Stack) -> ! { @@ -49,41 +44,38 @@ async fn main(spawner: Spawner) -> ! { rng.fill_bytes(&mut seed); let seed = u64::from_le_bytes(seed); - let eth_int = interrupt::take!(ETH); let mac_addr = [0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF]; - let device = unsafe { - Ethernet::new( - singleton!(State::new()), - p.ETH, - eth_int, - p.PA1, - p.PA2, - p.PC1, - p.PA7, - p.PC4, - p.PC5, - p.PG13, - p.PB13, - p.PG11, - GenericSMI, - mac_addr, - 0, - ) - }; + let device = Ethernet::new( + make_static!(PacketQueue::<16, 16>::new()), + p.ETH, + Irqs, + p.PA1, + p.PA2, + p.PC1, + p.PA7, + p.PC4, + p.PC5, + p.PG13, + p.PB13, + p.PG11, + GenericSMI::new(), + mac_addr, + 0, + ); - let config = embassy_net::ConfigStrategy::Dhcp; - //let config = embassy_net::ConfigStrategy::Static(embassy_net::Config { + let config = embassy_net::Config::dhcpv4(Default::default()); + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), // dns_servers: Vec::new(), // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), //}); // Init network stack - let stack = &*singleton!(Stack::new( + let stack = &*make_static!(Stack::new( device, config, - singleton!(StackResources::<1, 2, 8>::new()), + make_static!(StackResources::<2>::new()), seed )); @@ -114,7 +106,7 @@ async fn main(spawner: Spawner) -> ! { let r = connection.write_all(b"Hello\n").await; if let Err(e) = r { info!("write error: {:?}", e); - return; + continue; } Timer::after(Duration::from_secs(1)).await; } diff --git a/examples/stm32h7/src/bin/flash.rs b/examples/stm32h7/src/bin/flash.rs index 6682c64d5..f66df770b 100644 --- a/examples/stm32h7/src/bin/flash.rs +++ b/examples/stm32h7/src/bin/flash.rs @@ -6,7 +6,6 @@ use defmt::{info, unwrap}; use embassy_executor::Spawner; use embassy_stm32::flash::Flash; use embassy_time::{Duration, Timer}; -use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] @@ -14,28 +13,28 @@ async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); info!("Hello Flash!"); - const ADDR: u32 = 0x08_0000; + const ADDR: u32 = 0; // This is the offset into bank 2, the absolute address is 0x8_0000 // wait a bit before accessing the flash Timer::after(Duration::from_millis(300)).await; - let mut f = Flash::unlock(p.FLASH); + let mut f = Flash::new_blocking(p.FLASH).into_blocking_regions().bank2_region; info!("Reading..."); let mut buf = [0u8; 32]; - unwrap!(f.read(ADDR, &mut buf)); + unwrap!(f.blocking_read(ADDR, &mut buf)); info!("Read: {=[u8]:x}", buf); info!("Erasing..."); - unwrap!(f.erase(ADDR, ADDR + 128 * 1024)); + unwrap!(f.blocking_erase(ADDR, ADDR + 128 * 1024)); info!("Reading..."); let mut buf = [0u8; 32]; - unwrap!(f.read(ADDR, &mut buf)); + unwrap!(f.blocking_read(ADDR, &mut buf)); info!("Read after erase: {=[u8]:x}", buf); info!("Writing..."); - unwrap!(f.write( + unwrap!(f.blocking_write( ADDR, &[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, @@ -45,7 +44,7 @@ async fn main(_spawner: Spawner) { info!("Reading..."); let mut buf = [0u8; 32]; - unwrap!(f.read(ADDR, &mut buf)); + unwrap!(f.blocking_read(ADDR, &mut buf)); info!("Read: {=[u8]:x}", buf); assert_eq!( &buf[..], diff --git a/examples/stm32h7/src/bin/i2c.rs b/examples/stm32h7/src/bin/i2c.rs new file mode 100644 index 000000000..c2979c59b --- /dev/null +++ b/examples/stm32h7/src/bin/i2c.rs @@ -0,0 +1,47 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::i2c::{Error, I2c, TimeoutI2c}; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, i2c, peripherals}; +use embassy_time::Duration; +use {defmt_rtt as _, panic_probe as _}; + +const ADDRESS: u8 = 0x5F; +const WHOAMI: u8 = 0x0F; + +bind_interrupts!(struct Irqs { + I2C2_EV => i2c::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello world!"); + let p = embassy_stm32::init(Default::default()); + + let mut i2c = I2c::new( + p.I2C2, + p.PB10, + p.PB11, + Irqs, + p.DMA1_CH4, + p.DMA1_CH5, + Hertz(100_000), + Default::default(), + ); + + // I2C bus can freeze if SCL line is shorted or due to a broken device that clock stretches for too long. + // TimeoutI2c allows recovering from such errors by throwing `Error::Timeout` after a given delay. + let mut timeout_i2c = TimeoutI2c::new(&mut i2c, Duration::from_millis(1000)); + + let mut data = [0u8; 1]; + + match timeout_i2c.blocking_write_read(ADDRESS, &[WHOAMI], &mut data) { + Ok(()) => info!("Whoami: {}", data[0]), + Err(Error::Timeout) => error!("Operation timed out"), + Err(e) => error!("I2c Error: {:?}", e), + } +} diff --git a/examples/stm32h7/src/bin/low_level_timer_api.rs b/examples/stm32h7/src/bin/low_level_timer_api.rs index 1972f8ff2..d360df085 100644 --- a/examples/stm32h7/src/bin/low_level_timer_api.rs +++ b/examples/stm32h7/src/bin/low_level_timer_api.rs @@ -62,49 +62,39 @@ impl<'d, T: CaptureCompare32bitInstance> SimplePwm32<'d, T> { T::enable(); ::reset(); - unsafe { - ch1.set_speed(Speed::VeryHigh); - ch1.set_as_af(ch1.af_num(), AFType::OutputPushPull); - ch2.set_speed(Speed::VeryHigh); - ch2.set_as_af(ch1.af_num(), AFType::OutputPushPull); - ch3.set_speed(Speed::VeryHigh); - ch3.set_as_af(ch1.af_num(), AFType::OutputPushPull); - ch4.set_speed(Speed::VeryHigh); - ch4.set_as_af(ch1.af_num(), AFType::OutputPushPull); - } + ch1.set_speed(Speed::VeryHigh); + ch1.set_as_af(ch1.af_num(), AFType::OutputPushPull); + ch2.set_speed(Speed::VeryHigh); + ch2.set_as_af(ch1.af_num(), AFType::OutputPushPull); + ch3.set_speed(Speed::VeryHigh); + ch3.set_as_af(ch1.af_num(), AFType::OutputPushPull); + ch4.set_speed(Speed::VeryHigh); + ch4.set_as_af(ch1.af_num(), AFType::OutputPushPull); let mut this = Self { inner: tim }; this.set_freq(freq); this.inner.start(); - unsafe { - T::regs_gp32() - .ccmr_output(0) - .modify(|w| w.set_ocm(0, OutputCompareMode::PwmMode1.into())); - T::regs_gp32() - .ccmr_output(0) - .modify(|w| w.set_ocm(1, OutputCompareMode::PwmMode1.into())); - T::regs_gp32() - .ccmr_output(1) - .modify(|w| w.set_ocm(0, OutputCompareMode::PwmMode1.into())); - T::regs_gp32() - .ccmr_output(1) - .modify(|w| w.set_ocm(1, OutputCompareMode::PwmMode1.into())); - } + let r = T::regs_gp32(); + r.ccmr_output(0) + .modify(|w| w.set_ocm(0, OutputCompareMode::PwmMode1.into())); + r.ccmr_output(0) + .modify(|w| w.set_ocm(1, OutputCompareMode::PwmMode1.into())); + r.ccmr_output(1) + .modify(|w| w.set_ocm(0, OutputCompareMode::PwmMode1.into())); + r.ccmr_output(1) + .modify(|w| w.set_ocm(1, OutputCompareMode::PwmMode1.into())); + this } pub fn enable(&mut self, channel: Channel) { - unsafe { - T::regs_gp32().ccer().modify(|w| w.set_cce(channel.raw(), true)); - } + T::regs_gp32().ccer().modify(|w| w.set_cce(channel.raw(), true)); } pub fn disable(&mut self, channel: Channel) { - unsafe { - T::regs_gp32().ccer().modify(|w| w.set_cce(channel.raw(), false)); - } + T::regs_gp32().ccer().modify(|w| w.set_cce(channel.raw(), false)); } pub fn set_freq(&mut self, freq: Hertz) { @@ -112,11 +102,11 @@ impl<'d, T: CaptureCompare32bitInstance> SimplePwm32<'d, T> { } pub fn get_max_duty(&self) -> u32 { - unsafe { T::regs_gp32().arr().read().arr() } + T::regs_gp32().arr().read().arr() } pub fn set_duty(&mut self, channel: Channel, duty: u32) { defmt::assert!(duty < self.get_max_duty()); - unsafe { T::regs_gp32().ccr(channel.raw()).modify(|w| w.set_ccr(duty)) } + T::regs_gp32().ccr(channel.raw()).modify(|w| w.set_ccr(duty)) } } diff --git a/examples/stm32h7/src/bin/sdmmc.rs b/examples/stm32h7/src/bin/sdmmc.rs index 26d1db01e..ce91b6b1c 100644 --- a/examples/stm32h7/src/bin/sdmmc.rs +++ b/examples/stm32h7/src/bin/sdmmc.rs @@ -6,9 +6,13 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::sdmmc::Sdmmc; use embassy_stm32::time::mhz; -use embassy_stm32::{interrupt, Config}; +use embassy_stm32::{bind_interrupts, peripherals, sdmmc, Config}; use {defmt_rtt as _, panic_probe as _}; +bind_interrupts!(struct Irqs { + SDMMC1 => sdmmc::InterruptHandler; +}); + #[embassy_executor::main] async fn main(_spawner: Spawner) -> ! { let mut config = Config::default(); @@ -16,11 +20,9 @@ async fn main(_spawner: Spawner) -> ! { let p = embassy_stm32::init(config); info!("Hello World!"); - let irq = interrupt::take!(SDMMC1); - let mut sdmmc = Sdmmc::new_4bit( p.SDMMC1, - irq, + Irqs, p.PC12, p.PD2, p.PC8, diff --git a/examples/stm32h7/src/bin/signal.rs b/examples/stm32h7/src/bin/signal.rs index cc3e4e3ca..6d7c168d5 100644 --- a/examples/stm32h7/src/bin/signal.rs +++ b/examples/stm32h7/src/bin/signal.rs @@ -4,11 +4,12 @@ use defmt::{info, unwrap}; use embassy_executor::Spawner; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::signal::Signal; use embassy_time::{Duration, Timer}; use {defmt_rtt as _, panic_probe as _}; -static SIGNAL: Signal = Signal::new(); +static SIGNAL: Signal = Signal::new(); #[embassy_executor::task] async fn my_sending_task() { diff --git a/examples/stm32h7/src/bin/usart.rs b/examples/stm32h7/src/bin/usart.rs index 87c2b1253..0abb94abb 100644 --- a/examples/stm32h7/src/bin/usart.rs +++ b/examples/stm32h7/src/bin/usart.rs @@ -7,15 +7,20 @@ use defmt::*; use embassy_executor::Executor; use embassy_stm32::dma::NoDma; use embassy_stm32::usart::{Config, Uart}; +use embassy_stm32::{bind_interrupts, peripherals, usart}; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; +bind_interrupts!(struct Irqs { + UART7 => usart::InterruptHandler; +}); + #[embassy_executor::task] async fn main_task() { let p = embassy_stm32::init(Default::default()); let config = Config::default(); - let mut usart = Uart::new(p.UART7, p.PF6, p.PF7, NoDma, NoDma, config); + let mut usart = Uart::new(p.UART7, p.PF6, p.PF7, Irqs, NoDma, NoDma, config); unwrap!(usart.blocking_write(b"Hello Embassy World!\r\n")); info!("wrote Hello, starting echo"); diff --git a/examples/stm32h7/src/bin/usart_dma.rs b/examples/stm32h7/src/bin/usart_dma.rs index 3adffcbeb..f1fe7fce6 100644 --- a/examples/stm32h7/src/bin/usart_dma.rs +++ b/examples/stm32h7/src/bin/usart_dma.rs @@ -9,16 +9,21 @@ use defmt::*; use embassy_executor::Executor; use embassy_stm32::dma::NoDma; use embassy_stm32::usart::{Config, Uart}; +use embassy_stm32::{bind_interrupts, peripherals, usart}; use heapless::String; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; +bind_interrupts!(struct Irqs { + UART7 => usart::InterruptHandler; +}); + #[embassy_executor::task] async fn main_task() { let p = embassy_stm32::init(Default::default()); let config = Config::default(); - let mut usart = Uart::new(p.UART7, p.PF6, p.PF7, p.DMA1_CH0, NoDma, config); + let mut usart = Uart::new(p.UART7, p.PF6, p.PF7, Irqs, p.DMA1_CH0, NoDma, config); for n in 0u32.. { let mut s: String<128> = String::new(); diff --git a/examples/stm32h7/src/bin/usart_split.rs b/examples/stm32h7/src/bin/usart_split.rs index df2b600f8..330d1ce09 100644 --- a/examples/stm32h7/src/bin/usart_split.rs +++ b/examples/stm32h7/src/bin/usart_split.rs @@ -7,10 +7,15 @@ use embassy_executor::Spawner; use embassy_stm32::dma::NoDma; use embassy_stm32::peripherals::{DMA1_CH1, UART7}; use embassy_stm32::usart::{Config, Uart, UartRx}; +use embassy_stm32::{bind_interrupts, peripherals, usart}; use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; use embassy_sync::channel::Channel; use {defmt_rtt as _, panic_probe as _}; +bind_interrupts!(struct Irqs { + UART7 => usart::InterruptHandler; +}); + #[embassy_executor::task] async fn writer(mut usart: Uart<'static, UART7, NoDma, NoDma>) { unwrap!(usart.blocking_write(b"Hello Embassy World!\r\n")); @@ -31,7 +36,7 @@ async fn main(spawner: Spawner) -> ! { info!("Hello World!"); let config = Config::default(); - let mut usart = Uart::new(p.UART7, p.PF6, p.PF7, p.DMA1_CH0, p.DMA1_CH1, config); + let mut usart = Uart::new(p.UART7, p.PF6, p.PF7, Irqs, p.DMA1_CH0, p.DMA1_CH1, config); unwrap!(usart.blocking_write(b"Type 8 chars to echo!\r\n")); let (mut tx, rx) = usart.split(); diff --git a/examples/stm32h7/src/bin/usb_serial.rs b/examples/stm32h7/src/bin/usb_serial.rs new file mode 100644 index 000000000..97291f60c --- /dev/null +++ b/examples/stm32h7/src/bin/usb_serial.rs @@ -0,0 +1,110 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::{panic, *}; +use embassy_executor::Spawner; +use embassy_stm32::time::mhz; +use embassy_stm32::usb_otg::{Driver, Instance}; +use embassy_stm32::{bind_interrupts, peripherals, usb_otg, Config}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::Builder; +use futures::future::join; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + OTG_FS => usb_otg::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + let mut config = Config::default(); + config.rcc.sys_ck = Some(mhz(400)); + config.rcc.hclk = Some(mhz(200)); + config.rcc.pll1.q_ck = Some(mhz(100)); + let p = embassy_stm32::init(config); + + // Create the driver, from the HAL. + let mut ep_out_buffer = [0u8; 256]; + let mut config = embassy_stm32::usb_otg::Config::default(); + config.vbus_detection = true; + let driver = Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config); + + // Create embassy-usb Config + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial example"); + config.serial_number = Some("12345678"); + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut device_descriptor = [0; 256]; + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut device_descriptor, + &mut config_descriptor, + &mut bos_descriptor, + &mut control_buf, + ); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} diff --git a/examples/stm32h7/src/bin/wdg.rs b/examples/stm32h7/src/bin/wdg.rs new file mode 100644 index 000000000..9181dfd67 --- /dev/null +++ b/examples/stm32h7/src/bin/wdg.rs @@ -0,0 +1,24 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::wdg::IndependentWatchdog; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let mut wdg = IndependentWatchdog::new(p.IWDG1, 20_000_000); + + wdg.unleash(); + + loop { + Timer::after(Duration::from_secs(1)).await; + wdg.pet(); + } +} diff --git a/examples/stm32l0/.cargo/config.toml b/examples/stm32l0/.cargo/config.toml index a81a48f97..b050334b2 100644 --- a/examples/stm32l0/.cargo/config.toml +++ b/examples/stm32l0/.cargo/config.toml @@ -1,6 +1,6 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -# replace your chip as listed in `probe-run --list-chips` -runner = "probe-run --chip STM32L053R8Tx" +# replace your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32L053R8Tx" [build] target = "thumbv6m-none-eabi" diff --git a/examples/stm32l0/Cargo.toml b/examples/stm32l0/Cargo.toml index 11751a21d..747cec7bf 100644 --- a/examples/stm32l0/Cargo.toml +++ b/examples/stm32l0/Cargo.toml @@ -2,31 +2,36 @@ edition = "2021" name = "embassy-stm32l0-examples" version = "0.1.0" +license = "MIT OR Apache-2.0" [features] default = ["nightly"] -nightly = ["embassy-stm32/nightly", "embassy-lora", "lorawan-device", "lorawan", "embedded-io/async"] +nightly = ["embassy-stm32/nightly", "embassy-time/nightly", "embassy-time/unstable-traits", "embassy-executor/nightly", + "embassy-lora", "lora-phy", "lorawan-device", "lorawan", "embedded-io/async"] [dependencies] -embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } -embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-32768hz"] } +embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32l072cz", "time-driver-any", "exti", "unstable-traits", "memory-x"] } -embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["sx127x", "time", "defmt"], optional = true} - -lorawan-device = { version = "0.7.1", default-features = false, features = ["async"], optional = true } -lorawan = { version = "0.7.1", default-features = false, features = ["default-crypto"], optional = true } +embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["time", "defmt"], optional = true } +lora-phy = { version = "1", optional = true } +lorawan-device = { version = "0.10.0", default-features = false, features = ["async", "external-lora-phy"], optional = true } +lorawan = { version = "0.7.3", default-features = false, features = ["default-crypto"], optional = true } defmt = "0.3" -defmt-rtt = "0.3" +defmt-rtt = "0.4" embedded-storage = "0.3.0" -embedded-io = "0.3.0" +embedded-io = "0.4.0" -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" panic-probe = { version = "0.3", features = ["print-defmt"] } futures = { version = "0.3.17", default-features = false, features = ["async-await"] } heapless = { version = "0.7.5", default-features = false } embedded-hal = "0.2.6" -static_cell = "1.0" +static_cell = "1.1" + +[patch.crates-io] +lora-phy = { git = "https://github.com/embassy-rs/lora-phy", rev = "ad289428fd44b02788e2fa2116445cc8f640a265" } diff --git a/examples/stm32l0/src/bin/flash.rs b/examples/stm32l0/src/bin/flash.rs index 867cb4d3e..86f6c70b9 100644 --- a/examples/stm32l0/src/bin/flash.rs +++ b/examples/stm32l0/src/bin/flash.rs @@ -5,7 +5,6 @@ use defmt::{info, unwrap}; use embassy_executor::Spawner; use embassy_stm32::flash::Flash; -use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] @@ -15,27 +14,27 @@ async fn main(_spawner: Spawner) { const ADDR: u32 = 0x26000; - let mut f = Flash::unlock(p.FLASH); + let mut f = Flash::new_blocking(p.FLASH).into_blocking_regions().bank1_region; info!("Reading..."); let mut buf = [0u8; 8]; - unwrap!(f.read(ADDR, &mut buf)); + unwrap!(f.blocking_read(ADDR, &mut buf)); info!("Read: {=[u8]:x}", buf); info!("Erasing..."); - unwrap!(f.erase(ADDR, ADDR + 128)); + unwrap!(f.blocking_erase(ADDR, ADDR + 128)); info!("Reading..."); let mut buf = [0u8; 8]; - unwrap!(f.read(ADDR, &mut buf)); + unwrap!(f.blocking_read(ADDR, &mut buf)); info!("Read after erase: {=[u8]:x}", buf); info!("Writing..."); - unwrap!(f.write(ADDR, &[1, 2, 3, 4, 5, 6, 7, 8])); + unwrap!(f.blocking_write(ADDR, &[1, 2, 3, 4, 5, 6, 7, 8])); info!("Reading..."); let mut buf = [0u8; 8]; - unwrap!(f.read(ADDR, &mut buf)); + unwrap!(f.blocking_read(ADDR, &mut buf)); info!("Read: {=[u8]:x}", buf); assert_eq!(&buf[..], &[1, 2, 3, 4, 5, 6, 7, 8]); } diff --git a/examples/stm32l0/src/bin/lora_cad.rs b/examples/stm32l0/src/bin/lora_cad.rs new file mode 100644 index 000000000..588cea1e5 --- /dev/null +++ b/examples/stm32l0/src/bin/lora_cad.rs @@ -0,0 +1,105 @@ +//! This example runs on the STM32 LoRa Discovery board, which has a builtin Semtech Sx1276 radio. +//! It demonstrates LORA P2P CAD functionality. +#![no_std] +#![no_main] +#![macro_use] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_lora::iv::Stm32l0InterfaceVariant; +use embassy_stm32::exti::{Channel, ExtiInput}; +use embassy_stm32::gpio::{Input, Level, Output, Pin, Pull, Speed}; +use embassy_stm32::spi; +use embassy_stm32::time::khz; +use embassy_time::{Delay, Duration, Timer}; +use lora_phy::mod_params::*; +use lora_phy::sx1276_7_8_9::SX1276_7_8_9; +use lora_phy::LoRa; +use {defmt_rtt as _, panic_probe as _}; + +const LORA_FREQUENCY_IN_HZ: u32 = 903_900_000; // warning: set this appropriately for the region + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = embassy_stm32::Config::default(); + config.rcc.mux = embassy_stm32::rcc::ClockSrc::HSI16; + config.rcc.enable_hsi48 = true; + let p = embassy_stm32::init(config); + + // SPI for sx1276 + let spi = spi::Spi::new( + p.SPI1, + p.PB3, + p.PA7, + p.PA6, + p.DMA1_CH3, + p.DMA1_CH2, + khz(200), + spi::Config::default(), + ); + + let nss = Output::new(p.PA15.degrade(), Level::High, Speed::Low); + let reset = Output::new(p.PC0.degrade(), Level::High, Speed::Low); + + let irq_pin = Input::new(p.PB4.degrade(), Pull::Up); + let irq = ExtiInput::new(irq_pin, p.EXTI4.degrade()); + + let iv = Stm32l0InterfaceVariant::new(nss, reset, irq, None, None).unwrap(); + + let mut delay = Delay; + + let mut lora = { + match LoRa::new(SX1276_7_8_9::new(BoardType::Stm32l0Sx1276, spi, iv), false, &mut delay).await { + Ok(l) => l, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + let mut debug_indicator = Output::new(p.PB5, Level::Low, Speed::Low); + let mut start_indicator = Output::new(p.PB6, Level::Low, Speed::Low); + + start_indicator.set_high(); + Timer::after(Duration::from_secs(5)).await; + start_indicator.set_low(); + + let mdltn_params = { + match lora.create_modulation_params( + SpreadingFactor::_10, + Bandwidth::_250KHz, + CodingRate::_4_8, + LORA_FREQUENCY_IN_HZ, + ) { + Ok(mp) => mp, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + match lora.prepare_for_cad(&mdltn_params, true).await { + Ok(()) => {} + Err(err) => { + info!("Radio error = {}", err); + return; + } + }; + + match lora.cad().await { + Ok(cad_activity_detected) => { + if cad_activity_detected { + info!("cad successful with activity detected") + } else { + info!("cad successful without activity detected") + } + debug_indicator.set_high(); + Timer::after(Duration::from_secs(5)).await; + debug_indicator.set_low(); + } + Err(err) => info!("cad unsuccessful = {}", err), + } +} diff --git a/examples/stm32l0/src/bin/lora_lorawan.rs b/examples/stm32l0/src/bin/lora_lorawan.rs new file mode 100644 index 000000000..c397edd58 --- /dev/null +++ b/examples/stm32l0/src/bin/lora_lorawan.rs @@ -0,0 +1,88 @@ +//! This example runs on the STM32 LoRa Discovery board, which has a builtin Semtech Sx1276 radio. +//! It demonstrates LoRaWAN join functionality. +#![no_std] +#![no_main] +#![macro_use] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_lora::iv::Stm32l0InterfaceVariant; +use embassy_lora::LoraTimer; +use embassy_stm32::exti::{Channel, ExtiInput}; +use embassy_stm32::gpio::{Input, Level, Output, Pin, Pull, Speed}; +use embassy_stm32::rng::Rng; +use embassy_stm32::spi; +use embassy_stm32::time::khz; +use embassy_time::Delay; +use lora_phy::mod_params::*; +use lora_phy::sx1276_7_8_9::SX1276_7_8_9; +use lora_phy::LoRa; +use lorawan::default_crypto::DefaultFactory as Crypto; +use lorawan_device::async_device::lora_radio::LoRaRadio; +use lorawan_device::async_device::{region, Device, JoinMode}; +use {defmt_rtt as _, panic_probe as _}; + +const LORAWAN_REGION: region::Region = region::Region::EU868; // warning: set this appropriately for the region + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = embassy_stm32::Config::default(); + config.rcc.mux = embassy_stm32::rcc::ClockSrc::HSI16; + config.rcc.enable_hsi48 = true; + let p = embassy_stm32::init(config); + + // SPI for sx1276 + let spi = spi::Spi::new( + p.SPI1, + p.PB3, + p.PA7, + p.PA6, + p.DMA1_CH3, + p.DMA1_CH2, + khz(200), + spi::Config::default(), + ); + + let nss = Output::new(p.PA15.degrade(), Level::High, Speed::Low); + let reset = Output::new(p.PC0.degrade(), Level::High, Speed::Low); + + let irq_pin = Input::new(p.PB4.degrade(), Pull::Up); + let irq = ExtiInput::new(irq_pin, p.EXTI4.degrade()); + + let iv = Stm32l0InterfaceVariant::new(nss, reset, irq, None, None).unwrap(); + + let mut delay = Delay; + + let lora = { + match LoRa::new(SX1276_7_8_9::new(BoardType::Stm32l0Sx1276, spi, iv), true, &mut delay).await { + Ok(l) => l, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + let radio = LoRaRadio::new(lora); + let region: region::Configuration = region::Configuration::new(LORAWAN_REGION); + let mut device: Device<_, Crypto, _, _> = Device::new(region, radio, LoraTimer::new(), Rng::new(p.RNG)); + + defmt::info!("Joining LoRaWAN network"); + + // TODO: Adjust the EUI and Keys according to your network credentials + match device + .join(&JoinMode::OTAA { + deveui: [0, 0, 0, 0, 0, 0, 0, 0], + appeui: [0, 0, 0, 0, 0, 0, 0, 0], + appkey: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + }) + .await + { + Ok(()) => defmt::info!("LoRaWAN network joined"), + Err(err) => { + info!("Radio error = {}", err); + return; + } + }; +} diff --git a/examples/stm32l0/src/bin/lora_p2p_receive.rs b/examples/stm32l0/src/bin/lora_p2p_receive.rs new file mode 100644 index 000000000..bb7509509 --- /dev/null +++ b/examples/stm32l0/src/bin/lora_p2p_receive.rs @@ -0,0 +1,127 @@ +//! This example runs on the STM32 LoRa Discovery board, which has a builtin Semtech Sx1276 radio. +//! It demonstrates LORA P2P receive functionality in conjunction with the lora_p2p_send example. +#![no_std] +#![no_main] +#![macro_use] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_lora::iv::Stm32l0InterfaceVariant; +use embassy_stm32::exti::{Channel, ExtiInput}; +use embassy_stm32::gpio::{Input, Level, Output, Pin, Pull, Speed}; +use embassy_stm32::spi; +use embassy_stm32::time::khz; +use embassy_time::{Delay, Duration, Timer}; +use lora_phy::mod_params::*; +use lora_phy::sx1276_7_8_9::SX1276_7_8_9; +use lora_phy::LoRa; +use {defmt_rtt as _, panic_probe as _}; + +const LORA_FREQUENCY_IN_HZ: u32 = 903_900_000; // warning: set this appropriately for the region + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = embassy_stm32::Config::default(); + config.rcc.mux = embassy_stm32::rcc::ClockSrc::HSI16; + config.rcc.enable_hsi48 = true; + let p = embassy_stm32::init(config); + + // SPI for sx1276 + let spi = spi::Spi::new( + p.SPI1, + p.PB3, + p.PA7, + p.PA6, + p.DMA1_CH3, + p.DMA1_CH2, + khz(200), + spi::Config::default(), + ); + + let nss = Output::new(p.PA15.degrade(), Level::High, Speed::Low); + let reset = Output::new(p.PC0.degrade(), Level::High, Speed::Low); + + let irq_pin = Input::new(p.PB4.degrade(), Pull::Up); + let irq = ExtiInput::new(irq_pin, p.EXTI4.degrade()); + + let iv = Stm32l0InterfaceVariant::new(nss, reset, irq, None, None).unwrap(); + + let mut delay = Delay; + + let mut lora = { + match LoRa::new(SX1276_7_8_9::new(BoardType::Stm32l0Sx1276, spi, iv), false, &mut delay).await { + Ok(l) => l, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + let mut debug_indicator = Output::new(p.PB5, Level::Low, Speed::Low); + let mut start_indicator = Output::new(p.PB6, Level::Low, Speed::Low); + + start_indicator.set_high(); + Timer::after(Duration::from_secs(5)).await; + start_indicator.set_low(); + + let mut receiving_buffer = [00u8; 100]; + + let mdltn_params = { + match lora.create_modulation_params( + SpreadingFactor::_10, + Bandwidth::_250KHz, + CodingRate::_4_8, + LORA_FREQUENCY_IN_HZ, + ) { + Ok(mp) => mp, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + let rx_pkt_params = { + match lora.create_rx_packet_params(4, false, receiving_buffer.len() as u8, true, false, &mdltn_params) { + Ok(pp) => pp, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + match lora + .prepare_for_rx(&mdltn_params, &rx_pkt_params, None, true, false, 0, 0x00ffffffu32) + .await + { + Ok(()) => {} + Err(err) => { + info!("Radio error = {}", err); + return; + } + }; + + loop { + receiving_buffer = [00u8; 100]; + match lora.rx(&rx_pkt_params, &mut receiving_buffer).await { + Ok((received_len, _rx_pkt_status)) => { + if (received_len == 3) + && (receiving_buffer[0] == 0x01u8) + && (receiving_buffer[1] == 0x02u8) + && (receiving_buffer[2] == 0x03u8) + { + info!("rx successful"); + debug_indicator.set_high(); + Timer::after(Duration::from_secs(5)).await; + debug_indicator.set_low(); + } else { + info!("rx unknown packet"); + } + } + Err(err) => info!("rx unsuccessful = {}", err), + } + } +} diff --git a/examples/stm32l0/src/bin/lora_p2p_send.rs b/examples/stm32l0/src/bin/lora_p2p_send.rs new file mode 100644 index 000000000..e6fadc01d --- /dev/null +++ b/examples/stm32l0/src/bin/lora_p2p_send.rs @@ -0,0 +1,110 @@ +//! This example runs on the STM32 LoRa Discovery board, which has a builtin Semtech Sx1276 radio. +//! It demonstrates LORA P2P send functionality. +#![no_std] +#![no_main] +#![macro_use] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_lora::iv::Stm32l0InterfaceVariant; +use embassy_stm32::exti::{Channel, ExtiInput}; +use embassy_stm32::gpio::{Input, Level, Output, Pin, Pull, Speed}; +use embassy_stm32::spi; +use embassy_stm32::time::khz; +use embassy_time::Delay; +use lora_phy::mod_params::*; +use lora_phy::sx1276_7_8_9::SX1276_7_8_9; +use lora_phy::LoRa; +use {defmt_rtt as _, panic_probe as _}; + +const LORA_FREQUENCY_IN_HZ: u32 = 903_900_000; // warning: set this appropriately for the region + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = embassy_stm32::Config::default(); + config.rcc.mux = embassy_stm32::rcc::ClockSrc::HSI16; + config.rcc.enable_hsi48 = true; + let p = embassy_stm32::init(config); + + // SPI for sx1276 + let spi = spi::Spi::new( + p.SPI1, + p.PB3, + p.PA7, + p.PA6, + p.DMA1_CH3, + p.DMA1_CH2, + khz(200), + spi::Config::default(), + ); + + let nss = Output::new(p.PA15.degrade(), Level::High, Speed::Low); + let reset = Output::new(p.PC0.degrade(), Level::High, Speed::Low); + + let irq_pin = Input::new(p.PB4.degrade(), Pull::Up); + let irq = ExtiInput::new(irq_pin, p.EXTI4.degrade()); + + let iv = Stm32l0InterfaceVariant::new(nss, reset, irq, None, None).unwrap(); + + let mut delay = Delay; + + let mut lora = { + match LoRa::new(SX1276_7_8_9::new(BoardType::Stm32l0Sx1276, spi, iv), false, &mut delay).await { + Ok(l) => l, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + let mdltn_params = { + match lora.create_modulation_params( + SpreadingFactor::_10, + Bandwidth::_250KHz, + CodingRate::_4_8, + LORA_FREQUENCY_IN_HZ, + ) { + Ok(mp) => mp, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + let mut tx_pkt_params = { + match lora.create_tx_packet_params(4, false, true, false, &mdltn_params) { + Ok(pp) => pp, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + match lora.prepare_for_tx(&mdltn_params, 17, true).await { + Ok(()) => {} + Err(err) => { + info!("Radio error = {}", err); + return; + } + }; + + let buffer = [0x01u8, 0x02u8, 0x03u8]; + match lora.tx(&mdltn_params, &mut tx_pkt_params, &buffer, 0xffffff).await { + Ok(()) => { + info!("TX DONE"); + } + Err(err) => { + info!("Radio error = {}", err); + return; + } + }; + + match lora.sleep(&mut delay).await { + Ok(()) => info!("Sleep successful"), + Err(err) => info!("Sleep unsuccessful = {}", err), + } +} diff --git a/examples/stm32l0/src/bin/lorawan.rs b/examples/stm32l0/src/bin/lorawan.rs deleted file mode 100644 index 303558b96..000000000 --- a/examples/stm32l0/src/bin/lorawan.rs +++ /dev/null @@ -1,75 +0,0 @@ -//! This example runs on the STM32 LoRa Discovery board which has a builtin Semtech Sx127x radio -#![no_std] -#![no_main] -#![macro_use] -#![allow(dead_code)] -#![feature(generic_associated_types)] -#![feature(type_alias_impl_trait)] - -use embassy_executor::Spawner; -use embassy_lora::sx127x::*; -use embassy_lora::LoraTimer; -use embassy_stm32::exti::ExtiInput; -use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; -use embassy_stm32::rng::Rng; -use embassy_stm32::spi; -use embassy_stm32::time::khz; -use lorawan::default_crypto::DefaultFactory as Crypto; -use lorawan_device::async_device::{region, Device, JoinMode}; -use {defmt_rtt as _, panic_probe as _}; - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - let mut config = embassy_stm32::Config::default(); - config.rcc.mux = embassy_stm32::rcc::ClockSrc::HSI16; - config.rcc.enable_hsi48 = true; - let p = embassy_stm32::init(config); - - // SPI for sx127x - let spi = spi::Spi::new( - p.SPI1, - p.PB3, - p.PA7, - p.PA6, - p.DMA1_CH3, - p.DMA1_CH2, - khz(200), - spi::Config::default(), - ); - - let cs = Output::new(p.PA15, Level::High, Speed::Low); - let reset = Output::new(p.PC0, Level::High, Speed::Low); - let _ = Input::new(p.PB1, Pull::None); - - let ready = Input::new(p.PB4, Pull::Up); - let ready_pin = ExtiInput::new(ready, p.EXTI4); - - let radio = Sx127xRadio::new(spi, cs, reset, ready_pin, DummySwitch).await.unwrap(); - - let region = region::EU868::default().into(); - let mut device: Device<_, Crypto, _, _> = Device::new(region, radio, LoraTimer, Rng::new(p.RNG)); - - defmt::info!("Joining LoRaWAN network"); - - // TODO: Adjust the EUI and Keys according to your network credentials - device - .join(&JoinMode::OTAA { - deveui: [0, 0, 0, 0, 0, 0, 0, 0], - appeui: [0, 0, 0, 0, 0, 0, 0, 0], - appkey: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - }) - .await - .ok() - .unwrap(); - defmt::info!("LoRaWAN network joined"); - - defmt::info!("Sending 'PING'"); - device.send(b"PING", 1, false).await.ok().unwrap(); - defmt::info!("Message sent!"); -} - -pub struct DummySwitch; -impl RadioSwitch for DummySwitch { - fn set_rx(&mut self) {} - fn set_tx(&mut self) {} -} diff --git a/examples/stm32l0/src/bin/usart_dma.rs b/examples/stm32l0/src/bin/usart_dma.rs index 66657d0f0..eae8f3452 100644 --- a/examples/stm32l0/src/bin/usart_dma.rs +++ b/examples/stm32l0/src/bin/usart_dma.rs @@ -5,12 +5,17 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::usart::{Config, Uart}; +use embassy_stm32::{bind_interrupts, peripherals, usart}; use {defmt_rtt as _, panic_probe as _}; +bind_interrupts!(struct Irqs { + USART1 => usart::InterruptHandler; +}); + #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); - let mut usart = Uart::new(p.USART1, p.PB7, p.PB6, p.DMA1_CH2, p.DMA1_CH3, Config::default()); + let mut usart = Uart::new(p.USART1, p.PB7, p.PB6, Irqs, p.DMA1_CH2, p.DMA1_CH3, Config::default()); usart.write(b"Hello Embassy World!\r\n").await.unwrap(); info!("wrote Hello, starting echo"); diff --git a/examples/stm32l0/src/bin/usart_irq.rs b/examples/stm32l0/src/bin/usart_irq.rs index 0e2237388..f2c72a107 100644 --- a/examples/stm32l0/src/bin/usart_irq.rs +++ b/examples/stm32l0/src/bin/usart_irq.rs @@ -4,12 +4,15 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_stm32::dma::NoDma; -use embassy_stm32::interrupt; -use embassy_stm32::usart::{BufferedUart, Config, State, Uart}; +use embassy_stm32::usart::{BufferedUart, Config}; +use embassy_stm32::{bind_interrupts, peripherals, usart}; use embedded_io::asynch::{Read, Write}; use {defmt_rtt as _, panic_probe as _}; +bind_interrupts!(struct Irqs { + USART2 => usart::BufferedInterruptHandler; +}); + #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); @@ -21,17 +24,7 @@ async fn main(_spawner: Spawner) { let mut config = Config::default(); config.baudrate = 9600; - let usart = Uart::new(p.USART2, p.PA3, p.PA2, NoDma, NoDma, config); - let mut state = State::new(); - let mut usart = unsafe { - BufferedUart::new( - &mut state, - usart, - interrupt::take!(USART2), - &mut TX_BUFFER, - &mut RX_BUFFER, - ) - }; + let mut usart = unsafe { BufferedUart::new(p.USART2, Irqs, p.PA3, p.PA2, &mut TX_BUFFER, &mut RX_BUFFER, config) }; usart.write_all(b"Hello Embassy World!\r\n").await.unwrap(); info!("wrote Hello, starting echo"); diff --git a/examples/stm32l1/.cargo/config.toml b/examples/stm32l1/.cargo/config.toml index 404b6b55c..9cabd14ba 100644 --- a/examples/stm32l1/.cargo/config.toml +++ b/examples/stm32l1/.cargo/config.toml @@ -1,6 +1,6 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -# replace your chip as listed in `probe-run --list-chips` -runner = "probe-run --chip STM32L151CBxxA" +# replace your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32L151CBxxA" [build] target = "thumbv7m-none-eabi" diff --git a/examples/stm32l1/Cargo.toml b/examples/stm32l1/Cargo.toml index 18b35b305..dcca1cc3d 100644 --- a/examples/stm32l1/Cargo.toml +++ b/examples/stm32l1/Cargo.toml @@ -2,17 +2,18 @@ edition = "2021" name = "embassy-stm32l1-examples" version = "0.1.0" +license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } -embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-32768hz"] } +embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32l151cb-a", "time-driver-any", "memory-x"] } defmt = "0.3" -defmt-rtt = "0.3" +defmt-rtt = "0.4" -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" panic-probe = { version = "0.3", features = ["print-defmt"] } diff --git a/examples/stm32l1/src/bin/flash.rs b/examples/stm32l1/src/bin/flash.rs index a76b9879f..aeb535cca 100644 --- a/examples/stm32l1/src/bin/flash.rs +++ b/examples/stm32l1/src/bin/flash.rs @@ -5,7 +5,6 @@ use defmt::{info, unwrap}; use embassy_executor::Spawner; use embassy_stm32::flash::Flash; -use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] @@ -15,27 +14,27 @@ async fn main(_spawner: Spawner) { const ADDR: u32 = 0x26000; - let mut f = Flash::unlock(p.FLASH); + let mut f = Flash::new_blocking(p.FLASH).into_blocking_regions().bank1_region; info!("Reading..."); let mut buf = [0u8; 8]; - unwrap!(f.read(ADDR, &mut buf)); + unwrap!(f.blocking_read(ADDR, &mut buf)); info!("Read: {=[u8]:x}", buf); info!("Erasing..."); - unwrap!(f.erase(ADDR, ADDR + 256)); + unwrap!(f.blocking_erase(ADDR, ADDR + 256)); info!("Reading..."); let mut buf = [0u8; 8]; - unwrap!(f.read(ADDR, &mut buf)); + unwrap!(f.blocking_read(ADDR, &mut buf)); info!("Read after erase: {=[u8]:x}", buf); info!("Writing..."); - unwrap!(f.write(ADDR, &[1, 2, 3, 4, 5, 6, 7, 8])); + unwrap!(f.blocking_write(ADDR, &[1, 2, 3, 4, 5, 6, 7, 8])); info!("Reading..."); let mut buf = [0u8; 8]; - unwrap!(f.read(ADDR, &mut buf)); + unwrap!(f.blocking_read(ADDR, &mut buf)); info!("Read: {=[u8]:x}", buf); assert_eq!(&buf[..], &[1, 2, 3, 4, 5, 6, 7, 8]); } diff --git a/examples/stm32l4/.cargo/config.toml b/examples/stm32l4/.cargo/config.toml index 5534053c5..36e74e5a5 100644 --- a/examples/stm32l4/.cargo/config.toml +++ b/examples/stm32l4/.cargo/config.toml @@ -1,8 +1,8 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -# replace STM32F429ZITx with your chip as listed in `probe-run --list-chips` -#runner = "probe-run --chip STM32L475VGT6" -#runner = "probe-run --chip STM32L475VG" -runner = "probe-run --chip STM32L4S5VI" +# replace STM32F429ZITx with your chip as listed in `probe-rs chip list` +#runner = "probe-rs run --chip STM32L475VGT6" +#runner = "probe-rs run --chip STM32L475VG" +runner = "probe-rs run --chip STM32L4S5VI" [build] target = "thumbv7em-none-eabi" diff --git a/examples/stm32l4/Cargo.toml b/examples/stm32l4/Cargo.toml index cb7238e4c..c55558518 100644 --- a/examples/stm32l4/Cargo.toml +++ b/examples/stm32l4/Cargo.toml @@ -2,29 +2,27 @@ edition = "2021" name = "embassy-stm32l4-examples" version = "0.1.0" - -[features] +license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } -embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-32768hz"] } +embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" } -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "stm32l4s5vi", "time-driver-any", "exti", "unstable-traits"] } +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "stm32l4s5vi", "time-driver-any", "exti", "unstable-traits", "chrono"] } +embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } defmt = "0.3" -defmt-rtt = "0.3" +defmt-rtt = "0.4" cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" -embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.8" } -embedded-hal-async = { version = "0.1.0-alpha.1" } +embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11" } +embedded-hal-async = { version = "=0.2.0-alpha.2" } panic-probe = { version = "0.3", features = ["print-defmt"] } futures = { version = "0.3.17", default-features = false, features = ["async-await"] } heapless = { version = "0.7.5", default-features = false } +chrono = { version = "^0.4", default-features = false } micromath = "2.0.0" -usb-device = "0.2" -usbd-serial = "0.1.1" - diff --git a/examples/stm32l4/src/bin/adc.rs b/examples/stm32l4/src/bin/adc.rs index 281346e5f..1771e5202 100644 --- a/examples/stm32l4/src/bin/adc.rs +++ b/examples/stm32l4/src/bin/adc.rs @@ -12,12 +12,10 @@ use {defmt_rtt as _, panic_probe as _}; fn main() -> ! { info!("Hello World!"); - unsafe { - pac::RCC.ccipr().modify(|w| { - w.set_adcsel(0b11); - }); - pac::RCC.ahb2enr().modify(|w| w.set_adcen(true)); - } + pac::RCC.ccipr().modify(|w| { + w.set_adcsel(0b11); + }); + pac::RCC.ahb2enr().modify(|w| w.set_adcen(true)); let p = embassy_stm32::init(Default::default()); diff --git a/examples/stm32l4/src/bin/dac.rs b/examples/stm32l4/src/bin/dac.rs index d6e744aa6..ade43eb35 100644 --- a/examples/stm32l4/src/bin/dac.rs +++ b/examples/stm32l4/src/bin/dac.rs @@ -3,28 +3,21 @@ #![feature(type_alias_impl_trait)] use defmt::*; -use embassy_stm32::dac::{Channel, Dac, Value}; -use embassy_stm32::pac; +use embassy_stm32::dac::{DacCh1, DacChannel, Value}; +use embassy_stm32::dma::NoDma; use {defmt_rtt as _, panic_probe as _}; #[cortex_m_rt::entry] fn main() -> ! { + let p = embassy_stm32::init(Default::default()); info!("Hello World!"); - unsafe { - pac::RCC.apb1enr1().modify(|w| { - w.set_dac1en(true); - }); - } - - let p = embassy_stm32::init(Default::default()); - - let mut dac = Dac::new_1ch(p.DAC1, p.PA4); + let mut dac = DacCh1::new(p.DAC1, NoDma, p.PA4); loop { for v in 0..=255 { - unwrap!(dac.set(Channel::Ch1, Value::Bit8(to_sine_wave(v)))); - unwrap!(dac.trigger(Channel::Ch1)); + unwrap!(dac.set(Value::Bit8(to_sine_wave(v)))); + dac.trigger(); } } } diff --git a/examples/stm32l4/src/bin/dac_dma.rs b/examples/stm32l4/src/bin/dac_dma.rs new file mode 100644 index 000000000..c27cc03e1 --- /dev/null +++ b/examples/stm32l4/src/bin/dac_dma.rs @@ -0,0 +1,137 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::dac::{DacChannel, ValueArray}; +use embassy_stm32::pac::timer::vals::{Mms, Opm}; +use embassy_stm32::peripherals::{TIM6, TIM7}; +use embassy_stm32::rcc::low_level::RccPeripheral; +use embassy_stm32::time::Hertz; +use embassy_stm32::timer::low_level::Basic16bitInstance; +use micromath::F32Ext; +use {defmt_rtt as _, panic_probe as _}; + +pub type Dac1Type = + embassy_stm32::dac::DacCh1<'static, embassy_stm32::peripherals::DAC1, embassy_stm32::peripherals::DMA1_CH3>; + +pub type Dac2Type = + embassy_stm32::dac::DacCh2<'static, embassy_stm32::peripherals::DAC1, embassy_stm32::peripherals::DMA1_CH4>; + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let config = embassy_stm32::Config::default(); + + // Initialize the board and obtain a Peripherals instance + let p: embassy_stm32::Peripherals = embassy_stm32::init(config); + + // Obtain two independent channels (p.DAC1 can only be consumed once, though!) + let (dac_ch1, dac_ch2) = embassy_stm32::dac::Dac::new(p.DAC1, p.DMA1_CH3, p.DMA1_CH4, p.PA4, p.PA5).split(); + + spawner.spawn(dac_task1(dac_ch1)).ok(); + spawner.spawn(dac_task2(dac_ch2)).ok(); +} + +#[embassy_executor::task] +async fn dac_task1(mut dac: Dac1Type) { + let data: &[u8; 256] = &calculate_array::<256>(); + + info!("TIM6 frequency is {}", TIM6::frequency()); + const FREQUENCY: Hertz = Hertz::hz(200); + + // Compute the reload value such that we obtain the FREQUENCY for the sine + let reload: u32 = (TIM6::frequency().0 / FREQUENCY.0) / data.len() as u32; + + // Depends on your clock and on the specific chip used, you may need higher or lower values here + if reload < 10 { + error!("Reload value {} below threshold!", reload); + } + + dac.select_trigger(embassy_stm32::dac::Ch1Trigger::Tim6).unwrap(); + dac.enable_channel().unwrap(); + + TIM6::enable(); + TIM6::regs().arr().modify(|w| w.set_arr(reload as u16 - 1)); + TIM6::regs().cr2().modify(|w| w.set_mms(Mms::UPDATE)); + TIM6::regs().cr1().modify(|w| { + w.set_opm(Opm::DISABLED); + w.set_cen(true); + }); + + debug!( + "TIM6 Frequency {}, Target Frequency {}, Reload {}, Reload as u16 {}, Samples {}", + TIM6::frequency(), + FREQUENCY, + reload, + reload as u16, + data.len() + ); + + // Loop technically not necessary if DMA circular mode is enabled + loop { + info!("Loop DAC1"); + if let Err(e) = dac.write(ValueArray::Bit8(data), true).await { + error!("Could not write to dac: {}", e); + } + } +} + +#[embassy_executor::task] +async fn dac_task2(mut dac: Dac2Type) { + let data: &[u8; 256] = &calculate_array::<256>(); + + info!("TIM7 frequency is {}", TIM7::frequency()); + + const FREQUENCY: Hertz = Hertz::hz(600); + let reload: u32 = (TIM7::frequency().0 / FREQUENCY.0) / data.len() as u32; + + if reload < 10 { + error!("Reload value {} below threshold!", reload); + } + + TIM7::enable(); + TIM7::regs().arr().modify(|w| w.set_arr(reload as u16 - 1)); + TIM7::regs().cr2().modify(|w| w.set_mms(Mms::UPDATE)); + TIM7::regs().cr1().modify(|w| { + w.set_opm(Opm::DISABLED); + w.set_cen(true); + }); + + dac.select_trigger(embassy_stm32::dac::Ch2Trigger::Tim7).unwrap(); + + debug!( + "TIM7 Frequency {}, Target Frequency {}, Reload {}, Reload as u16 {}, Samples {}", + TIM7::frequency(), + FREQUENCY, + reload, + reload as u16, + data.len() + ); + + if let Err(e) = dac.write(ValueArray::Bit8(data), true).await { + error!("Could not write to dac: {}", e); + } +} + +fn to_sine_wave(v: u8) -> u8 { + if v >= 128 { + // top half + let r = 3.14 * ((v - 128) as f32 / 128.0); + (r.sin() * 128.0 + 127.0) as u8 + } else { + // bottom half + let r = 3.14 + 3.14 * (v as f32 / 128.0); + (r.sin() * 128.0 + 127.0) as u8 + } +} + +fn calculate_array() -> [u8; N] { + let mut res = [0; N]; + let mut i = 0; + while i < N { + res[i] = to_sine_wave(i as u8); + i += 1; + } + res +} diff --git a/examples/stm32l4/src/bin/i2c.rs b/examples/stm32l4/src/bin/i2c.rs index d54c080c7..d0060d20c 100644 --- a/examples/stm32l4/src/bin/i2c.rs +++ b/examples/stm32l4/src/bin/i2c.rs @@ -6,22 +6,25 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::dma::NoDma; use embassy_stm32::i2c::I2c; -use embassy_stm32::interrupt; use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, i2c, peripherals}; use {defmt_rtt as _, panic_probe as _}; const ADDRESS: u8 = 0x5F; const WHOAMI: u8 = 0x0F; +bind_interrupts!(struct Irqs { + I2C2_EV => i2c::InterruptHandler; +}); + #[embassy_executor::main] -async fn main(_spawner: Spawner) -> ! { +async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); - let irq = interrupt::take!(I2C2_EV); let mut i2c = I2c::new( p.I2C2, p.PB10, p.PB11, - irq, + Irqs, NoDma, NoDma, Hertz(100_000), diff --git a/examples/stm32l4/src/bin/i2c_blocking_async.rs b/examples/stm32l4/src/bin/i2c_blocking_async.rs index 35a86660d..eca59087b 100644 --- a/examples/stm32l4/src/bin/i2c_blocking_async.rs +++ b/examples/stm32l4/src/bin/i2c_blocking_async.rs @@ -7,23 +7,26 @@ use embassy_embedded_hal::adapter::BlockingAsync; use embassy_executor::Spawner; use embassy_stm32::dma::NoDma; use embassy_stm32::i2c::I2c; -use embassy_stm32::interrupt; use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, i2c, peripherals}; use embedded_hal_async::i2c::I2c as I2cTrait; use {defmt_rtt as _, panic_probe as _}; const ADDRESS: u8 = 0x5F; const WHOAMI: u8 = 0x0F; +bind_interrupts!(struct Irqs { + I2C2_EV => i2c::InterruptHandler; +}); + #[embassy_executor::main] -async fn main(_spawner: Spawner) -> ! { +async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); - let irq = interrupt::take!(I2C2_EV); let i2c = I2c::new( p.I2C2, p.PB10, p.PB11, - irq, + Irqs, NoDma, NoDma, Hertz(100_000), diff --git a/examples/stm32l4/src/bin/i2c_dma.rs b/examples/stm32l4/src/bin/i2c_dma.rs index 3ce9398a4..cf6f3da67 100644 --- a/examples/stm32l4/src/bin/i2c_dma.rs +++ b/examples/stm32l4/src/bin/i2c_dma.rs @@ -5,22 +5,25 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::i2c::I2c; -use embassy_stm32::interrupt; use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, i2c, peripherals}; use {defmt_rtt as _, panic_probe as _}; const ADDRESS: u8 = 0x5F; const WHOAMI: u8 = 0x0F; +bind_interrupts!(struct Irqs { + I2C2_EV => i2c::InterruptHandler; +}); + #[embassy_executor::main] -async fn main(_spawner: Spawner) -> ! { +async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); - let irq = interrupt::take!(I2C2_EV); let mut i2c = I2c::new( p.I2C2, p.PB10, p.PB11, - irq, + Irqs, p.DMA1_CH4, p.DMA1_CH5, Hertz(100_000), diff --git a/examples/stm32l4/src/bin/mco.rs b/examples/stm32l4/src/bin/mco.rs new file mode 100644 index 000000000..dea0c66e0 --- /dev/null +++ b/examples/stm32l4/src/bin/mco.rs @@ -0,0 +1,27 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::rcc::{Mco, Mco1Source, McoClock}; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let _mco = Mco::new(p.MCO, p.PA8, Mco1Source::Hsi16, McoClock::DIV1); + + let mut led = Output::new(p.PB14, Level::High, Speed::Low); + + loop { + led.set_high(); + Timer::after(Duration::from_millis(300)).await; + led.set_low(); + Timer::after(Duration::from_millis(300)).await; + } +} diff --git a/examples/stm32l4/src/bin/rtc.rs b/examples/stm32l4/src/bin/rtc.rs new file mode 100644 index 000000000..d72d5ddb6 --- /dev/null +++ b/examples/stm32l4/src/bin/rtc.rs @@ -0,0 +1,49 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use chrono::{NaiveDate, NaiveDateTime}; +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::rcc::{self, ClockSrc, PLLClkDiv, PLLMul, PLLSource, PLLSrcDiv}; +use embassy_stm32::rtc::{Rtc, RtcConfig}; +use embassy_stm32::time::Hertz; +use embassy_stm32::Config; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = { + let mut config = Config::default(); + config.rcc.mux = ClockSrc::PLL( + PLLSource::HSE(Hertz::mhz(8)), + PLLClkDiv::Div2, + PLLSrcDiv::Div1, + PLLMul::Mul20, + None, + ); + config.rcc.rtc_mux = rcc::RtcClockSource::LSE32; + embassy_stm32::init(config) + }; + info!("Hello World!"); + + let now = NaiveDate::from_ymd_opt(2020, 5, 15) + .unwrap() + .and_hms_opt(10, 30, 15) + .unwrap(); + + let mut rtc = Rtc::new( + p.RTC, + RtcConfig::default().clock_config(embassy_stm32::rtc::RtcClockSource::LSE), + ); + info!("Got RTC! {:?}", now.timestamp()); + + rtc.set_datetime(now.into()).expect("datetime not set"); + + // In reality the delay would be much longer + Timer::after(Duration::from_millis(20000)).await; + + let then: NaiveDateTime = rtc.now().unwrap().into(); + info!("Got RTC! {:?}", then.timestamp()); +} diff --git a/examples/stm32l4/src/bin/usart.rs b/examples/stm32l4/src/bin/usart.rs index 4a4b46c53..beb5ec558 100644 --- a/examples/stm32l4/src/bin/usart.rs +++ b/examples/stm32l4/src/bin/usart.rs @@ -5,8 +5,13 @@ use defmt::*; use embassy_stm32::dma::NoDma; use embassy_stm32::usart::{Config, Uart}; +use embassy_stm32::{bind_interrupts, peripherals, usart}; use {defmt_rtt as _, panic_probe as _}; +bind_interrupts!(struct Irqs { + UART4 => usart::InterruptHandler; +}); + #[cortex_m_rt::entry] fn main() -> ! { info!("Hello World!"); @@ -14,7 +19,7 @@ fn main() -> ! { let p = embassy_stm32::init(Default::default()); let config = Config::default(); - let mut usart = Uart::new(p.UART4, p.PA1, p.PA0, NoDma, NoDma, config); + let mut usart = Uart::new(p.UART4, p.PA1, p.PA0, Irqs, NoDma, NoDma, config); unwrap!(usart.blocking_write(b"Hello Embassy World!\r\n")); info!("wrote Hello, starting echo"); diff --git a/examples/stm32l4/src/bin/usart_dma.rs b/examples/stm32l4/src/bin/usart_dma.rs index 728906897..b7d4cb01e 100644 --- a/examples/stm32l4/src/bin/usart_dma.rs +++ b/examples/stm32l4/src/bin/usart_dma.rs @@ -8,16 +8,21 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::dma::NoDma; use embassy_stm32::usart::{Config, Uart}; +use embassy_stm32::{bind_interrupts, peripherals, usart}; use heapless::String; use {defmt_rtt as _, panic_probe as _}; +bind_interrupts!(struct Irqs { + UART4 => usart::InterruptHandler; +}); + #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); info!("Hello World!"); let config = Config::default(); - let mut usart = Uart::new(p.UART4, p.PA1, p.PA0, p.DMA1_CH3, NoDma, config); + let mut usart = Uart::new(p.UART4, p.PA1, p.PA0, Irqs, p.DMA1_CH3, NoDma, config); for n in 0u32.. { let mut s: String<128> = String::new(); diff --git a/examples/stm32l4/src/bin/usb_serial.rs b/examples/stm32l4/src/bin/usb_serial.rs new file mode 100644 index 000000000..410d6891b --- /dev/null +++ b/examples/stm32l4/src/bin/usb_serial.rs @@ -0,0 +1,112 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::{panic, *}; +use defmt_rtt as _; // global logger +use embassy_executor::Spawner; +use embassy_stm32::rcc::*; +use embassy_stm32::usb_otg::{Driver, Instance}; +use embassy_stm32::{bind_interrupts, peripherals, usb_otg, Config}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::Builder; +use futures::future::join; +use panic_probe as _; + +bind_interrupts!(struct Irqs { + OTG_FS => usb_otg::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + let mut config = Config::default(); + config.rcc.mux = ClockSrc::PLL(PLLSource::HSI16, PLLClkDiv::Div2, PLLSrcDiv::Div1, PLLMul::Mul10, None); + config.rcc.hsi48 = true; + + let p = embassy_stm32::init(config); + + // Create the driver, from the HAL. + let mut ep_out_buffer = [0u8; 256]; + let mut config = embassy_stm32::usb_otg::Config::default(); + config.vbus_detection = true; + let driver = Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config); + + // Create embassy-usb Config + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.max_packet_size_0 = 64; + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial example"); + config.serial_number = Some("12345678"); + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut device_descriptor = [0; 256]; + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut device_descriptor, + &mut config_descriptor, + &mut bos_descriptor, + &mut control_buf, + ); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} diff --git a/examples/stm32l5/.cargo/config.toml b/examples/stm32l5/.cargo/config.toml index f2af6b556..86a145a27 100644 --- a/examples/stm32l5/.cargo/config.toml +++ b/examples/stm32l5/.cargo/config.toml @@ -1,6 +1,6 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -# replace STM32L552ZETxQ with your chip as listed in `probe-run --list-chips` -runner = "probe-run --chip STM32L552ZETxQ" +# replace STM32L552ZETxQ with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32L552ZETxQ" [build] target = "thumbv8m.main-none-eabihf" diff --git a/examples/stm32l5/Cargo.toml b/examples/stm32l5/Cargo.toml index 624c73c26..54911482e 100644 --- a/examples/stm32l5/Cargo.toml +++ b/examples/stm32l5/Cargo.toml @@ -2,30 +2,27 @@ edition = "2021" name = "embassy-stm32l5-examples" version = "0.1.0" - -[features] +license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } -embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-32768hz"] } +embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "stm32l552ze", "time-driver-any", "exti", "unstable-traits", "memory-x"] } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } -embassy-usb-serial = { version = "0.1.0", path = "../../embassy-usb-serial", features = ["defmt"] } -embassy-usb-hid = { version = "0.1.0", path = "../../embassy-usb-hid", features = ["defmt"] } -embassy-usb-ncm = { version = "0.1.0", path = "../../embassy-usb-ncm", features = ["defmt"] } -embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "pool-16"] } -usbd-hid = "0.5.2" +embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "dhcpv4", "medium-ethernet"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +usbd-hid = "0.6.0" defmt = "0.3" -defmt-rtt = "0.3" +defmt-rtt = "0.4" panic-probe = { version = "0.3", features = ["print-defmt"] } -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" futures = { version = "0.3.17", default-features = false, features = ["async-await"] } heapless = { version = "0.7.5", default-features = false } rand_core = { version = "0.6.3", default-features = false } -embedded-io = { version = "0.3.0", features = ["async"] } -static_cell = "1.0" +embedded-io = { version = "0.4.0", features = ["async"] } +static_cell = { version = "1.1", features = ["nightly"]} diff --git a/examples/stm32l5/src/bin/usb_ethernet.rs b/examples/stm32l5/src/bin/usb_ethernet.rs index 3286f5c4d..32eba4277 100644 --- a/examples/stm32l5/src/bin/usb_ethernet.rs +++ b/examples/stm32l5/src/bin/usb_ethernet.rs @@ -1,38 +1,30 @@ #![no_std] #![no_main] -#![feature(generic_associated_types)] #![feature(type_alias_impl_trait)] -use core::sync::atomic::{AtomicBool, Ordering}; -use core::task::Waker; - use defmt::*; use embassy_executor::Spawner; use embassy_net::tcp::TcpSocket; -use embassy_net::{PacketBox, PacketBoxExt, PacketBuf, Stack, StackResources}; +use embassy_net::{Stack, StackResources}; use embassy_stm32::rcc::*; use embassy_stm32::rng::Rng; -use embassy_stm32::time::Hertz; use embassy_stm32::usb::Driver; -use embassy_stm32::{interrupt, Config}; -use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; -use embassy_sync::channel::Channel; +use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; +use embassy_usb::class::cdc_ncm::embassy_net::{Device, Runner, State as NetState}; +use embassy_usb::class::cdc_ncm::{CdcNcmClass, State}; use embassy_usb::{Builder, UsbDevice}; -use embassy_usb_ncm::{CdcNcmClass, Receiver, Sender, State}; -use embedded_io::asynch::{Read, Write}; +use embedded_io::asynch::Write; use rand_core::RngCore; -use static_cell::StaticCell; +use static_cell::make_static; use {defmt_rtt as _, panic_probe as _}; type MyDriver = Driver<'static, embassy_stm32::peripherals::USB>; -macro_rules! singleton { - ($val:expr) => {{ - type T = impl Sized; - static STATIC_CELL: StaticCell = StaticCell::new(); - STATIC_CELL.init_with(move || $val) - }}; -} +const MTU: usize = 1514; + +bind_interrupts!(struct Irqs { + USB_FS => usb::InterruptHandler; +}); #[embassy_executor::task] async fn usb_task(mut device: UsbDevice<'static, MyDriver>) -> ! { @@ -40,46 +32,12 @@ async fn usb_task(mut device: UsbDevice<'static, MyDriver>) -> ! { } #[embassy_executor::task] -async fn usb_ncm_rx_task(mut class: Receiver<'static, MyDriver>) { - loop { - warn!("WAITING for connection"); - LINK_UP.store(false, Ordering::Relaxed); - - class.wait_connection().await.unwrap(); - - warn!("Connected"); - LINK_UP.store(true, Ordering::Relaxed); - - loop { - let mut p = unwrap!(PacketBox::new(embassy_net::Packet::new())); - let n = match class.read_packet(&mut p[..]).await { - Ok(n) => n, - Err(e) => { - warn!("error reading packet: {:?}", e); - break; - } - }; - - let buf = p.slice(0..n); - if RX_CHANNEL.try_send(buf).is_err() { - warn!("Failed pushing rx'd packet to channel."); - } - } - } +async fn usb_ncm_task(class: Runner<'static, MyDriver, MTU>) -> ! { + class.run().await } #[embassy_executor::task] -async fn usb_ncm_tx_task(mut class: Sender<'static, MyDriver>) { - loop { - let pkt = TX_CHANNEL.recv().await; - if let Err(e) = class.write_packet(&pkt[..]).await { - warn!("Failed to TX packet: {:?}", e); - } - } -} - -#[embassy_executor::task] -async fn net_task(stack: &'static Stack) -> ! { +async fn net_task(stack: &'static Stack>) -> ! { stack.run().await } @@ -91,8 +49,7 @@ async fn main(spawner: Spawner) { let p = embassy_stm32::init(config); // Create the driver, from the HAL. - let irq = interrupt::take!(USB_FS); - let driver = Driver::new(p.USB, irq, p.PA12, p.PA11); + let driver = Driver::new(p.USB, Irqs, p.PA12, p.PA11); // Create embassy-usb Config let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); @@ -108,58 +65,34 @@ async fn main(spawner: Spawner) { config.device_sub_class = 0x02; config.device_protocol = 0x01; - struct Resources { - device_descriptor: [u8; 256], - config_descriptor: [u8; 256], - bos_descriptor: [u8; 256], - control_buf: [u8; 128], - serial_state: State<'static>, - } - let res: &mut Resources = singleton!(Resources { - device_descriptor: [0; 256], - config_descriptor: [0; 256], - bos_descriptor: [0; 256], - control_buf: [0; 128], - serial_state: State::new(), - }); - // Create embassy-usb DeviceBuilder using the driver and config. let mut builder = Builder::new( driver, config, - &mut res.device_descriptor, - &mut res.config_descriptor, - &mut res.bos_descriptor, - &mut res.control_buf, - None, + &mut make_static!([0; 256])[..], + &mut make_static!([0; 256])[..], + &mut make_static!([0; 256])[..], + &mut make_static!([0; 128])[..], ); - // WARNINGS for Android ethernet tethering: - // - On Pixel 4a, it refused to work on Android 11, worked on Android 12. - // - if the host's MAC address has the "locally-administered" bit set (bit 1 of first byte), - // it doesn't work! The "Ethernet tethering" option in settings doesn't get enabled. - // This is due to regex spaghetti: https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-mainline-12.0.0_r84/core/res/res/values/config.xml#417 - // and this nonsense in the linux kernel: https://github.com/torvalds/linux/blob/c00c5e1d157bec0ef0b0b59aa5482eb8dc7e8e49/drivers/net/usb/usbnet.c#L1751-L1757 - // Our MAC addr. let our_mac_addr = [0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC]; // Host's MAC addr. This is the MAC the host "thinks" its USB-to-ethernet adapter has. let host_mac_addr = [0x88, 0x88, 0x88, 0x88, 0x88, 0x88]; // Create classes on the builder. - let class = CdcNcmClass::new(&mut builder, &mut res.serial_state, host_mac_addr, 64); + let class = CdcNcmClass::new(&mut builder, make_static!(State::new()), host_mac_addr, 64); // Build the builder. let usb = builder.build(); unwrap!(spawner.spawn(usb_task(usb))); - let (tx, rx) = class.split(); - unwrap!(spawner.spawn(usb_ncm_rx_task(rx))); - unwrap!(spawner.spawn(usb_ncm_tx_task(tx))); + let (runner, device) = class.into_embassy_net_device::(make_static!(NetState::new()), our_mac_addr); + unwrap!(spawner.spawn(usb_ncm_task(runner))); - let config = embassy_net::ConfigStrategy::Dhcp; - //let config = embassy_net::ConfigStrategy::Static(embassy_net::Config { + let config = embassy_net::Config::dhcpv4(Default::default()); + //let config = embassy_net::Config::ipv4_static(embassy_net::StaticConfigV4 { // address: Ipv4Cidr::new(Ipv4Address::new(10, 42, 0, 61), 24), // dns_servers: Vec::new(), // gateway: Some(Ipv4Address::new(10, 42, 0, 1)), @@ -170,11 +103,10 @@ async fn main(spawner: Spawner) { let seed = rng.next_u64(); // Init network stack - let device = Device { mac_addr: our_mac_addr }; - let stack = &*singleton!(Stack::new( + let stack = &*make_static!(Stack::new( device, config, - singleton!(StackResources::<1, 2, 8>::new()), + make_static!(StackResources::<2>::new()), seed )); @@ -188,7 +120,7 @@ async fn main(spawner: Spawner) { loop { let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); - socket.set_timeout(Some(embassy_net::SmolDuration::from_secs(10))); + socket.set_timeout(Some(embassy_time::Duration::from_secs(10))); info!("Listening on TCP:1234..."); if let Err(e) = socket.accept(1234).await { @@ -223,50 +155,3 @@ async fn main(spawner: Spawner) { } } } - -static TX_CHANNEL: Channel = Channel::new(); -static RX_CHANNEL: Channel = Channel::new(); -static LINK_UP: AtomicBool = AtomicBool::new(false); - -struct Device { - mac_addr: [u8; 6], -} - -impl embassy_net::Device for Device { - fn register_waker(&mut self, waker: &Waker) { - // loopy loopy wakey wakey - waker.wake_by_ref() - } - - fn link_state(&mut self) -> embassy_net::LinkState { - match LINK_UP.load(Ordering::Relaxed) { - true => embassy_net::LinkState::Up, - false => embassy_net::LinkState::Down, - } - } - - fn capabilities(&self) -> embassy_net::DeviceCapabilities { - let mut caps = embassy_net::DeviceCapabilities::default(); - caps.max_transmission_unit = 1514; // 1500 IP + 14 ethernet header - caps.medium = embassy_net::Medium::Ethernet; - caps - } - - fn is_transmit_ready(&mut self) -> bool { - true - } - - fn transmit(&mut self, pkt: PacketBuf) { - if TX_CHANNEL.try_send(pkt).is_err() { - warn!("TX failed") - } - } - - fn receive<'a>(&mut self) -> Option { - RX_CHANNEL.try_recv().ok() - } - - fn ethernet_address(&self) -> [u8; 6] { - self.mac_addr - } -} diff --git a/examples/stm32l5/src/bin/usb_hid_mouse.rs b/examples/stm32l5/src/bin/usb_hid_mouse.rs index f7e3d93e3..7e894e407 100644 --- a/examples/stm32l5/src/bin/usb_hid_mouse.rs +++ b/examples/stm32l5/src/bin/usb_hid_mouse.rs @@ -1,22 +1,24 @@ #![no_std] #![no_main] -#![feature(generic_associated_types)] #![feature(type_alias_impl_trait)] use defmt::*; use embassy_executor::Spawner; +use embassy_futures::join::join; use embassy_stm32::rcc::*; -use embassy_stm32::time::Hertz; use embassy_stm32::usb::Driver; -use embassy_stm32::{interrupt, Config, Peripherals}; +use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; use embassy_time::{Duration, Timer}; +use embassy_usb::class::hid::{HidWriter, ReportId, RequestHandler, State}; use embassy_usb::control::OutResponse; use embassy_usb::Builder; -use embassy_usb_hid::{HidWriter, ReportId, RequestHandler, State}; -use futures::future::join; use usbd_hid::descriptor::{MouseReport, SerializedDescriptor}; use {defmt_rtt as _, panic_probe as _}; +bind_interrupts!(struct Irqs { + USB_FS => usb::InterruptHandler; +}); + #[embassy_executor::main] async fn main(_spawner: Spawner) { let mut config = Config::default(); @@ -25,8 +27,7 @@ async fn main(_spawner: Spawner) { let p = embassy_stm32::init(config); // Create the driver, from the HAL. - let irq = interrupt::take!(USB_FS); - let driver = Driver::new(p.USB, irq, p.PA12, p.PA11); + let driver = Driver::new(p.USB, Irqs, p.PA12, p.PA11); // Create embassy-usb Config let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); @@ -53,11 +54,10 @@ async fn main(_spawner: Spawner) { &mut config_descriptor, &mut bos_descriptor, &mut control_buf, - None, ); // Create classes on the builder. - let config = embassy_usb_hid::Config { + let config = embassy_usb::class::hid::Config { report_descriptor: MouseReport::desc(), request_handler: Some(&request_handler), poll_ms: 60, @@ -111,11 +111,11 @@ impl RequestHandler for MyRequestHandler { OutResponse::Accepted } - fn set_idle(&self, id: Option, dur: Duration) { + fn set_idle_ms(&self, id: Option, dur: u32) { info!("Set idle rate for {:?} to {:?}", id, dur); } - fn get_idle(&self, id: Option) -> Option { + fn get_idle_ms(&self, id: Option) -> Option { info!("Get idle rate for {:?}", id); None } diff --git a/examples/stm32l5/src/bin/usb_serial.rs b/examples/stm32l5/src/bin/usb_serial.rs index 323db6557..0c719560f 100644 --- a/examples/stm32l5/src/bin/usb_serial.rs +++ b/examples/stm32l5/src/bin/usb_serial.rs @@ -4,16 +4,19 @@ use defmt::{panic, *}; use embassy_executor::Spawner; +use embassy_futures::join::join; use embassy_stm32::rcc::*; -use embassy_stm32::time::Hertz; use embassy_stm32::usb::{Driver, Instance}; -use embassy_stm32::{interrupt, Config}; +use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; use embassy_usb::driver::EndpointError; use embassy_usb::Builder; -use embassy_usb_serial::{CdcAcmClass, State}; -use futures::future::join; use {defmt_rtt as _, panic_probe as _}; +bind_interrupts!(struct Irqs { + USB_FS => usb::InterruptHandler; +}); + #[embassy_executor::main] async fn main(_spawner: Spawner) { let mut config = Config::default(); @@ -24,8 +27,7 @@ async fn main(_spawner: Spawner) { info!("Hello World!"); // Create the driver, from the HAL. - let irq = interrupt::take!(USB_FS); - let driver = Driver::new(p.USB, irq, p.PA12, p.PA11); + let driver = Driver::new(p.USB, Irqs, p.PA12, p.PA11); // Create embassy-usb Config let config = embassy_usb::Config::new(0xc0de, 0xcafe); @@ -47,7 +49,6 @@ async fn main(_spawner: Spawner) { &mut config_descriptor, &mut bos_descriptor, &mut control_buf, - None, ); // Create classes on the builder. diff --git a/examples/stm32u5/.cargo/config.toml b/examples/stm32u5/.cargo/config.toml index 975630a14..36c5b63a6 100644 --- a/examples/stm32u5/.cargo/config.toml +++ b/examples/stm32u5/.cargo/config.toml @@ -1,6 +1,6 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -# replace STM32U585AIIx with your chip as listed in `probe-run --list-chips` -runner = "probe-run --chip STM32U585AIIx" +# replace STM32U585AIIx with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32U585AIIx" [build] target = "thumbv8m.main-none-eabihf" diff --git a/examples/stm32u5/Cargo.toml b/examples/stm32u5/Cargo.toml index ff0ec9f42..835e32940 100644 --- a/examples/stm32u5/Cargo.toml +++ b/examples/stm32u5/Cargo.toml @@ -2,17 +2,19 @@ edition = "2021" name = "embassy-stm32u5-examples" version = "0.1.0" +license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } -embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-32768hz"] } +embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "stm32u585ai", "time-driver-any", "memory-x" ] } +embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } defmt = "0.3" -defmt-rtt = "0.3" +defmt-rtt = "0.4" -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" panic-probe = { version = "0.3", features = ["print-defmt"] } @@ -20,8 +22,3 @@ futures = { version = "0.3.17", default-features = false, features = ["async-awa heapless = { version = "0.7.5", default-features = false } micromath = "2.0.0" - -#[patch.crates-io] -#defmt = { git="https://github.com/knurling-rs/defmt.git" } -#defmt-rtt = { git="https://github.com/knurling-rs/defmt.git" } - diff --git a/examples/stm32u5/src/bin/usb_serial.rs b/examples/stm32u5/src/bin/usb_serial.rs new file mode 100644 index 000000000..9e47fb18a --- /dev/null +++ b/examples/stm32u5/src/bin/usb_serial.rs @@ -0,0 +1,112 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::{panic, *}; +use defmt_rtt as _; // global logger +use embassy_executor::Spawner; +use embassy_stm32::rcc::*; +use embassy_stm32::usb_otg::{Driver, Instance}; +use embassy_stm32::{bind_interrupts, peripherals, usb_otg, Config}; +use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; +use embassy_usb::driver::EndpointError; +use embassy_usb::Builder; +use futures::future::join; +use panic_probe as _; + +bind_interrupts!(struct Irqs { + OTG_FS => usb_otg::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + let mut config = Config::default(); + config.rcc.mux = ClockSrc::PLL1R(PllSrc::HSI16, PllM::Div2, PllN::Mul10, PllClkDiv::NotDivided); + //config.rcc.mux = ClockSrc::MSI(MSIRange::Range48mhz); + config.rcc.hsi48 = true; + + let p = embassy_stm32::init(config); + + // Create the driver, from the HAL. + let mut ep_out_buffer = [0u8; 256]; + let mut config = embassy_stm32::usb_otg::Config::default(); + config.vbus_detection = true; + let driver = Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config); + + // Create embassy-usb Config + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-serial example"); + config.serial_number = Some("12345678"); + + // Required for windows compatibility. + // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut device_descriptor = [0; 256]; + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut device_descriptor, + &mut config_descriptor, + &mut bos_descriptor, + &mut control_buf, + ); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let echo_fut = async { + loop { + class.wait_connection().await; + info!("Connected"); + let _ = echo(&mut class).await; + info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, echo_fut).await; +} + +struct Disconnected {} + +impl From for Disconnected { + fn from(val: EndpointError) -> Self { + match val { + EndpointError::BufferOverflow => panic!("Buffer overflow"), + EndpointError::Disabled => Disconnected {}, + } + } +} + +async fn echo<'d, T: Instance + 'd>(class: &mut CdcAcmClass<'d, Driver<'d, T>>) -> Result<(), Disconnected> { + let mut buf = [0; 64]; + loop { + let n = class.read_packet(&mut buf).await?; + let data = &buf[..n]; + info!("data: {:x}", data); + class.write_packet(data).await?; + } +} diff --git a/examples/stm32wb/.cargo/config.toml b/examples/stm32wb/.cargo/config.toml index fcf95741a..51c499ee7 100644 --- a/examples/stm32wb/.cargo/config.toml +++ b/examples/stm32wb/.cargo/config.toml @@ -1,6 +1,7 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -# replace STM32WB55CCUx with your chip as listed in `probe-run --list-chips` -runner = "probe-run --chip STM32WB55CCUx --speed 1000 --connect-under-reset" +# replace STM32WB55CCUx with your chip as listed in `probe-rs chip list` +# runner = "probe-run --chip STM32WB55RGVx --speed 1000 --connect-under-reset" +runner = "teleprobe local run --chip STM32WB55RG --elf" [build] target = "thumbv7em-none-eabihf" diff --git a/examples/stm32wb/Cargo.toml b/examples/stm32wb/Cargo.toml index 3b10da0ad..becf2d3fb 100644 --- a/examples/stm32wb/Cargo.toml +++ b/examples/stm32wb/Cargo.toml @@ -2,19 +2,47 @@ edition = "2021" name = "embassy-stm32wb-examples" version = "0.1.0" +license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } -embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-32768hz"] } -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32wb55cc", "time-driver-any", "exti"] } +embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32wb55rg", "time-driver-any", "memory-x", "exti"] } +embassy-stm32-wpan = { version = "0.1.0", path = "../../embassy-stm32-wpan", features = ["defmt", "stm32wb55rg"] } defmt = "0.3" -defmt-rtt = "0.3" +defmt-rtt = "0.4" -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" panic-probe = { version = "0.3", features = ["print-defmt"] } futures = { version = "0.3.17", default-features = false, features = ["async-await"] } heapless = { version = "0.7.5", default-features = false } + + +[features] +default = ["ble", "mac"] +mac = ["embassy-stm32-wpan/mac"] +ble = ["embassy-stm32-wpan/ble"] + +[[bin]] +name = "tl_mbox_ble" +required-features = ["ble"] + +[[bin]] +name = "tl_mbox_mac" +required-features = ["mac"] + +[[bin]] +name = "mac_ffd" +required-features = ["mac"] + +[[bin]] +name = "eddystone_beacon" +required-features = ["ble"] + +[[bin]] +name = "gatt_server" +required-features = ["ble"] \ No newline at end of file diff --git a/examples/stm32wb/build.rs b/examples/stm32wb/build.rs index 30691aa97..29b3a9b2a 100644 --- a/examples/stm32wb/build.rs +++ b/examples/stm32wb/build.rs @@ -1,35 +1,11 @@ -//! This build script copies the `memory.x` file from the crate root into -//! a directory where the linker can always find it at build time. -//! For many projects this is optional, as the linker always searches the -//! project root directory -- wherever `Cargo.toml` is. However, if you -//! are using a workspace or have a more complicated build setup, this -//! build script becomes required. Additionally, by requesting that -//! Cargo re-run the build script whenever `memory.x` is changed, -//! updating `memory.x` ensures a rebuild of the application with the -//! new memory settings. - -use std::env; -use std::fs::File; -use std::io::Write; -use std::path::PathBuf; - -fn main() { - // Put `memory.x` in our output directory and ensure it's - // on the linker search path. - let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); - File::create(out.join("memory.x")) - .unwrap() - .write_all(include_bytes!("memory.x")) - .unwrap(); - println!("cargo:rustc-link-search={}", out.display()); - - // By default, Cargo will re-run a build script whenever - // any file in the project changes. By specifying `memory.x` - // here, we ensure the build script is only re-run when - // `memory.x` is changed. - println!("cargo:rerun-if-changed=memory.x"); +use std::error::Error; +fn main() -> Result<(), Box> { println!("cargo:rustc-link-arg-bins=--nmagic"); println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rerun-if-changed=link.x"); + println!("cargo:rustc-link-arg-bins=-Ttl_mbox.x"); println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + + Ok(()) } diff --git a/examples/stm32wb/memory.x b/examples/stm32wb/memory.x deleted file mode 100644 index 2b4dcce34..000000000 --- a/examples/stm32wb/memory.x +++ /dev/null @@ -1,41 +0,0 @@ -/* - The size of this file must be exactly the same as in other memory_xx.x files. - Memory size for STM32WB55xC with 256K FLASH -*/ - -MEMORY -{ - FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 256K - RAM (xrw) : ORIGIN = 0x20000004, LENGTH = 191K - RAM_SHARED (xrw) : ORIGIN = 0x20030000, LENGTH = 10K -} - -/* Place stack at the end of SRAM1 */ -_stack_start = ORIGIN(RAM) + LENGTH(RAM); - -/* - * Scatter the mailbox interface memory sections in shared memory - */ -SECTIONS { - TL_REF_TABLE (NOLOAD) : { *(TL_REF_TABLE) } >RAM_SHARED - - TL_DEVICE_INFO_TABLE 0x2003001c (NOLOAD) : { *(TL_DEVICE_INFO_TABLE) } >RAM_SHARED - TL_BLE_TABLE 0x2003003c (NOLOAD) : { *(TL_BLE_TABLE) } >RAM_SHARED - TL_THREAD_TABLE 0x2003004c (NOLOAD) : { *(TL_THREAD_TABLE) } >RAM_SHARED - TL_SYS_TABLE 0x20030058 (NOLOAD) : { *(TL_SYS_TABLE) } >RAM_SHARED - TL_MEM_MANAGER_TABLE 0x20030060 (NOLOAD) : { *(TL_MEM_MANAGER_TABLE) } >RAM_SHARED - TL_TRACES_TABLE 0x2003007c (NOLOAD) : { *(TL_TRACES_TABLE) } >RAM_SHARED - TL_MAC_802_15_4_TABLE 0x20030080 (NOLOAD) : { *(TL_MAC_802_15_4_TABLE) } >RAM_SHARED - - HCI_ACL_DATA_BUFFER 0x20030a08 (NOLOAD) : { *(HCI_ACL_DATA_BUFFER) } >RAM_SHARED - BLE_CMD_BUFFER 0x200308fc (NOLOAD) : { *(BLE_CMD_BUFFER) } >RAM_SHARED - BLE_SPARE_EVT_BUF 0x200301a8 (NOLOAD) : { *(BLE_SPARE_EVT_BUF) } >RAM_SHARED - SYS_SPARE_EVT_BUF 0x200302b4 (NOLOAD) : { *(SYS_SPARE_EVT_BUF) } >RAM_SHARED - EVT_POOL 0x200303c0 (NOLOAD) : { *(EVT_POOL) } >RAM_SHARED - SYS_CMD_BUF 0x2003009c (NOLOAD) : { *(SYS_CMD_BUF) } >RAM_SHARED - SYSTEM_EVT_QUEUE 0x20030b28 (NOLOAD) : { *(SYSTEM_EVT_QUEUE) } >RAM_SHARED - EVT_QUEUE 0x20030b10 (NOLOAD) : { *(EVT_QUEUE) } >RAM_SHARED - CS_BUFFER 0x20030b18 (NOLOAD) : { *(CS_BUFFER) } >RAM_SHARED - TRACES_EVT_QUEUE 0x20030094 (NOLOAD) : { *(TRACES_EVT_QUEUE) } >RAM_SHARED - FREE_BUF_QUEUE 0x2003008c (NOLOAD) : { *(FREE_BUF_QUEUE) } >RAM_SHARED -} diff --git a/examples/stm32wb/src/bin/eddystone_beacon.rs b/examples/stm32wb/src/bin/eddystone_beacon.rs new file mode 100644 index 000000000..451bd7d29 --- /dev/null +++ b/examples/stm32wb/src/bin/eddystone_beacon.rs @@ -0,0 +1,249 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use core::time::Duration; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::bind_interrupts; +use embassy_stm32::ipcc::{Config, ReceiveInterruptHandler, TransmitInterruptHandler}; +use embassy_stm32_wpan::hci::host::uart::UartHci; +use embassy_stm32_wpan::hci::host::{AdvertisingFilterPolicy, EncryptionKey, HostHci, OwnAddressType}; +use embassy_stm32_wpan::hci::types::AdvertisingType; +use embassy_stm32_wpan::hci::vendor::stm32wb::command::gap::{ + AdvertisingDataType, DiscoverableParameters, GapCommands, Role, +}; +use embassy_stm32_wpan::hci::vendor::stm32wb::command::gatt::GattCommands; +use embassy_stm32_wpan::hci::vendor::stm32wb::command::hal::{ConfigData, HalCommands, PowerLevel}; +use embassy_stm32_wpan::hci::BdAddr; +use embassy_stm32_wpan::lhci::LhciC1DeviceInformationCcrp; +use embassy_stm32_wpan::TlMbox; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs{ + IPCC_C1_RX => ReceiveInterruptHandler; + IPCC_C1_TX => TransmitInterruptHandler; +}); + +const BLE_GAP_DEVICE_NAME_LENGTH: u8 = 7; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + /* + How to make this work: + + - Obtain a NUCLEO-STM32WB55 from your preferred supplier. + - Download and Install STM32CubeProgrammer. + - Download stm32wb5x_FUS_fw.bin, stm32wb5x_BLE_Stack_full_fw.bin, and Release_Notes.html from + gh:STMicroelectronics/STM32CubeWB@2234d97/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x + - Open STM32CubeProgrammer + - On the right-hand pane, click "firmware upgrade" to upgrade the st-link firmware. + - Once complete, click connect to connect to the device. + - On the left hand pane, click the RSS signal icon to open "Firmware Upgrade Services". + - In the Release_Notes.html, find the memory address that corresponds to your device for the stm32wb5x_FUS_fw.bin file + - Select that file, the memory address, "verify download", and then "Firmware Upgrade". + - Once complete, in the Release_Notes.html, find the memory address that corresponds to your device for the + stm32wb5x_BLE_Stack_full_fw.bin file. It should not be the same memory address. + - Select that file, the memory address, "verify download", and then "Firmware Upgrade". + - Select "Start Wireless Stack". + - Disconnect from the device. + - In the examples folder for stm32wb, modify the memory.x file to match your target device. + - Run this example. + + Note: extended stack versions are not supported at this time. Do not attempt to install a stack with "extended" in the name. + */ + + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let config = Config::default(); + let mut mbox = TlMbox::init(p.IPCC, Irqs, config); + + let sys_event = mbox.sys_subsystem.read().await; + info!("sys event: {}", sys_event.payload()); + + let _ = mbox.sys_subsystem.shci_c2_ble_init(Default::default()).await; + + info!("resetting BLE..."); + mbox.ble_subsystem.reset().await; + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + info!("config public address..."); + mbox.ble_subsystem + .write_config_data(&ConfigData::public_address(get_bd_addr()).build()) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + info!("config random address..."); + mbox.ble_subsystem + .write_config_data(&ConfigData::random_address(get_random_addr()).build()) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + info!("config identity root..."); + mbox.ble_subsystem + .write_config_data(&ConfigData::identity_root(&get_irk()).build()) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + info!("config encryption root..."); + mbox.ble_subsystem + .write_config_data(&ConfigData::encryption_root(&get_erk()).build()) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + info!("config tx power level..."); + mbox.ble_subsystem.set_tx_power_level(PowerLevel::ZerodBm).await; + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + info!("GATT init..."); + mbox.ble_subsystem.init_gatt().await; + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + info!("GAP init..."); + mbox.ble_subsystem + .init_gap(Role::PERIPHERAL, false, BLE_GAP_DEVICE_NAME_LENGTH) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + // info!("set scan response..."); + // mbox.ble_subsystem.le_set_scan_response_data(&[]).await.unwrap(); + // let response = mbox.ble_subsystem.read().await.unwrap(); + // defmt::info!("{}", response); + + info!("set discoverable..."); + mbox.ble_subsystem + .set_discoverable(&DiscoverableParameters { + advertising_type: AdvertisingType::NonConnectableUndirected, + advertising_interval: Some((Duration::from_millis(250), Duration::from_millis(250))), + address_type: OwnAddressType::Public, + filter_policy: AdvertisingFilterPolicy::AllowConnectionAndScan, + local_name: None, + advertising_data: &[], + conn_interval: (None, None), + }) + .await + .unwrap(); + + let response = mbox.ble_subsystem.read().await; + defmt::info!("{}", response); + + // remove some advertisement to decrease the packet size + info!("delete tx power ad type..."); + mbox.ble_subsystem + .delete_ad_type(AdvertisingDataType::TxPowerLevel) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + info!("delete conn interval ad type..."); + mbox.ble_subsystem + .delete_ad_type(AdvertisingDataType::PeripheralConnectionInterval) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + info!("update advertising data..."); + mbox.ble_subsystem + .update_advertising_data(&eddystone_advertising_data()) + .await + .unwrap(); + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + info!("update advertising data type..."); + mbox.ble_subsystem + .update_advertising_data(&[3, AdvertisingDataType::UuidCompleteList16 as u8, 0xaa, 0xfe]) + .await + .unwrap(); + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + info!("update advertising data flags..."); + mbox.ble_subsystem + .update_advertising_data(&[ + 2, + AdvertisingDataType::Flags as u8, + (0x02 | 0x04) as u8, // BLE general discoverable, without BR/EDR support + ]) + .await + .unwrap(); + let response = mbox.ble_subsystem.read().await.unwrap(); + defmt::info!("{}", response); + + cortex_m::asm::wfi(); +} + +fn get_bd_addr() -> BdAddr { + let mut bytes = [0u8; 6]; + + let lhci_info = LhciC1DeviceInformationCcrp::new(); + bytes[0] = (lhci_info.uid64 & 0xff) as u8; + bytes[1] = ((lhci_info.uid64 >> 8) & 0xff) as u8; + bytes[2] = ((lhci_info.uid64 >> 16) & 0xff) as u8; + bytes[3] = lhci_info.device_type_id; + bytes[4] = (lhci_info.st_company_id & 0xff) as u8; + bytes[5] = (lhci_info.st_company_id >> 8 & 0xff) as u8; + + BdAddr(bytes) +} + +fn get_random_addr() -> BdAddr { + let mut bytes = [0u8; 6]; + + let lhci_info = LhciC1DeviceInformationCcrp::new(); + bytes[0] = (lhci_info.uid64 & 0xff) as u8; + bytes[1] = ((lhci_info.uid64 >> 8) & 0xff) as u8; + bytes[2] = ((lhci_info.uid64 >> 16) & 0xff) as u8; + bytes[3] = 0; + bytes[4] = 0x6E; + bytes[5] = 0xED; + + BdAddr(bytes) +} + +const BLE_CFG_IRK: [u8; 16] = [ + 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, +]; +const BLE_CFG_ERK: [u8; 16] = [ + 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21, 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21, +]; + +fn get_irk() -> EncryptionKey { + EncryptionKey(BLE_CFG_IRK) +} + +fn get_erk() -> EncryptionKey { + EncryptionKey(BLE_CFG_ERK) +} + +fn eddystone_advertising_data() -> [u8; 24] { + const EDDYSTONE_URL: &[u8] = b"www.rust-lang.com"; + + let mut service_data = [0u8; 24]; + let url_len = EDDYSTONE_URL.len(); + + service_data[0] = 6 + url_len as u8; + service_data[1] = AdvertisingDataType::ServiceData as u8; + + // 16-bit eddystone uuid + service_data[2] = 0xaa; + service_data[3] = 0xFE; + + service_data[4] = 0x10; // URL frame type + service_data[5] = 22_i8 as u8; // calibrated TX power at 0m + service_data[6] = 0x03; // eddystone url prefix = https + + service_data[7..(7 + url_len)].copy_from_slice(EDDYSTONE_URL); + + service_data +} diff --git a/examples/stm32wb/src/bin/gatt_server.rs b/examples/stm32wb/src/bin/gatt_server.rs new file mode 100644 index 000000000..0f6419d45 --- /dev/null +++ b/examples/stm32wb/src/bin/gatt_server.rs @@ -0,0 +1,397 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use core::time::Duration; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::bind_interrupts; +use embassy_stm32::ipcc::{Config, ReceiveInterruptHandler, TransmitInterruptHandler}; +use embassy_stm32_wpan::hci::event::command::{CommandComplete, ReturnParameters}; +use embassy_stm32_wpan::hci::host::uart::{Packet, UartHci}; +use embassy_stm32_wpan::hci::host::{AdvertisingFilterPolicy, EncryptionKey, HostHci, OwnAddressType}; +use embassy_stm32_wpan::hci::types::AdvertisingType; +use embassy_stm32_wpan::hci::vendor::stm32wb::command::gap::{ + AddressType, AuthenticationRequirements, DiscoverableParameters, GapCommands, IoCapability, LocalName, Pin, Role, + SecureConnectionSupport, +}; +use embassy_stm32_wpan::hci::vendor::stm32wb::command::gatt::{ + AddCharacteristicParameters, AddServiceParameters, CharacteristicEvent, CharacteristicPermission, + CharacteristicProperty, EncryptionKeySize, GattCommands, ServiceType, UpdateCharacteristicValueParameters, Uuid, + WriteResponseParameters, +}; +use embassy_stm32_wpan::hci::vendor::stm32wb::command::hal::{ConfigData, HalCommands, PowerLevel}; +use embassy_stm32_wpan::hci::vendor::stm32wb::event::{self, AttributeHandle, Stm32Wb5xEvent}; +use embassy_stm32_wpan::hci::{BdAddr, Event}; +use embassy_stm32_wpan::lhci::LhciC1DeviceInformationCcrp; +use embassy_stm32_wpan::sub::ble::Ble; +use embassy_stm32_wpan::TlMbox; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs{ + IPCC_C1_RX => ReceiveInterruptHandler; + IPCC_C1_TX => TransmitInterruptHandler; +}); + +const BLE_GAP_DEVICE_NAME_LENGTH: u8 = 7; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + /* + How to make this work: + + - Obtain a NUCLEO-STM32WB55 from your preferred supplier. + - Download and Install STM32CubeProgrammer. + - Download stm32wb5x_FUS_fw.bin, stm32wb5x_BLE_Stack_full_fw.bin, and Release_Notes.html from + gh:STMicroelectronics/STM32CubeWB@2234d97/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x + - Open STM32CubeProgrammer + - On the right-hand pane, click "firmware upgrade" to upgrade the st-link firmware. + - Once complete, click connect to connect to the device. + - On the left hand pane, click the RSS signal icon to open "Firmware Upgrade Services". + - In the Release_Notes.html, find the memory address that corresponds to your device for the stm32wb5x_FUS_fw.bin file + - Select that file, the memory address, "verify download", and then "Firmware Upgrade". + - Once complete, in the Release_Notes.html, find the memory address that corresponds to your device for the + stm32wb5x_BLE_Stack_full_fw.bin file. It should not be the same memory address. + - Select that file, the memory address, "verify download", and then "Firmware Upgrade". + - Select "Start Wireless Stack". + - Disconnect from the device. + - In the examples folder for stm32wb, modify the memory.x file to match your target device. + - Run this example. + + Note: extended stack versions are not supported at this time. Do not attempt to install a stack with "extended" in the name. + */ + + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let config = Config::default(); + let mut mbox = TlMbox::init(p.IPCC, Irqs, config); + + let sys_event = mbox.sys_subsystem.read().await; + info!("sys event: {}", sys_event.payload()); + + let _ = mbox.sys_subsystem.shci_c2_ble_init(Default::default()).await; + + info!("resetting BLE..."); + mbox.ble_subsystem.reset().await; + let response = mbox.ble_subsystem.read().await; + defmt::debug!("{}", response); + + info!("config public address..."); + mbox.ble_subsystem + .write_config_data(&ConfigData::public_address(get_bd_addr()).build()) + .await; + let response = mbox.ble_subsystem.read().await; + defmt::debug!("{}", response); + + info!("config random address..."); + mbox.ble_subsystem + .write_config_data(&ConfigData::random_address(get_random_addr()).build()) + .await; + let response = mbox.ble_subsystem.read().await; + defmt::debug!("{}", response); + + info!("config identity root..."); + mbox.ble_subsystem + .write_config_data(&ConfigData::identity_root(&get_irk()).build()) + .await; + let response = mbox.ble_subsystem.read().await; + defmt::debug!("{}", response); + + info!("config encryption root..."); + mbox.ble_subsystem + .write_config_data(&ConfigData::encryption_root(&get_erk()).build()) + .await; + let response = mbox.ble_subsystem.read().await; + defmt::debug!("{}", response); + + info!("config tx power level..."); + mbox.ble_subsystem.set_tx_power_level(PowerLevel::ZerodBm).await; + let response = mbox.ble_subsystem.read().await; + defmt::debug!("{}", response); + + info!("GATT init..."); + mbox.ble_subsystem.init_gatt().await; + let response = mbox.ble_subsystem.read().await; + defmt::debug!("{}", response); + + info!("GAP init..."); + mbox.ble_subsystem + .init_gap(Role::PERIPHERAL, false, BLE_GAP_DEVICE_NAME_LENGTH) + .await; + let response = mbox.ble_subsystem.read().await; + defmt::debug!("{}", response); + + info!("set IO capabilities..."); + mbox.ble_subsystem.set_io_capability(IoCapability::DisplayConfirm).await; + let response = mbox.ble_subsystem.read().await; + defmt::debug!("{}", response); + + info!("set authentication requirements..."); + mbox.ble_subsystem + .set_authentication_requirement(&AuthenticationRequirements { + bonding_required: false, + keypress_notification_support: false, + mitm_protection_required: false, + encryption_key_size_range: (8, 16), + fixed_pin: Pin::Requested, + identity_address_type: AddressType::Public, + secure_connection_support: SecureConnectionSupport::Optional, + }) + .await + .unwrap(); + let response = mbox.ble_subsystem.read().await; + defmt::debug!("{}", response); + + info!("set scan response data..."); + mbox.ble_subsystem.le_set_scan_response_data(b"TXTX").await.unwrap(); + let response = mbox.ble_subsystem.read().await; + defmt::debug!("{}", response); + + info!("set scan response data..."); + mbox.ble_subsystem.le_set_scan_response_data(b"TXTX").await.unwrap(); + let response = mbox.ble_subsystem.read().await; + defmt::debug!("{}", response); + + defmt::info!("initializing services and characteristics..."); + let mut ble_context = init_gatt_services(&mut mbox.ble_subsystem).await.unwrap(); + defmt::info!("{}", ble_context); + + let discovery_params = DiscoverableParameters { + advertising_type: AdvertisingType::ConnectableUndirected, + advertising_interval: Some((Duration::from_millis(100), Duration::from_millis(100))), + address_type: OwnAddressType::Public, + filter_policy: AdvertisingFilterPolicy::AllowConnectionAndScan, + local_name: Some(LocalName::Complete(b"TXTX")), + advertising_data: &[], + conn_interval: (None, None), + }; + + info!("set discoverable..."); + mbox.ble_subsystem.set_discoverable(&discovery_params).await.unwrap(); + let response = mbox.ble_subsystem.read().await; + defmt::debug!("{}", response); + + loop { + let response = mbox.ble_subsystem.read().await; + defmt::debug!("{}", response); + + if let Ok(Packet::Event(event)) = response { + match event { + Event::LeConnectionComplete(_) => { + defmt::info!("connected"); + } + Event::DisconnectionComplete(_) => { + defmt::info!("disconnected"); + ble_context.is_subscribed = false; + mbox.ble_subsystem.set_discoverable(&discovery_params).await.unwrap(); + } + Event::Vendor(vendor_event) => match vendor_event { + Stm32Wb5xEvent::AttReadPermitRequest(read_req) => { + defmt::info!("read request received {}, allowing", read_req); + mbox.ble_subsystem.allow_read(read_req.conn_handle).await + } + Stm32Wb5xEvent::AttWritePermitRequest(write_req) => { + defmt::info!("write request received {}, allowing", write_req); + mbox.ble_subsystem + .write_response(&WriteResponseParameters { + conn_handle: write_req.conn_handle, + attribute_handle: write_req.attribute_handle, + status: Ok(()), + value: write_req.value(), + }) + .await + .unwrap() + } + Stm32Wb5xEvent::GattAttributeModified(attribute) => { + defmt::info!("{}", ble_context); + if attribute.attr_handle.0 == ble_context.chars.notify.0 + 2 { + if attribute.data()[0] == 0x01 { + defmt::info!("subscribed"); + ble_context.is_subscribed = true; + } else { + defmt::info!("unsubscribed"); + ble_context.is_subscribed = false; + } + } + } + _ => {} + }, + _ => {} + } + } + } +} + +fn get_bd_addr() -> BdAddr { + let mut bytes = [0u8; 6]; + + let lhci_info = LhciC1DeviceInformationCcrp::new(); + bytes[0] = (lhci_info.uid64 & 0xff) as u8; + bytes[1] = ((lhci_info.uid64 >> 8) & 0xff) as u8; + bytes[2] = ((lhci_info.uid64 >> 16) & 0xff) as u8; + bytes[3] = lhci_info.device_type_id; + bytes[4] = (lhci_info.st_company_id & 0xff) as u8; + bytes[5] = (lhci_info.st_company_id >> 8 & 0xff) as u8; + + BdAddr(bytes) +} + +fn get_random_addr() -> BdAddr { + let mut bytes = [0u8; 6]; + + let lhci_info = LhciC1DeviceInformationCcrp::new(); + bytes[0] = (lhci_info.uid64 & 0xff) as u8; + bytes[1] = ((lhci_info.uid64 >> 8) & 0xff) as u8; + bytes[2] = ((lhci_info.uid64 >> 16) & 0xff) as u8; + bytes[3] = 0; + bytes[4] = 0x6E; + bytes[5] = 0xED; + + BdAddr(bytes) +} + +const BLE_CFG_IRK: [u8; 16] = [ + 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, +]; +const BLE_CFG_ERK: [u8; 16] = [ + 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21, 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21, +]; + +fn get_irk() -> EncryptionKey { + EncryptionKey(BLE_CFG_IRK) +} + +fn get_erk() -> EncryptionKey { + EncryptionKey(BLE_CFG_ERK) +} + +#[derive(defmt::Format)] +pub struct BleContext { + pub service_handle: AttributeHandle, + pub chars: CharHandles, + pub is_subscribed: bool, +} + +#[derive(defmt::Format)] +pub struct CharHandles { + pub read: AttributeHandle, + pub write: AttributeHandle, + pub notify: AttributeHandle, +} + +pub async fn init_gatt_services(ble_subsystem: &mut Ble) -> Result { + let service_handle = gatt_add_service(ble_subsystem, Uuid::Uuid16(0x500)).await?; + + let read = gatt_add_char( + ble_subsystem, + service_handle, + Uuid::Uuid16(0x501), + CharacteristicProperty::READ, + Some(b"Hello from embassy!"), + ) + .await?; + + let write = gatt_add_char( + ble_subsystem, + service_handle, + Uuid::Uuid16(0x502), + CharacteristicProperty::WRITE_WITHOUT_RESPONSE | CharacteristicProperty::WRITE | CharacteristicProperty::READ, + None, + ) + .await?; + + let notify = gatt_add_char( + ble_subsystem, + service_handle, + Uuid::Uuid16(0x503), + CharacteristicProperty::NOTIFY | CharacteristicProperty::READ, + None, + ) + .await?; + + Ok(BleContext { + service_handle, + is_subscribed: false, + chars: CharHandles { read, write, notify }, + }) +} + +async fn gatt_add_service(ble_subsystem: &mut Ble, uuid: Uuid) -> Result { + ble_subsystem + .add_service(&AddServiceParameters { + uuid, + service_type: ServiceType::Primary, + max_attribute_records: 8, + }) + .await; + let response = ble_subsystem.read().await; + defmt::debug!("{}", response); + + if let Ok(Packet::Event(Event::CommandComplete(CommandComplete { + return_params: + ReturnParameters::Vendor(event::command::ReturnParameters::GattAddService(event::command::GattService { + service_handle, + .. + })), + .. + }))) = response + { + Ok(service_handle) + } else { + Err(()) + } +} + +async fn gatt_add_char( + ble_subsystem: &mut Ble, + service_handle: AttributeHandle, + characteristic_uuid: Uuid, + characteristic_properties: CharacteristicProperty, + default_value: Option<&[u8]>, +) -> Result { + ble_subsystem + .add_characteristic(&AddCharacteristicParameters { + service_handle, + characteristic_uuid, + characteristic_properties, + characteristic_value_len: 32, + security_permissions: CharacteristicPermission::empty(), + gatt_event_mask: CharacteristicEvent::all(), + encryption_key_size: EncryptionKeySize::with_value(7).unwrap(), + is_variable: true, + }) + .await; + let response = ble_subsystem.read().await; + defmt::debug!("{}", response); + + if let Ok(Packet::Event(Event::CommandComplete(CommandComplete { + return_params: + ReturnParameters::Vendor(event::command::ReturnParameters::GattAddCharacteristic( + event::command::GattCharacteristic { + characteristic_handle, .. + }, + )), + .. + }))) = response + { + if let Some(value) = default_value { + ble_subsystem + .update_characteristic_value(&UpdateCharacteristicValueParameters { + service_handle, + characteristic_handle, + offset: 0, + value, + }) + .await + .unwrap(); + + let response = ble_subsystem.read().await; + defmt::debug!("{}", response); + } + Ok(characteristic_handle) + } else { + Err(()) + } +} diff --git a/examples/stm32wb/src/bin/mac_ffd.rs b/examples/stm32wb/src/bin/mac_ffd.rs new file mode 100644 index 000000000..bc71e29aa --- /dev/null +++ b/examples/stm32wb/src/bin/mac_ffd.rs @@ -0,0 +1,206 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::bind_interrupts; +use embassy_stm32::ipcc::{Config, ReceiveInterruptHandler, TransmitInterruptHandler}; +use embassy_stm32_wpan::mac::commands::{AssociateResponse, ResetRequest, SetRequest, StartRequest}; +use embassy_stm32_wpan::mac::event::MacEvent; +use embassy_stm32_wpan::mac::typedefs::{MacChannel, MacStatus, PanId, PibId, SecurityLevel}; +use embassy_stm32_wpan::sub::mm; +use embassy_stm32_wpan::TlMbox; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs{ + IPCC_C1_RX => ReceiveInterruptHandler; + IPCC_C1_TX => TransmitInterruptHandler; +}); + +#[embassy_executor::task] +async fn run_mm_queue(memory_manager: mm::MemoryManager) { + memory_manager.run_queue().await; +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + /* + How to make this work: + + - Obtain a NUCLEO-STM32WB55 from your preferred supplier. + - Download and Install STM32CubeProgrammer. + - Download stm32wb5x_FUS_fw.bin, stm32wb5x_BLE_Stack_full_fw.bin, and Release_Notes.html from + gh:STMicroelectronics/STM32CubeWB@2234d97/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x + - Open STM32CubeProgrammer + - On the right-hand pane, click "firmware upgrade" to upgrade the st-link firmware. + - Once complete, click connect to connect to the device. + - On the left hand pane, click the RSS signal icon to open "Firmware Upgrade Services". + - In the Release_Notes.html, find the memory address that corresponds to your device for the stm32wb5x_FUS_fw.bin file + - Select that file, the memory address, "verify download", and then "Firmware Upgrade". + - Once complete, in the Release_Notes.html, find the memory address that corresponds to your device for the + stm32wb5x_BLE_Stack_full_fw.bin file. It should not be the same memory address. + - Select that file, the memory address, "verify download", and then "Firmware Upgrade". + - Select "Start Wireless Stack". + - Disconnect from the device. + - In the examples folder for stm32wb, modify the memory.x file to match your target device. + - Run this example. + + Note: extended stack versions are not supported at this time. Do not attempt to install a stack with "extended" in the name. + */ + + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let config = Config::default(); + let mbox = TlMbox::init(p.IPCC, Irqs, config); + + spawner.spawn(run_mm_queue(mbox.mm_subsystem)).unwrap(); + + let sys_event = mbox.sys_subsystem.read().await; + info!("sys event: {}", sys_event.payload()); + + core::mem::drop(sys_event); + + let result = mbox.sys_subsystem.shci_c2_mac_802_15_4_init().await; + info!("initialized mac: {}", result); + + info!("resetting"); + mbox.mac_subsystem + .send_command(&ResetRequest { + set_default_pib: true, + ..Default::default() + }) + .await + .unwrap(); + { + let evt = mbox.mac_subsystem.read().await; + defmt::info!("{:#x}", evt.mac_event()); + } + + info!("setting extended address"); + let extended_address: u64 = 0xACDE480000000001; + mbox.mac_subsystem + .send_command(&SetRequest { + pib_attribute_ptr: &extended_address as *const _ as *const u8, + pib_attribute: PibId::ExtendedAddress, + }) + .await + .unwrap(); + { + let evt = mbox.mac_subsystem.read().await; + defmt::info!("{:#x}", evt.mac_event()); + } + + info!("setting short address"); + let short_address: u16 = 0x1122; + mbox.mac_subsystem + .send_command(&SetRequest { + pib_attribute_ptr: &short_address as *const _ as *const u8, + pib_attribute: PibId::ShortAddress, + }) + .await + .unwrap(); + { + let evt = mbox.mac_subsystem.read().await; + defmt::info!("{:#x}", evt.mac_event()); + } + + info!("setting association permit"); + let association_permit: bool = true; + mbox.mac_subsystem + .send_command(&SetRequest { + pib_attribute_ptr: &association_permit as *const _ as *const u8, + pib_attribute: PibId::AssociationPermit, + }) + .await + .unwrap(); + { + let evt = mbox.mac_subsystem.read().await; + defmt::info!("{:#x}", evt.mac_event()); + } + + info!("setting TX power"); + let transmit_power: i8 = 2; + mbox.mac_subsystem + .send_command(&SetRequest { + pib_attribute_ptr: &transmit_power as *const _ as *const u8, + pib_attribute: PibId::TransmitPower, + }) + .await + .unwrap(); + { + let evt = mbox.mac_subsystem.read().await; + defmt::info!("{:#x}", evt.mac_event()); + } + + info!("starting FFD device"); + mbox.mac_subsystem + .send_command(&StartRequest { + pan_id: PanId([0x1A, 0xAA]), + channel_number: MacChannel::Channel16, + beacon_order: 0x0F, + superframe_order: 0x0F, + pan_coordinator: true, + battery_life_extension: false, + ..Default::default() + }) + .await + .unwrap(); + { + let evt = mbox.mac_subsystem.read().await; + defmt::info!("{:#x}", evt.mac_event()); + } + + info!("setting RX on when idle"); + let rx_on_while_idle: bool = true; + mbox.mac_subsystem + .send_command(&SetRequest { + pib_attribute_ptr: &rx_on_while_idle as *const _ as *const u8, + pib_attribute: PibId::RxOnWhenIdle, + }) + .await + .unwrap(); + { + let evt = mbox.mac_subsystem.read().await; + defmt::info!("{:#x}", evt.mac_event()); + } + + loop { + let evt = mbox.mac_subsystem.read().await; + if let Ok(evt) = evt.mac_event() { + defmt::info!("parsed mac event"); + defmt::info!("{:#x}", evt); + + match evt { + MacEvent::MlmeAssociateInd(association) => mbox + .mac_subsystem + .send_command(&AssociateResponse { + device_address: association.device_address, + assoc_short_address: [0x33, 0x44], + status: MacStatus::Success, + security_level: SecurityLevel::Unsecure, + ..Default::default() + }) + .await + .unwrap(), + MacEvent::McpsDataInd(data_ind) => { + let payload = data_ind.payload(); + let ref_payload = b"Hello from embassy!"; + info!("{}", payload); + + if payload == ref_payload { + info!("success"); + } else { + info!("ref payload: {}", ref_payload); + } + } + _ => { + defmt::info!("other mac event"); + } + } + } else { + defmt::info!("failed to parse mac event"); + } + } +} diff --git a/examples/stm32wb/src/bin/mac_rfd.rs b/examples/stm32wb/src/bin/mac_rfd.rs new file mode 100644 index 000000000..7cb401d89 --- /dev/null +++ b/examples/stm32wb/src/bin/mac_rfd.rs @@ -0,0 +1,186 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::bind_interrupts; +use embassy_stm32::ipcc::{Config, ReceiveInterruptHandler, TransmitInterruptHandler}; +use embassy_stm32_wpan::mac::commands::{AssociateRequest, DataRequest, GetRequest, ResetRequest, SetRequest}; +use embassy_stm32_wpan::mac::event::MacEvent; +use embassy_stm32_wpan::mac::typedefs::{ + AddressMode, Capabilities, KeyIdMode, MacAddress, MacChannel, PanId, PibId, SecurityLevel, +}; +use embassy_stm32_wpan::sub::mm; +use embassy_stm32_wpan::TlMbox; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs{ + IPCC_C1_RX => ReceiveInterruptHandler; + IPCC_C1_TX => TransmitInterruptHandler; +}); + +#[embassy_executor::task] +async fn run_mm_queue(memory_manager: mm::MemoryManager) { + memory_manager.run_queue().await; +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + /* + How to make this work: + + - Obtain a NUCLEO-STM32WB55 from your preferred supplier. + - Download and Install STM32CubeProgrammer. + - Download stm32wb5x_FUS_fw.bin, stm32wb5x_BLE_Stack_full_fw.bin, and Release_Notes.html from + gh:STMicroelectronics/STM32CubeWB@2234d97/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x + - Open STM32CubeProgrammer + - On the right-hand pane, click "firmware upgrade" to upgrade the st-link firmware. + - Once complete, click connect to connect to the device. + - On the left hand pane, click the RSS signal icon to open "Firmware Upgrade Services". + - In the Release_Notes.html, find the memory address that corresponds to your device for the stm32wb5x_FUS_fw.bin file + - Select that file, the memory address, "verify download", and then "Firmware Upgrade". + - Once complete, in the Release_Notes.html, find the memory address that corresponds to your device for the + stm32wb5x_BLE_Stack_full_fw.bin file. It should not be the same memory address. + - Select that file, the memory address, "verify download", and then "Firmware Upgrade". + - Select "Start Wireless Stack". + - Disconnect from the device. + - In the examples folder for stm32wb, modify the memory.x file to match your target device. + - Run this example. + + Note: extended stack versions are not supported at this time. Do not attempt to install a stack with "extended" in the name. + */ + + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let config = Config::default(); + let mbox = TlMbox::init(p.IPCC, Irqs, config); + + spawner.spawn(run_mm_queue(mbox.mm_subsystem)).unwrap(); + + let sys_event = mbox.sys_subsystem.read().await; + info!("sys event: {}", sys_event.payload()); + + core::mem::drop(sys_event); + + let result = mbox.sys_subsystem.shci_c2_mac_802_15_4_init().await; + info!("initialized mac: {}", result); + + info!("resetting"); + mbox.mac_subsystem + .send_command(&ResetRequest { + set_default_pib: true, + ..Default::default() + }) + .await + .unwrap(); + { + let evt = mbox.mac_subsystem.read().await; + defmt::info!("{:#x}", evt.mac_event()); + } + + info!("setting extended address"); + let extended_address: u64 = 0xACDE480000000002; + mbox.mac_subsystem + .send_command(&SetRequest { + pib_attribute_ptr: &extended_address as *const _ as *const u8, + pib_attribute: PibId::ExtendedAddress, + }) + .await + .unwrap(); + { + let evt = mbox.mac_subsystem.read().await; + defmt::info!("{:#x}", evt.mac_event()); + } + + info!("getting extended address"); + mbox.mac_subsystem + .send_command(&GetRequest { + pib_attribute: PibId::ExtendedAddress, + ..Default::default() + }) + .await + .unwrap(); + + { + let evt = mbox.mac_subsystem.read().await; + info!("{:#x}", evt.mac_event()); + + if let Ok(MacEvent::MlmeGetCnf(evt)) = evt.mac_event() { + if evt.pib_attribute_value_len == 8 { + let value = unsafe { core::ptr::read_unaligned(evt.pib_attribute_value_ptr as *const u64) }; + + info!("value {:#x}", value) + } + } + } + + info!("assocation request"); + let a = AssociateRequest { + channel_number: MacChannel::Channel16, + channel_page: 0, + coord_addr_mode: AddressMode::Short, + coord_address: MacAddress { short: [34, 17] }, + capability_information: Capabilities::ALLOCATE_ADDRESS, + coord_pan_id: PanId([0x1A, 0xAA]), + security_level: SecurityLevel::Unsecure, + key_id_mode: KeyIdMode::Implicite, + key_source: [0; 8], + key_index: 152, + }; + info!("{}", a); + mbox.mac_subsystem.send_command(&a).await.unwrap(); + let short_addr = { + let evt = mbox.mac_subsystem.read().await; + info!("{:#x}", evt.mac_event()); + + if let Ok(MacEvent::MlmeAssociateCnf(conf)) = evt.mac_event() { + conf.assoc_short_address + } else { + defmt::panic!() + } + }; + + info!("setting short address"); + mbox.mac_subsystem + .send_command(&SetRequest { + pib_attribute_ptr: &short_addr as *const _ as *const u8, + pib_attribute: PibId::ShortAddress, + }) + .await + .unwrap(); + { + let evt = mbox.mac_subsystem.read().await; + info!("{:#x}", evt.mac_event()); + } + + info!("sending data"); + let data = b"Hello from embassy!"; + mbox.mac_subsystem + .send_command( + DataRequest { + src_addr_mode: AddressMode::Short, + dst_addr_mode: AddressMode::Short, + dst_pan_id: PanId([0x1A, 0xAA]), + dst_address: MacAddress::BROADCAST, + msdu_handle: 0x02, + ack_tx: 0x00, + gts_tx: false, + security_level: SecurityLevel::Unsecure, + ..Default::default() + } + .set_buffer(data), + ) + .await + .unwrap(); + { + let evt = mbox.mac_subsystem.read().await; + info!("{:#x}", evt.mac_event()); + } + + loop { + let evt = mbox.mac_subsystem.read().await; + info!("{:#x}", evt.mac_event()); + } +} diff --git a/examples/stm32wb/src/bin/tl_mbox.rs b/examples/stm32wb/src/bin/tl_mbox.rs new file mode 100644 index 000000000..9fc4b8aac --- /dev/null +++ b/examples/stm32wb/src/bin/tl_mbox.rs @@ -0,0 +1,76 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::bind_interrupts; +use embassy_stm32::ipcc::{Config, ReceiveInterruptHandler, TransmitInterruptHandler}; +use embassy_stm32_wpan::TlMbox; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs{ + IPCC_C1_RX => ReceiveInterruptHandler; + IPCC_C1_TX => TransmitInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + /* + How to make this work: + + - Obtain a NUCLEO-STM32WB55 from your preferred supplier. + - Download and Install STM32CubeProgrammer. + - Download stm32wb5x_FUS_fw.bin, stm32wb5x_BLE_Stack_full_fw.bin, and Release_Notes.html from + gh:STMicroelectronics/STM32CubeWB@2234d97/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x + - Open STM32CubeProgrammer + - On the right-hand pane, click "firmware upgrade" to upgrade the st-link firmware. + - Once complete, click connect to connect to the device. + - On the left hand pane, click the RSS signal icon to open "Firmware Upgrade Services". + - In the Release_Notes.html, find the memory address that corresponds to your device for the stm32wb5x_FUS_fw.bin file + - Select that file, the memory address, "verify download", and then "Firmware Upgrade". + - Once complete, in the Release_Notes.html, find the memory address that corresponds to your device for the + stm32wb5x_BLE_Stack_full_fw.bin file. It should not be the same memory address. + - Select that file, the memory address, "verify download", and then "Firmware Upgrade". + - Select "Start Wireless Stack". + - Disconnect from the device. + - In the examples folder for stm32wb, modify the memory.x file to match your target device. + - Run this example. + + Note: extended stack versions are not supported at this time. Do not attempt to install a stack with "extended" in the name. + */ + + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let config = Config::default(); + let mbox = TlMbox::init(p.IPCC, Irqs, config); + + loop { + let wireless_fw_info = mbox.sys_subsystem.wireless_fw_info(); + match wireless_fw_info { + None => info!("not yet initialized"), + Some(fw_info) => { + let version_major = fw_info.version_major(); + let version_minor = fw_info.version_minor(); + let subversion = fw_info.subversion(); + + let sram2a_size = fw_info.sram2a_size(); + let sram2b_size = fw_info.sram2b_size(); + + info!( + "version {}.{}.{} - SRAM2a {} - SRAM2b {}", + version_major, version_minor, subversion, sram2a_size, sram2b_size + ); + + break; + } + } + + Timer::after(Duration::from_millis(50)).await; + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/examples/stm32wb/src/bin/tl_mbox_ble.rs b/examples/stm32wb/src/bin/tl_mbox_ble.rs new file mode 100644 index 000000000..90349422e --- /dev/null +++ b/examples/stm32wb/src/bin/tl_mbox_ble.rs @@ -0,0 +1,64 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::bind_interrupts; +use embassy_stm32::ipcc::{Config, ReceiveInterruptHandler, TransmitInterruptHandler}; +use embassy_stm32_wpan::TlMbox; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs{ + IPCC_C1_RX => ReceiveInterruptHandler; + IPCC_C1_TX => TransmitInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + /* + How to make this work: + + - Obtain a NUCLEO-STM32WB55 from your preferred supplier. + - Download and Install STM32CubeProgrammer. + - Download stm32wb5x_FUS_fw.bin, stm32wb5x_BLE_Stack_full_fw.bin, and Release_Notes.html from + gh:STMicroelectronics/STM32CubeWB@2234d97/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x + - Open STM32CubeProgrammer + - On the right-hand pane, click "firmware upgrade" to upgrade the st-link firmware. + - Once complete, click connect to connect to the device. + - On the left hand pane, click the RSS signal icon to open "Firmware Upgrade Services". + - In the Release_Notes.html, find the memory address that corresponds to your device for the stm32wb5x_FUS_fw.bin file + - Select that file, the memory address, "verify download", and then "Firmware Upgrade". + - Once complete, in the Release_Notes.html, find the memory address that corresponds to your device for the + stm32wb5x_BLE_Stack_full_fw.bin file. It should not be the same memory address. + - Select that file, the memory address, "verify download", and then "Firmware Upgrade". + - Select "Start Wireless Stack". + - Disconnect from the device. + - In the examples folder for stm32wb, modify the memory.x file to match your target device. + - Run this example. + + Note: extended stack versions are not supported at this time. Do not attempt to install a stack with "extended" in the name. + */ + + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let config = Config::default(); + let mbox = TlMbox::init(p.IPCC, Irqs, config); + + let sys_event = mbox.sys_subsystem.read().await; + info!("sys event: {}", sys_event.payload()); + + let _ = mbox.sys_subsystem.shci_c2_ble_init(Default::default()).await; + + info!("starting ble..."); + mbox.ble_subsystem.tl_write(0x0c, &[]).await; + + info!("waiting for ble..."); + let ble_event = mbox.ble_subsystem.tl_read().await; + + info!("ble event: {}", ble_event.payload()); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/examples/stm32wb/src/bin/tl_mbox_mac.rs b/examples/stm32wb/src/bin/tl_mbox_mac.rs new file mode 100644 index 000000000..5931c392b --- /dev/null +++ b/examples/stm32wb/src/bin/tl_mbox_mac.rs @@ -0,0 +1,76 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::bind_interrupts; +use embassy_stm32::ipcc::{Config, ReceiveInterruptHandler, TransmitInterruptHandler}; +use embassy_stm32_wpan::sub::mm; +use embassy_stm32_wpan::TlMbox; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs{ + IPCC_C1_RX => ReceiveInterruptHandler; + IPCC_C1_TX => TransmitInterruptHandler; +}); + +#[embassy_executor::task] +async fn run_mm_queue(memory_manager: mm::MemoryManager) { + memory_manager.run_queue().await; +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + /* + How to make this work: + + - Obtain a NUCLEO-STM32WB55 from your preferred supplier. + - Download and Install STM32CubeProgrammer. + - Download stm32wb5x_FUS_fw.bin, stm32wb5x_BLE_Stack_full_fw.bin, and Release_Notes.html from + gh:STMicroelectronics/STM32CubeWB@2234d97/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x + - Open STM32CubeProgrammer + - On the right-hand pane, click "firmware upgrade" to upgrade the st-link firmware. + - Once complete, click connect to connect to the device. + - On the left hand pane, click the RSS signal icon to open "Firmware Upgrade Services". + - In the Release_Notes.html, find the memory address that corresponds to your device for the stm32wb5x_FUS_fw.bin file + - Select that file, the memory address, "verify download", and then "Firmware Upgrade". + - Once complete, in the Release_Notes.html, find the memory address that corresponds to your device for the + stm32wb5x_BLE_Stack_full_fw.bin file. It should not be the same memory address. + - Select that file, the memory address, "verify download", and then "Firmware Upgrade". + - Select "Start Wireless Stack". + - Disconnect from the device. + - In the examples folder for stm32wb, modify the memory.x file to match your target device. + - Run this example. + + Note: extended stack versions are not supported at this time. Do not attempt to install a stack with "extended" in the name. + */ + + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let config = Config::default(); + let mbox = TlMbox::init(p.IPCC, Irqs, config); + + spawner.spawn(run_mm_queue(mbox.mm_subsystem)).unwrap(); + + let sys_event = mbox.sys_subsystem.read().await; + info!("sys event: {}", sys_event.payload()); + + core::mem::drop(sys_event); + + let result = mbox.sys_subsystem.shci_c2_mac_802_15_4_init().await; + info!("initialized mac: {}", result); + + // + // info!("starting ble..."); + // mbox.ble_subsystem.t_write(0x0c, &[]).await; + // + // info!("waiting for ble..."); + // let ble_event = mbox.ble_subsystem.tl_read().await; + // + // info!("ble event: {}", ble_event.payload()); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/examples/stm32wl/.cargo/config.toml b/examples/stm32wl/.cargo/config.toml index e395d75b4..ee416fcbc 100644 --- a/examples/stm32wl/.cargo/config.toml +++ b/examples/stm32wl/.cargo/config.toml @@ -1,9 +1,9 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -# replace your chip as listed in `probe-run --list-chips` -runner = "probe-run --chip STM32WLE5JCIx" +# replace your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32WLE5JCIx" [build] -target = "thumbv7em-none-eabihf" +target = "thumbv7em-none-eabi" [env] DEFMT_LOG = "trace" diff --git a/examples/stm32wl/Cargo.toml b/examples/stm32wl/Cargo.toml index 5f6679f4b..e2c66f456 100644 --- a/examples/stm32wl/Cargo.toml +++ b/examples/stm32wl/Cargo.toml @@ -2,24 +2,30 @@ edition = "2021" name = "embassy-stm32wl-examples" version = "0.1.0" +license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } -embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-32768hz"] } -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "stm32wl55jc-cm4", "time-driver-any", "memory-x", "subghz", "unstable-pac", "exti"] } +embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["nightly", "unstable-traits", "defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "unstable-traits", "defmt", "stm32wl55jc-cm4", "time-driver-any", "memory-x", "unstable-pac", "exti", "chrono"] } +embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" } embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["stm32wl", "time", "defmt"] } - -lorawan-device = { version = "0.7.1", default-features = false, features = ["async"] } -lorawan = { version = "0.7.1", default-features = false, features = ["default-crypto"] } +lora-phy = { version = "1" } +lorawan-device = { version = "0.10.0", default-features = false, features = ["async", "external-lora-phy"] } +lorawan = { version = "0.7.3", default-features = false, features = ["default-crypto"] } defmt = "0.3" -defmt-rtt = "0.3" +defmt-rtt = "0.4" -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" embedded-storage = "0.3.0" panic-probe = { version = "0.3", features = ["print-defmt"] } futures = { version = "0.3.17", default-features = false, features = ["async-await"] } heapless = { version = "0.7.5", default-features = false } +chrono = { version = "^0.4", default-features = false } + +[patch.crates-io] +lora-phy = { git = "https://github.com/embassy-rs/lora-phy", rev = "ad289428fd44b02788e2fa2116445cc8f640a265" } diff --git a/examples/stm32wl/src/bin/flash.rs b/examples/stm32wl/src/bin/flash.rs index eb7489760..5e52d49ec 100644 --- a/examples/stm32wl/src/bin/flash.rs +++ b/examples/stm32wl/src/bin/flash.rs @@ -5,7 +5,6 @@ use defmt::{info, unwrap}; use embassy_executor::Spawner; use embassy_stm32::flash::Flash; -use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] @@ -15,27 +14,27 @@ async fn main(_spawner: Spawner) { const ADDR: u32 = 0x36000; - let mut f = Flash::unlock(p.FLASH); + let mut f = Flash::new_blocking(p.FLASH).into_blocking_regions().bank1_region; info!("Reading..."); let mut buf = [0u8; 8]; - unwrap!(f.read(ADDR, &mut buf)); + unwrap!(f.blocking_read(ADDR, &mut buf)); info!("Read: {=[u8]:x}", buf); info!("Erasing..."); - unwrap!(f.erase(ADDR, ADDR + 2048)); + unwrap!(f.blocking_erase(ADDR, ADDR + 2048)); info!("Reading..."); let mut buf = [0u8; 8]; - unwrap!(f.read(ADDR, &mut buf)); + unwrap!(f.blocking_read(ADDR, &mut buf)); info!("Read: {=[u8]:x}", buf); info!("Writing..."); - unwrap!(f.write(ADDR, &[1, 2, 3, 4, 5, 6, 7, 8])); + unwrap!(f.blocking_write(ADDR, &[1, 2, 3, 4, 5, 6, 7, 8])); info!("Reading..."); let mut buf = [0u8; 8]; - unwrap!(f.read(ADDR, &mut buf)); + unwrap!(f.blocking_read(ADDR, &mut buf)); info!("Read: {=[u8]:x}", buf); assert_eq!(&buf[..], &[1, 2, 3, 4, 5, 6, 7, 8]); } diff --git a/examples/stm32wl/src/bin/lora_lorawan.rs b/examples/stm32wl/src/bin/lora_lorawan.rs new file mode 100644 index 000000000..805d21418 --- /dev/null +++ b/examples/stm32wl/src/bin/lora_lorawan.rs @@ -0,0 +1,80 @@ +//! This example runs on a STM32WL board, which has a builtin Semtech Sx1262 radio. +//! It demonstrates LoRaWAN join functionality. +#![no_std] +#![no_main] +#![macro_use] +#![feature(type_alias_impl_trait, async_fn_in_trait)] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_lora::iv::{InterruptHandler, Stm32wlInterfaceVariant}; +use embassy_lora::LoraTimer; +use embassy_stm32::gpio::{Level, Output, Pin, Speed}; +use embassy_stm32::rng::Rng; +use embassy_stm32::spi::Spi; +use embassy_stm32::{bind_interrupts, pac}; +use embassy_time::Delay; +use lora_phy::mod_params::*; +use lora_phy::sx1261_2::SX1261_2; +use lora_phy::LoRa; +use lorawan::default_crypto::DefaultFactory as Crypto; +use lorawan_device::async_device::lora_radio::LoRaRadio; +use lorawan_device::async_device::{region, Device, JoinMode}; +use {defmt_rtt as _, panic_probe as _}; + +const LORAWAN_REGION: region::Region = region::Region::EU868; // warning: set this appropriately for the region + +bind_interrupts!(struct Irqs{ + SUBGHZ_RADIO => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = embassy_stm32::Config::default(); + config.rcc.mux = embassy_stm32::rcc::ClockSrc::HSE32; + config.rcc.enable_lsi = true; // enable RNG + let p = embassy_stm32::init(config); + + pac::RCC.ccipr().modify(|w| w.set_rngsel(0b01)); + + let spi = Spi::new_subghz(p.SUBGHZSPI, p.DMA1_CH1, p.DMA1_CH2); + + // Set CTRL1 and CTRL3 for high-power transmission, while CTRL2 acts as an RF switch between tx and rx + let _ctrl1 = Output::new(p.PC4.degrade(), Level::Low, Speed::High); + let ctrl2 = Output::new(p.PC5.degrade(), Level::High, Speed::High); + let _ctrl3 = Output::new(p.PC3.degrade(), Level::High, Speed::High); + let iv = Stm32wlInterfaceVariant::new(Irqs, None, Some(ctrl2)).unwrap(); + + let mut delay = Delay; + + let lora = { + match LoRa::new(SX1261_2::new(BoardType::Stm32wlSx1262, spi, iv), true, &mut delay).await { + Ok(l) => l, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + let radio = LoRaRadio::new(lora); + let region: region::Configuration = region::Configuration::new(LORAWAN_REGION); + let mut device: Device<_, Crypto, _, _> = Device::new(region, radio, LoraTimer::new(), Rng::new(p.RNG)); + + defmt::info!("Joining LoRaWAN network"); + + // TODO: Adjust the EUI and Keys according to your network credentials + match device + .join(&JoinMode::OTAA { + deveui: [0, 0, 0, 0, 0, 0, 0, 0], + appeui: [0, 0, 0, 0, 0, 0, 0, 0], + appkey: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + }) + .await + { + Ok(()) => defmt::info!("LoRaWAN network joined"), + Err(err) => { + info!("Radio error = {}", err); + return; + } + }; +} diff --git a/examples/stm32wl/src/bin/lora_p2p_receive.rs b/examples/stm32wl/src/bin/lora_p2p_receive.rs new file mode 100644 index 000000000..d3f051b1c --- /dev/null +++ b/examples/stm32wl/src/bin/lora_p2p_receive.rs @@ -0,0 +1,117 @@ +//! This example runs on the STM32WL board, which has a builtin Semtech Sx1262 radio. +//! It demonstrates LORA P2P receive functionality in conjunction with the lora_p2p_send example. +#![no_std] +#![no_main] +#![macro_use] +#![feature(type_alias_impl_trait, async_fn_in_trait)] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_lora::iv::{InterruptHandler, Stm32wlInterfaceVariant}; +use embassy_stm32::bind_interrupts; +use embassy_stm32::gpio::{Level, Output, Pin, Speed}; +use embassy_stm32::spi::Spi; +use embassy_time::{Delay, Duration, Timer}; +use lora_phy::mod_params::*; +use lora_phy::sx1261_2::SX1261_2; +use lora_phy::LoRa; +use {defmt_rtt as _, panic_probe as _}; + +const LORA_FREQUENCY_IN_HZ: u32 = 903_900_000; // warning: set this appropriately for the region + +bind_interrupts!(struct Irqs{ + SUBGHZ_RADIO => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = embassy_stm32::Config::default(); + config.rcc.mux = embassy_stm32::rcc::ClockSrc::HSE32; + let p = embassy_stm32::init(config); + + let spi = Spi::new_subghz(p.SUBGHZSPI, p.DMA1_CH1, p.DMA1_CH2); + + // Set CTRL1 and CTRL3 for high-power transmission, while CTRL2 acts as an RF switch between tx and rx + let _ctrl1 = Output::new(p.PC4.degrade(), Level::Low, Speed::High); + let ctrl2 = Output::new(p.PC5.degrade(), Level::High, Speed::High); + let _ctrl3 = Output::new(p.PC3.degrade(), Level::High, Speed::High); + let iv = Stm32wlInterfaceVariant::new(Irqs, None, Some(ctrl2)).unwrap(); + + let mut delay = Delay; + + let mut lora = { + match LoRa::new(SX1261_2::new(BoardType::Stm32wlSx1262, spi, iv), false, &mut delay).await { + Ok(l) => l, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + let mut debug_indicator = Output::new(p.PB9, Level::Low, Speed::Low); + let mut start_indicator = Output::new(p.PB15, Level::Low, Speed::Low); + + start_indicator.set_high(); + Timer::after(Duration::from_secs(5)).await; + start_indicator.set_low(); + + let mut receiving_buffer = [00u8; 100]; + + let mdltn_params = { + match lora.create_modulation_params( + SpreadingFactor::_10, + Bandwidth::_250KHz, + CodingRate::_4_8, + LORA_FREQUENCY_IN_HZ, + ) { + Ok(mp) => mp, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + let rx_pkt_params = { + match lora.create_rx_packet_params(4, false, receiving_buffer.len() as u8, true, false, &mdltn_params) { + Ok(pp) => pp, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + match lora + .prepare_for_rx(&mdltn_params, &rx_pkt_params, None, true, false, 0, 0x00ffffffu32) + .await + { + Ok(()) => {} + Err(err) => { + info!("Radio error = {}", err); + return; + } + }; + + loop { + receiving_buffer = [00u8; 100]; + match lora.rx(&rx_pkt_params, &mut receiving_buffer).await { + Ok((received_len, _rx_pkt_status)) => { + if (received_len == 3) + && (receiving_buffer[0] == 0x01u8) + && (receiving_buffer[1] == 0x02u8) + && (receiving_buffer[2] == 0x03u8) + { + info!("rx successful"); + debug_indicator.set_high(); + Timer::after(Duration::from_secs(5)).await; + debug_indicator.set_low(); + } else { + info!("rx unknown packet"); + } + } + Err(err) => info!("rx unsuccessful = {}", err), + } + } +} diff --git a/examples/stm32wl/src/bin/lora_p2p_send.rs b/examples/stm32wl/src/bin/lora_p2p_send.rs new file mode 100644 index 000000000..fc5205c85 --- /dev/null +++ b/examples/stm32wl/src/bin/lora_p2p_send.rs @@ -0,0 +1,100 @@ +//! This example runs on a STM32WL board, which has a builtin Semtech Sx1262 radio. +//! It demonstrates LORA P2P send functionality. +#![no_std] +#![no_main] +#![macro_use] +#![feature(type_alias_impl_trait, async_fn_in_trait)] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_lora::iv::{InterruptHandler, Stm32wlInterfaceVariant}; +use embassy_stm32::bind_interrupts; +use embassy_stm32::gpio::{Level, Output, Pin, Speed}; +use embassy_stm32::spi::Spi; +use embassy_time::Delay; +use lora_phy::mod_params::*; +use lora_phy::sx1261_2::SX1261_2; +use lora_phy::LoRa; +use {defmt_rtt as _, panic_probe as _}; + +const LORA_FREQUENCY_IN_HZ: u32 = 903_900_000; // warning: set this appropriately for the region + +bind_interrupts!(struct Irqs{ + SUBGHZ_RADIO => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = embassy_stm32::Config::default(); + config.rcc.mux = embassy_stm32::rcc::ClockSrc::HSE32; + let p = embassy_stm32::init(config); + + let spi = Spi::new_subghz(p.SUBGHZSPI, p.DMA1_CH1, p.DMA1_CH2); + + // Set CTRL1 and CTRL3 for high-power transmission, while CTRL2 acts as an RF switch between tx and rx + let _ctrl1 = Output::new(p.PC4.degrade(), Level::Low, Speed::High); + let ctrl2 = Output::new(p.PC5.degrade(), Level::High, Speed::High); + let _ctrl3 = Output::new(p.PC3.degrade(), Level::High, Speed::High); + let iv = Stm32wlInterfaceVariant::new(Irqs, None, Some(ctrl2)).unwrap(); + + let mut delay = Delay; + + let mut lora = { + match LoRa::new(SX1261_2::new(BoardType::Stm32wlSx1262, spi, iv), false, &mut delay).await { + Ok(l) => l, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + let mdltn_params = { + match lora.create_modulation_params( + SpreadingFactor::_10, + Bandwidth::_250KHz, + CodingRate::_4_8, + LORA_FREQUENCY_IN_HZ, + ) { + Ok(mp) => mp, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + let mut tx_pkt_params = { + match lora.create_tx_packet_params(4, false, true, false, &mdltn_params) { + Ok(pp) => pp, + Err(err) => { + info!("Radio error = {}", err); + return; + } + } + }; + + match lora.prepare_for_tx(&mdltn_params, 20, false).await { + Ok(()) => {} + Err(err) => { + info!("Radio error = {}", err); + return; + } + }; + + let buffer = [0x01u8, 0x02u8, 0x03u8]; + match lora.tx(&mdltn_params, &mut tx_pkt_params, &buffer, 0xffffff).await { + Ok(()) => { + info!("TX DONE"); + } + Err(err) => { + info!("Radio error = {}", err); + return; + } + }; + + match lora.sleep(&mut delay).await { + Ok(()) => info!("Sleep successful"), + Err(err) => info!("Sleep unsuccessful = {}", err), + } +} diff --git a/examples/stm32wl/src/bin/lorawan.rs b/examples/stm32wl/src/bin/lorawan.rs deleted file mode 100644 index 7e8a8946d..000000000 --- a/examples/stm32wl/src/bin/lorawan.rs +++ /dev/null @@ -1,78 +0,0 @@ -#![no_std] -#![no_main] -#![macro_use] -#![allow(dead_code)] -#![feature(generic_associated_types)] -#![feature(type_alias_impl_trait)] - -use embassy_executor::Spawner; -use embassy_lora::stm32wl::*; -use embassy_lora::LoraTimer; -use embassy_stm32::dma::NoDma; -use embassy_stm32::gpio::{Level, Output, Pin, Speed}; -use embassy_stm32::rng::Rng; -use embassy_stm32::subghz::*; -use embassy_stm32::{interrupt, pac}; -use lorawan::default_crypto::DefaultFactory as Crypto; -use lorawan_device::async_device::{region, Device, JoinMode}; -use {defmt_rtt as _, panic_probe as _}; - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - let mut config = embassy_stm32::Config::default(); - config.rcc.mux = embassy_stm32::rcc::ClockSrc::HSI16; - config.rcc.enable_lsi = true; - let p = embassy_stm32::init(config); - - unsafe { pac::RCC.ccipr().modify(|w| w.set_rngsel(0b01)) } - - let ctrl1 = Output::new(p.PC3.degrade(), Level::High, Speed::High); - let ctrl2 = Output::new(p.PC4.degrade(), Level::High, Speed::High); - let ctrl3 = Output::new(p.PC5.degrade(), Level::High, Speed::High); - let rfs = RadioSwitch::new(ctrl1, ctrl2, ctrl3); - - let radio = SubGhz::new(p.SUBGHZSPI, p.PA5, p.PA7, p.PA6, NoDma, NoDma); - - let irq = interrupt::take!(SUBGHZ_RADIO); - static mut RADIO_STATE: SubGhzState<'static> = SubGhzState::new(); - let radio = unsafe { SubGhzRadio::new(&mut RADIO_STATE, radio, rfs, irq) }; - - let mut region: region::Configuration = region::EU868::default().into(); - - // NOTE: This is specific for TTN, as they have a special RX1 delay - region.set_receive_delay1(5000); - - let mut device: Device<_, Crypto, _, _> = Device::new(region, radio, LoraTimer, Rng::new(p.RNG)); - - // Depending on network, this might be part of JOIN - device.set_datarate(region::DR::_0); // SF12 - - // device.set_datarate(region::DR::_1); // SF11 - // device.set_datarate(region::DR::_2); // SF10 - // device.set_datarate(region::DR::_3); // SF9 - // device.set_datarate(region::DR::_4); // SF8 - // device.set_datarate(region::DR::_5); // SF7 - - defmt::info!("Joining LoRaWAN network"); - - // TODO: Adjust the EUI and Keys according to your network credentials - device - .join(&JoinMode::OTAA { - deveui: [0, 0, 0, 0, 0, 0, 0, 0], - appeui: [0, 0, 0, 0, 0, 0, 0, 0], - appkey: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - }) - .await - .ok() - .unwrap(); - defmt::info!("LoRaWAN network joined"); - - let mut rx: [u8; 255] = [0; 255]; - defmt::info!("Sending 'PING'"); - let len = device.send_recv(b"PING", &mut rx[..], 1, true).await.ok().unwrap(); - if len > 0 { - defmt::info!("Message sent, received downlink: {:?}", &rx[..len]); - } else { - defmt::info!("Message sent!"); - } -} diff --git a/examples/stm32wl/src/bin/random.rs b/examples/stm32wl/src/bin/random.rs new file mode 100644 index 000000000..d8562fca5 --- /dev/null +++ b/examples/stm32wl/src/bin/random.rs @@ -0,0 +1,31 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::pac; +use embassy_stm32::rng::Rng; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = embassy_stm32::Config::default(); + config.rcc.mux = embassy_stm32::rcc::ClockSrc::HSE32; + config.rcc.enable_lsi = true; //Needed for RNG to work + + let p = embassy_stm32::init(config); + pac::RCC.ccipr().modify(|w| { + w.set_rngsel(0b01); + }); + + info!("Hello World!"); + + let mut rng = Rng::new(p.RNG); + + let mut buf = [0u8; 16]; + unwrap!(rng.async_fill_bytes(&mut buf).await); + info!("random bytes: {:02x}", buf); + + loop {} +} diff --git a/examples/stm32wl/src/bin/rtc.rs b/examples/stm32wl/src/bin/rtc.rs new file mode 100644 index 000000000..e11825499 --- /dev/null +++ b/examples/stm32wl/src/bin/rtc.rs @@ -0,0 +1,43 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use chrono::{NaiveDate, NaiveDateTime}; +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::rcc::{self, ClockSrc}; +use embassy_stm32::rtc::{Rtc, RtcConfig}; +use embassy_stm32::Config; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = { + let mut config = Config::default(); + config.rcc.mux = ClockSrc::HSE32; + config.rcc.rtc_mux = rcc::RtcClockSource::LSE32; + config.rcc.enable_rtc_apb = true; + embassy_stm32::init(config) + }; + info!("Hello World!"); + + let now = NaiveDate::from_ymd_opt(2020, 5, 15) + .unwrap() + .and_hms_opt(10, 30, 15) + .unwrap(); + + let mut rtc = Rtc::new( + p.RTC, + RtcConfig::default().clock_config(embassy_stm32::rtc::RtcClockSource::LSE), + ); + info!("Got RTC! {:?}", now.timestamp()); + + rtc.set_datetime(now.into()).expect("datetime not set"); + + // In reality the delay would be much longer + Timer::after(Duration::from_millis(20000)).await; + + let then: NaiveDateTime = rtc.now().unwrap().into(); + info!("Got RTC! {:?}", then.timestamp()); +} diff --git a/examples/stm32wl/src/bin/subghz.rs b/examples/stm32wl/src/bin/subghz.rs deleted file mode 100644 index c5e9bb597..000000000 --- a/examples/stm32wl/src/bin/subghz.rs +++ /dev/null @@ -1,119 +0,0 @@ -#![no_std] -#![no_main] -#![macro_use] -#![allow(dead_code)] -#![feature(generic_associated_types)] -#![feature(type_alias_impl_trait)] - -use defmt::*; -use embassy_executor::Spawner; -use embassy_stm32::dma::NoDma; -use embassy_stm32::exti::ExtiInput; -use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; -use embassy_stm32::interrupt; -use embassy_stm32::interrupt::{Interrupt, InterruptExt}; -use embassy_stm32::subghz::*; -use embassy_sync::signal::Signal; -use {defmt_rtt as _, panic_probe as _}; - -const PING_DATA: &str = "PING"; -const DATA_LEN: u8 = PING_DATA.len() as u8; -const PING_DATA_BYTES: &[u8] = PING_DATA.as_bytes(); -const PREAMBLE_LEN: u16 = 5 * 8; - -const RF_FREQ: RfFreq = RfFreq::from_frequency(867_500_000); - -const SYNC_WORD: [u8; 8] = [0x79, 0x80, 0x0C, 0xC0, 0x29, 0x95, 0xF8, 0x4A]; -const SYNC_WORD_LEN: u8 = SYNC_WORD.len() as u8; -const SYNC_WORD_LEN_BITS: u8 = SYNC_WORD_LEN * 8; - -const TX_BUF_OFFSET: u8 = 128; -const RX_BUF_OFFSET: u8 = 0; -const LORA_PACKET_PARAMS: LoRaPacketParams = LoRaPacketParams::new() - .set_crc_en(true) - .set_preamble_len(PREAMBLE_LEN) - .set_payload_len(DATA_LEN) - .set_invert_iq(false) - .set_header_type(HeaderType::Fixed); - -const LORA_MOD_PARAMS: LoRaModParams = LoRaModParams::new() - .set_bw(LoRaBandwidth::Bw125) - .set_cr(CodingRate::Cr45) - .set_ldro_en(true) - .set_sf(SpreadingFactor::Sf7); - -// configuration for +10 dBm output power -// see table 35 "PA optimal setting and operating modes" -const PA_CONFIG: PaConfig = PaConfig::new().set_pa_duty_cycle(0x1).set_hp_max(0x0).set_pa(PaSel::Lp); - -const TCXO_MODE: TcxoMode = TcxoMode::new() - .set_txco_trim(TcxoTrim::Volts1pt7) - .set_timeout(Timeout::from_duration_sat(core::time::Duration::from_millis(10))); - -const TX_PARAMS: TxParams = TxParams::new().set_power(0x0D).set_ramp_time(RampTime::Micros40); - -#[embassy_executor::main] -async fn main(_spawner: Spawner) { - let mut config = embassy_stm32::Config::default(); - config.rcc.mux = embassy_stm32::rcc::ClockSrc::HSE32; - let p = embassy_stm32::init(config); - - let mut led1 = Output::new(p.PB15, Level::High, Speed::Low); - let mut led2 = Output::new(p.PB9, Level::Low, Speed::Low); - let mut led3 = Output::new(p.PB11, Level::Low, Speed::Low); - - let button = Input::new(p.PA0, Pull::Up); - let mut pin = ExtiInput::new(button, p.EXTI0); - - static IRQ_SIGNAL: Signal<()> = Signal::new(); - let radio_irq = interrupt::take!(SUBGHZ_RADIO); - radio_irq.set_handler(|_| { - IRQ_SIGNAL.signal(()); - unsafe { interrupt::SUBGHZ_RADIO::steal() }.disable(); - }); - - let mut radio = SubGhz::new(p.SUBGHZSPI, p.PA5, p.PA7, p.PA6, NoDma, NoDma); - - defmt::info!("Radio ready for use"); - - led1.set_low(); - - led2.set_high(); - - unwrap!(radio.set_standby(StandbyClk::Rc)); - unwrap!(radio.set_tcxo_mode(&TCXO_MODE)); - unwrap!(radio.set_standby(StandbyClk::Hse)); - unwrap!(radio.set_regulator_mode(RegMode::Ldo)); - unwrap!(radio.set_buffer_base_address(TX_BUF_OFFSET, RX_BUF_OFFSET)); - unwrap!(radio.set_pa_config(&PA_CONFIG)); - unwrap!(radio.set_pa_ocp(Ocp::Max60m)); - unwrap!(radio.set_tx_params(&TX_PARAMS)); - unwrap!(radio.set_packet_type(PacketType::LoRa)); - unwrap!(radio.set_lora_sync_word(LoRaSyncWord::Public)); - unwrap!(radio.set_lora_mod_params(&LORA_MOD_PARAMS)); - unwrap!(radio.set_lora_packet_params(&LORA_PACKET_PARAMS)); - unwrap!(radio.calibrate_image(CalibrateImage::ISM_863_870)); - unwrap!(radio.set_rf_frequency(&RF_FREQ)); - - defmt::info!("Status: {:?}", unwrap!(radio.status())); - - led2.set_low(); - - loop { - pin.wait_for_rising_edge().await; - led3.set_high(); - unwrap!(radio.set_irq_cfg(&CfgIrq::new().irq_enable_all(Irq::TxDone))); - unwrap!(radio.write_buffer(TX_BUF_OFFSET, PING_DATA_BYTES)); - unwrap!(radio.set_tx(Timeout::DISABLED)); - - radio_irq.enable(); - IRQ_SIGNAL.wait().await; - - let (_, irq_status) = unwrap!(radio.irq_status()); - if irq_status & Irq::TxDone.mask() != 0 { - defmt::info!("TX done"); - } - unwrap!(radio.clear_irq_status(irq_status)); - led3.set_low(); - } -} diff --git a/examples/stm32wl/src/bin/uart_async.rs b/examples/stm32wl/src/bin/uart_async.rs new file mode 100644 index 000000000..07b0f9d2c --- /dev/null +++ b/examples/stm32wl/src/bin/uart_async.rs @@ -0,0 +1,63 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::usart::{Config, InterruptHandler, Uart}; +use embassy_stm32::{bind_interrupts, peripherals}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs{ + USART1 => InterruptHandler; + LPUART1 => InterruptHandler; +}); + +/* +Pass Incoming data from LPUART1 to USART1 +Example is written for the LoRa-E5 mini v1.0, +but can be surely changed for your needs. +*/ +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = embassy_stm32::Config::default(); + config.rcc.mux = embassy_stm32::rcc::ClockSrc::HSE32; + let p = embassy_stm32::init(config); + + defmt::info!("Starting system"); + + let mut config1 = Config::default(); + config1.baudrate = 9600; + + let mut config2 = Config::default(); + config2.baudrate = 9600; + + //RX/TX connected to USB/UART Bridge on LoRa-E5 mini v1.0 + let mut usart1 = Uart::new(p.USART1, p.PB7, p.PB6, Irqs, p.DMA1_CH3, p.DMA1_CH4, config1); + + //RX1/TX1 (LPUART) on LoRa-E5 mini v1.0 + let mut usart2 = Uart::new(p.LPUART1, p.PC0, p.PC1, Irqs, p.DMA1_CH5, p.DMA1_CH6, config2); + + unwrap!(usart1.write(b"Hello Embassy World!\r\n").await); + unwrap!(usart2.write(b"Hello Embassy World!\r\n").await); + + let mut buf = [0u8; 300]; + loop { + let result = usart2.read_until_idle(&mut buf).await; + match result { + Ok(size) => { + match usart1.write(&buf[0..size]).await { + Ok(()) => { + //Write suc. + } + Err(..) => { + //Wasn't able to write + } + } + } + Err(_err) => { + //Ignore eg. framing errors + } + } + } +} diff --git a/examples/wasm/Cargo.toml b/examples/wasm/Cargo.toml index ea61fb921..3679e3857 100644 --- a/examples/wasm/Cargo.toml +++ b/examples/wasm/Cargo.toml @@ -2,14 +2,15 @@ edition = "2021" name = "embassy-wasm-example" version = "0.1.0" +license = "MIT OR Apache-2.0" [lib] crate-type = ["cdylib"] [dependencies] -embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["log"] } -embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["log", "wasm", "nightly", "integrated-timers"] } -embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["log", "wasm", "nightly"] } +embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["log"] } +embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-wasm", "executor-thread", "log", "nightly", "integrated-timers"] } +embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["log", "wasm", "nightly"] } wasm-logger = "0.2.0" wasm-bindgen = "0.2" diff --git a/examples/wasm/src/lib.rs b/examples/wasm/src/lib.rs index d44c020b6..edfe8bafc 100644 --- a/examples/wasm/src/lib.rs +++ b/examples/wasm/src/lib.rs @@ -1,5 +1,4 @@ #![feature(type_alias_impl_trait)] -#![allow(incomplete_features)] use embassy_executor::Spawner; use embassy_time::{Duration, Timer}; diff --git a/rust-toolchain.toml b/rust-toolchain.toml index f5e342edc..179ed1d6a 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,13 +1,14 @@ # Before upgrading check that everything is available on all tier1 targets here: # https://rust-lang.github.io/rustup-components-history [toolchain] -channel = "nightly-2022-08-16" -components = [ "rust-src", "rustfmt" ] +channel = "nightly-2023-06-28" +components = [ "rust-src", "rustfmt", "llvm-tools-preview" ] targets = [ "thumbv7em-none-eabi", "thumbv7m-none-eabi", "thumbv6m-none-eabi", "thumbv7em-none-eabihf", "thumbv8m.main-none-eabihf", + "riscv32imac-unknown-none-elf", "wasm32-unknown-unknown", -] +] \ No newline at end of file diff --git a/stm32-data b/stm32-data deleted file mode 160000 index 14a448c31..000000000 --- a/stm32-data +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 14a448c318192fe9da1c95a4de1beb4ec4892f1c diff --git a/stm32-gen-features/.cargo/config.toml b/stm32-gen-features/.cargo/config.toml deleted file mode 100644 index 17d81c14d..000000000 --- a/stm32-gen-features/.cargo/config.toml +++ /dev/null @@ -1,3 +0,0 @@ -[profile.dev] -opt-level = 3 -lto = false diff --git a/stm32-gen-features/.gitignore b/stm32-gen-features/.gitignore deleted file mode 100644 index ea8c4bf7f..000000000 --- a/stm32-gen-features/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/target diff --git a/stm32-gen-features/src/lib.rs b/stm32-gen-features/src/lib.rs deleted file mode 100644 index 7aaad9da3..000000000 --- a/stm32-gen-features/src/lib.rs +++ /dev/null @@ -1,169 +0,0 @@ -//! FIXME discuss about which errors to print and when to panic - -use std::path::Path; - -const SEPARATOR_START: &str = "# BEGIN GENERATED FEATURES\n"; -const SEPARATOR_END: &str = "# END GENERATED FEATURES\n"; -const HELP: &str = "# Generated by stm32-gen-features. DO NOT EDIT.\n"; - -/// Get the list of all the chips and their supported cores -/// -/// Print errors to `stderr` when something is returned by the glob but is not in the returned -/// [`Vec`] -/// -/// This function is slow because all the yaml files are parsed. -pub fn chip_names_and_cores() -> Vec<(String, Vec)> { - glob::glob("../stm32-data/data/chips/*.json") - .unwrap() - .filter_map(|entry| entry.map_err(|e| eprintln!("{:?}", e)).ok()) - .filter_map(|entry| { - if let Some(name) = entry.file_stem().and_then(|stem| stem.to_str()) { - Some((name.to_lowercase(), chip_cores(&entry))) - } else { - eprintln!("{:?} is not a regular file", entry); - None - } - }) - .collect() -} - -/// Get the list of the cores of a chip by its associated file -/// -/// # Panic -/// Panics if the file does not exist or if it contains yaml syntax errors. -/// Panics if "cores" is not an array. -fn chip_cores(path: &Path) -> Vec { - let file_contents = std::fs::read_to_string(path).unwrap(); - let doc = &yaml_rust::YamlLoader::load_from_str(&file_contents).unwrap()[0]; - doc["cores"] - .as_vec() - .unwrap_or_else(|| panic!("{:?}:[cores] is not an array", path)) - .iter() - .enumerate() - .map(|(i, core)| { - core["name"] - .as_str() - .unwrap_or_else(|| panic!("{:?}:[cores][{}][name] is not a string", path, i)) - .to_owned() - }) - .collect() -} - -/// Generate data needed in `../embassy-stm32/Cargo.toml` -/// -/// Print errors to `stderr` when something is returned by the glob but is not in the returned -/// [`Vec`] -/// -/// # Panic -/// Panics if a file contains yaml syntax errors or if a value does not have a consistent type -pub fn embassy_stm32_needed_data(names_and_cores: &[(String, Vec)]) -> String { - let mut result = String::new(); - for (chip_name, cores) in names_and_cores { - if cores.len() > 1 { - for core_name in cores.iter() { - result += &format!( - "{chip}-{core} = [ \"stm32-metapac/{chip}-{core}\" ]\n", - chip = chip_name, - core = core_name - ); - } - } else { - result += &format!("{chip} = [ \"stm32-metapac/{chip}\" ]\n", chip = chip_name); - } - } - result -} - -/// Generate data needed in `../stm32-metapac/Cargo.toml` -/// -/// Print errors to `stderr` when something is returned by the glob but is not in the returned -/// [`Vec`] -/// -/// # Panic -/// Panics if a file contains yaml syntax errors or if a value does not have a consistent type -pub fn stm32_metapac_needed_data(names_and_cores: &[(String, Vec)]) -> String { - let mut result = String::new(); - for (chip_name, cores) in names_and_cores { - if cores.len() > 1 { - for core_name in cores { - result += &format!("{}-{} = []\n", chip_name, core_name); - } - } else { - result += &format!("{} = []\n", chip_name); - } - } - result -} - -/// Get contents before and after generated contents -/// -/// # Panic -/// Panics when a separator cound not be not found -fn split_cargo_toml_contents(contents: &str) -> (&str, &str) { - let (before, remainder) = contents - .split_once(SEPARATOR_START) - .unwrap_or_else(|| panic!("missing \"{}\" tag", SEPARATOR_START)); - let (_, after) = remainder - .split_once(SEPARATOR_END) - .unwrap_or_else(|| panic!("missing \"{}\" tag", SEPARATOR_END)); - - (before, after) -} - -/// Generates new contents for Cargo.toml -/// -/// # Panic -/// Panics when a separator cound not be not found -pub fn generate_cargo_toml_file(previous_text: &str, new_contents: &str) -> String { - let (before, after) = split_cargo_toml_contents(previous_text); - before.to_owned() + SEPARATOR_START + HELP + new_contents + SEPARATOR_END + after -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - #[ignore] - fn stm32f407vg_yaml_file_exists() { - assert!(chip_names_and_cores() - .as_slice() - .into_iter() - .any(|(name, _)| { name == "stm32f407vg" })) - } - - #[test] - fn keeps_text_around_separators() { - let initial = "\ -before -# BEGIN GENERATED FEATURES -# END GENERATED FEATURES -after -"; - - let expected = "\ -before -# BEGIN GENERATED FEATURES -# Generated by stm32-gen-features. DO NOT EDIT. -a = [\"b\"] -# END GENERATED FEATURES -after -"; - - let new_contents = String::from("a = [\"b\"]\n"); - assert_eq!(generate_cargo_toml_file(initial, &new_contents), expected); - } - - #[test] - #[should_panic] - fn does_not_generate_if_separators_are_missing() { - let initial = "\ -before -# END GENERATED FEATURES -after -"; - - let new_contents = String::from("a = [\"b\"]\n"); - generate_cargo_toml_file(initial, &new_contents); - } -} diff --git a/stm32-gen-features/src/main.rs b/stm32-gen-features/src/main.rs deleted file mode 100644 index f40925169..000000000 --- a/stm32-gen-features/src/main.rs +++ /dev/null @@ -1,25 +0,0 @@ -use gen_features::{ - chip_names_and_cores, embassy_stm32_needed_data, generate_cargo_toml_file, stm32_metapac_needed_data, -}; - -fn main() { - let names_and_cores = chip_names_and_cores(); - update_cargo_file( - "../embassy-stm32/Cargo.toml", - &embassy_stm32_needed_data(&names_and_cores), - ); - update_cargo_file( - "../stm32-metapac/Cargo.toml", - &stm32_metapac_needed_data(&names_and_cores), - ); -} - -/// Update a Cargo.toml file -/// -/// Update the content between "# BEGIN GENERATED FEATURES" and "# END GENERATED FEATURES" -/// with the given content -fn update_cargo_file(path: &str, new_contents: &str) { - let previous_text = std::fs::read_to_string(path).unwrap(); - let new_text = generate_cargo_toml_file(&previous_text, new_contents); - std::fs::write(path, new_text).unwrap(); -} diff --git a/stm32-metapac-gen/Cargo.toml b/stm32-metapac-gen/Cargo.toml deleted file mode 100644 index 0ec2075f3..000000000 --- a/stm32-metapac-gen/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "stm32-metapac-gen" -version = "0.1.0" -edition = "2021" - - -[dependencies] -regex = "1.5.4" -chiptool = { git = "https://github.com/embassy-rs/chiptool", rev = "28ffa8a19d84914089547f52900ffb5877a5dc23" } -serde = { version = "1.0.130", features = [ "derive" ] } -serde_yaml = "0.8.21" -proc-macro2 = "1.0.29" diff --git a/stm32-metapac-gen/src/data.rs b/stm32-metapac-gen/src/data.rs deleted file mode 100644 index 17eccfe9a..000000000 --- a/stm32-metapac-gen/src/data.rs +++ /dev/null @@ -1,124 +0,0 @@ -use serde::Deserialize; - -#[derive(Debug, Eq, PartialEq, Clone, Deserialize)] -pub struct Chip { - pub name: String, - pub family: String, - pub line: String, - pub cores: Vec, - pub memory: Vec, - pub packages: Vec, -} - -#[derive(Debug, Eq, PartialEq, Clone, Deserialize)] -pub struct MemoryRegion { - pub name: String, - pub kind: MemoryRegionKind, - pub address: u32, - pub size: u32, - pub settings: Option, -} - -#[derive(Debug, Eq, PartialEq, Clone, Deserialize)] -pub struct FlashSettings { - pub erase_size: u32, - pub write_size: u32, - pub erase_value: u8, -} - -#[derive(Debug, Eq, PartialEq, Clone, Deserialize)] -pub enum MemoryRegionKind { - #[serde(rename = "flash")] - Flash, - #[serde(rename = "ram")] - Ram, -} - -#[derive(Debug, Eq, PartialEq, Clone, Deserialize)] -pub struct Core { - pub name: String, - pub peripherals: Vec, - pub interrupts: Vec, - pub dma_channels: Vec, -} - -#[derive(Debug, Eq, PartialEq, Clone, Deserialize)] -pub struct Interrupt { - pub name: String, - pub number: u32, -} - -#[derive(Debug, Eq, PartialEq, Clone, Deserialize)] -pub struct Package { - pub name: String, - pub package: String, -} - -#[derive(Debug, Eq, PartialEq, Clone, Deserialize)] -pub struct Peripheral { - pub name: String, - pub address: u64, - #[serde(default)] - pub registers: Option, - #[serde(default)] - pub rcc: Option, - #[serde(default)] - pub pins: Vec, - #[serde(default)] - pub dma_channels: Vec, - #[serde(default)] - pub interrupts: Vec, -} - -#[derive(Debug, Eq, PartialEq, Clone, Deserialize)] -pub struct PeripheralInterrupt { - pub signal: String, - pub interrupt: String, -} - -#[derive(Debug, Eq, PartialEq, Clone, Deserialize)] -pub struct PeripheralRcc { - pub clock: String, - #[serde(default)] - pub enable: Option, - #[serde(default)] - pub reset: Option, -} - -#[derive(Debug, Eq, PartialEq, Clone, Deserialize)] -pub struct PeripheralRccRegister { - pub register: String, - pub field: String, -} - -#[derive(Debug, Eq, PartialEq, Clone, Deserialize)] -pub struct PeripheralPin { - pub pin: String, - pub signal: String, - pub af: Option, -} - -#[derive(Debug, Eq, PartialEq, Clone, Deserialize)] -pub struct DmaChannel { - pub name: String, - pub dma: String, - pub channel: u32, - pub dmamux: Option, - pub dmamux_channel: Option, -} - -#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Hash)] -pub struct PeripheralDmaChannel { - pub signal: String, - pub channel: Option, - pub dmamux: Option, - pub dma: Option, - pub request: Option, -} - -#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Hash)] -pub struct PeripheralRegisters { - pub kind: String, - pub version: String, - pub block: String, -} diff --git a/stm32-metapac-gen/src/lib.rs b/stm32-metapac-gen/src/lib.rs deleted file mode 100644 index 9bd60cb79..000000000 --- a/stm32-metapac-gen/src/lib.rs +++ /dev/null @@ -1,380 +0,0 @@ -use std::collections::{BTreeMap, HashMap, HashSet}; -use std::fmt::{Debug, Write as _}; -use std::fs; -use std::fs::File; -use std::io::Write; -use std::path::{Path, PathBuf}; -use std::str::FromStr; - -use chiptool::generate::CommonModule; -use chiptool::{generate, ir, transform}; -use proc_macro2::TokenStream; -use regex::Regex; - -mod data; -use data::*; - -#[derive(Debug, Eq, PartialEq, Clone)] -struct Metadata<'a> { - name: &'a str, - family: &'a str, - line: &'a str, - memory: &'a [MemoryRegion], - peripherals: &'a [Peripheral], - interrupts: &'a [Interrupt], - dma_channels: &'a [DmaChannel], -} - -pub struct Options { - pub chips: Vec, - pub out_dir: PathBuf, - pub data_dir: PathBuf, -} - -pub struct Gen { - opts: Options, - all_peripheral_versions: HashSet<(String, String)>, - metadata_dedup: HashMap, -} - -impl Gen { - pub fn new(opts: Options) -> Self { - Self { - opts, - all_peripheral_versions: HashSet::new(), - metadata_dedup: HashMap::new(), - } - } - - fn gen_chip(&mut self, chip_core_name: &str, chip: &Chip, core: &Core, core_index: usize) { - let mut ir = ir::IR::new(); - - let mut dev = ir::Device { - interrupts: Vec::new(), - peripherals: Vec::new(), - }; - - let mut peripheral_versions: BTreeMap = BTreeMap::new(); - - let gpio_base = core.peripherals.iter().find(|p| p.name == "GPIOA").unwrap().address as u32; - let gpio_stride = 0x400; - - for p in &core.peripherals { - let mut ir_peri = ir::Peripheral { - name: p.name.clone(), - array: None, - base_address: p.address, - block: None, - description: None, - interrupts: HashMap::new(), - }; - - if let Some(bi) = &p.registers { - if let Some(old_version) = peripheral_versions.insert(bi.kind.clone(), bi.version.clone()) { - if old_version != bi.version { - panic!( - "Peripheral {} has multiple versions: {} and {}", - bi.kind, old_version, bi.version - ); - } - } - ir_peri.block = Some(format!("{}::{}", bi.kind, bi.block)); - - if bi.kind == "gpio" { - assert_eq!(0, (p.address as u32 - gpio_base) % gpio_stride); - } - } - - dev.peripherals.push(ir_peri); - } - - for irq in &core.interrupts { - dev.interrupts.push(ir::Interrupt { - name: irq.name.clone(), - description: None, - value: irq.number, - }); - } - - ir.devices.insert("".to_string(), dev); - - let mut extra = format!( - "pub fn GPIO(n: usize) -> gpio::Gpio {{ - gpio::Gpio(({} + {}*n) as _) - }}", - gpio_base, gpio_stride, - ); - - for (module, version) in &peripheral_versions { - self.all_peripheral_versions.insert((module.clone(), version.clone())); - write!( - &mut extra, - "#[path=\"../../peripherals/{}_{}.rs\"] pub mod {};\n", - module, version, module - ) - .unwrap(); - } - write!(&mut extra, "pub const CORE_INDEX: usize = {};\n", core_index).unwrap(); - - let flash = chip.memory.iter().find(|r| r.name == "BANK_1").unwrap(); - let settings = flash.settings.as_ref().unwrap(); - write!(&mut extra, "pub const FLASH_BASE: usize = {};\n", flash.address).unwrap(); - write!(&mut extra, "pub const FLASH_SIZE: usize = {};\n", flash.size).unwrap(); - write!(&mut extra, "pub const ERASE_SIZE: usize = {};\n", settings.erase_size).unwrap(); - write!(&mut extra, "pub const WRITE_SIZE: usize = {};\n", settings.write_size).unwrap(); - write!(&mut extra, "pub const ERASE_VALUE: u8 = {};\n", settings.erase_value).unwrap(); - - // Cleanups! - transform::sort::Sort {}.run(&mut ir).unwrap(); - transform::Sanitize {}.run(&mut ir).unwrap(); - - // ============================== - // Setup chip dir - - let chip_dir = self - .opts - .out_dir - .join("src/chips") - .join(chip_core_name.to_ascii_lowercase()); - fs::create_dir_all(&chip_dir).unwrap(); - - // ============================== - // generate pac.rs - - let data = generate::render(&ir, &gen_opts()).unwrap().to_string(); - let data = data.replace("] ", "]\n"); - - // Remove inner attributes like #![no_std] - let data = Regex::new("# *! *\\[.*\\]").unwrap().replace_all(&data, ""); - - let mut file = File::create(chip_dir.join("pac.rs")).unwrap(); - file.write_all(data.as_bytes()).unwrap(); - file.write_all(extra.as_bytes()).unwrap(); - - let mut device_x = String::new(); - - for irq in &core.interrupts { - write!(&mut device_x, "PROVIDE({} = DefaultHandler);\n", irq.name).unwrap(); - } - - // ============================== - // generate metadata.rs - - // (peripherals, interrupts, dma_channels) are often equal across multiple chips. - // To reduce bloat, deduplicate them. - let mut data = String::new(); - write!( - &mut data, - " - const PERIPHERALS: &'static [Peripheral] = {}; - const INTERRUPTS: &'static [Interrupt] = {}; - const DMA_CHANNELS: &'static [DmaChannel] = {}; - ", - stringify(&core.peripherals), - stringify(&core.interrupts), - stringify(&core.dma_channels), - ) - .unwrap(); - - let out_dir = self.opts.out_dir.clone(); - let n = self.metadata_dedup.len(); - let deduped_file = self.metadata_dedup.entry(data.clone()).or_insert_with(|| { - let file = format!("metadata_{:04}.rs", n); - let path = out_dir.join("src/chips").join(&file); - fs::write(path, data).unwrap(); - - file - }); - - let data = format!( - "include!(\"../{}\"); - pub const METADATA: Metadata = Metadata {{ - name: {:?}, - family: {:?}, - line: {:?}, - memory: {}, - peripherals: PERIPHERALS, - interrupts: INTERRUPTS, - dma_channels: DMA_CHANNELS, - }};", - deduped_file, - &chip.name, - &chip.family, - &chip.line, - stringify(&chip.memory), - ); - - let mut file = File::create(chip_dir.join("metadata.rs")).unwrap(); - file.write_all(data.as_bytes()).unwrap(); - - // ============================== - // generate device.x - - File::create(chip_dir.join("device.x")) - .unwrap() - .write_all(device_x.as_bytes()) - .unwrap(); - - // ============================== - // generate default memory.x - gen_memory_x(&chip_dir, &chip); - } - - fn load_chip(&mut self, name: &str) -> Chip { - let chip_path = self.opts.data_dir.join("chips").join(&format!("{}.json", name)); - let chip = fs::read(chip_path).expect(&format!("Could not load chip {}", name)); - serde_yaml::from_slice(&chip).unwrap() - } - - pub fn gen(&mut self) { - fs::create_dir_all(self.opts.out_dir.join("src/peripherals")).unwrap(); - fs::create_dir_all(self.opts.out_dir.join("src/chips")).unwrap(); - - let mut chip_core_names: Vec = Vec::new(); - - for chip_name in &self.opts.chips.clone() { - println!("Generating {}...", chip_name); - - let mut chip = self.load_chip(chip_name); - - // Cleanup - for core in &mut chip.cores { - for irq in &mut core.interrupts { - irq.name = irq.name.to_ascii_uppercase(); - } - for p in &mut core.peripherals { - for irq in &mut p.interrupts { - irq.interrupt = irq.interrupt.to_ascii_uppercase(); - } - } - } - - // Generate - for (core_index, core) in chip.cores.iter().enumerate() { - let chip_core_name = match chip.cores.len() { - 1 => chip_name.clone(), - _ => format!("{}-{}", chip_name, core.name), - }; - - chip_core_names.push(chip_core_name.clone()); - self.gen_chip(&chip_core_name, &chip, core, core_index) - } - } - - for (module, version) in &self.all_peripheral_versions { - println!("loading {} {}", module, version); - - let regs_path = Path::new(&self.opts.data_dir) - .join("registers") - .join(&format!("{}_{}.yaml", module, version)); - - let mut ir: ir::IR = serde_yaml::from_reader(File::open(regs_path).unwrap()).unwrap(); - - transform::expand_extends::ExpandExtends {}.run(&mut ir).unwrap(); - - transform::map_names(&mut ir, |k, s| match k { - transform::NameKind::Block => *s = format!("{}", s), - transform::NameKind::Fieldset => *s = format!("regs::{}", s), - transform::NameKind::Enum => *s = format!("vals::{}", s), - _ => {} - }); - - transform::sort::Sort {}.run(&mut ir).unwrap(); - transform::Sanitize {}.run(&mut ir).unwrap(); - - let items = generate::render(&ir, &gen_opts()).unwrap(); - let mut file = File::create( - self.opts - .out_dir - .join("src/peripherals") - .join(format!("{}_{}.rs", module, version)), - ) - .unwrap(); - let data = items.to_string().replace("] ", "]\n"); - - // Remove inner attributes like #![no_std] - let re = Regex::new("# *! *\\[.*\\]").unwrap(); - let data = re.replace_all(&data, ""); - file.write_all(data.as_bytes()).unwrap(); - } - - // Generate Cargo.toml - const BUILDDEP_BEGIN: &[u8] = b"# BEGIN BUILD DEPENDENCIES"; - const BUILDDEP_END: &[u8] = b"# END BUILD DEPENDENCIES"; - - let mut contents = include_bytes!("../../stm32-metapac/Cargo.toml").to_vec(); - let begin = bytes_find(&contents, BUILDDEP_BEGIN).unwrap(); - let end = bytes_find(&contents, BUILDDEP_END).unwrap() + BUILDDEP_END.len(); - contents.drain(begin..end); - fs::write(self.opts.out_dir.join("Cargo.toml"), contents).unwrap(); - - // copy misc files - fs::write( - self.opts.out_dir.join("build.rs"), - include_bytes!("../../stm32-metapac/build_pregenerated.rs"), - ) - .unwrap(); - fs::write( - self.opts.out_dir.join("src/lib.rs"), - include_bytes!("../../stm32-metapac/src/lib.rs"), - ) - .unwrap(); - fs::write( - self.opts.out_dir.join("src/common.rs"), - chiptool::generate::COMMON_MODULE, - ) - .unwrap(); - fs::write( - self.opts.out_dir.join("src/metadata.rs"), - include_bytes!("../../stm32-metapac/src/metadata.rs"), - ) - .unwrap(); - } -} - -fn bytes_find(haystack: &[u8], needle: &[u8]) -> Option { - haystack.windows(needle.len()).position(|window| window == needle) -} - -fn stringify(metadata: T) -> String { - let mut metadata = format!("{:?}", metadata); - if metadata.starts_with('[') { - metadata = format!("&{}", metadata); - } - metadata = metadata.replace(": [", ": &["); - metadata = metadata.replace("kind: Ram", "kind: MemoryRegionKind::Ram"); - metadata = metadata.replace("kind: Flash", "kind: MemoryRegionKind::Flash"); - metadata -} - -fn gen_opts() -> generate::Options { - generate::Options { - common_module: CommonModule::External(TokenStream::from_str("crate::common").unwrap()), - } -} - -fn gen_memory_x(out_dir: &PathBuf, chip: &Chip) { - let mut memory_x = String::new(); - - let flash = chip.memory.iter().find(|r| r.name == "BANK_1").unwrap(); - let ram = chip.memory.iter().find(|r| r.name == "SRAM").unwrap(); - - write!(memory_x, "MEMORY\n{{\n").unwrap(); - write!( - memory_x, - " FLASH : ORIGIN = 0x{:x}, LENGTH = {}\n", - flash.address, flash.size, - ) - .unwrap(); - write!( - memory_x, - " RAM : ORIGIN = 0x{:x}, LENGTH = {}\n", - ram.address, ram.size, - ) - .unwrap(); - write!(memory_x, "}}").unwrap(); - - fs::create_dir_all(out_dir.join("memory_x")).unwrap(); - let mut file = File::create(out_dir.join("memory_x").join("memory.x")).unwrap(); - file.write_all(memory_x.as_bytes()).unwrap(); -} diff --git a/stm32-metapac-gen/src/main.rs b/stm32-metapac-gen/src/main.rs deleted file mode 100644 index 40a73adf8..000000000 --- a/stm32-metapac-gen/src/main.rs +++ /dev/null @@ -1,36 +0,0 @@ -use std::env::args; -use std::path::PathBuf; - -use stm32_metapac_gen::*; - -fn main() { - let out_dir = PathBuf::from("out"); - let data_dir = PathBuf::from("../stm32-data/data"); - - let args: Vec = args().collect(); - - let mut chips = match &args[..] { - [_, chip] => { - vec![chip.clone()] - } - [_] => { - std::fs::read_dir(data_dir.join("chips")) - .unwrap() - .filter_map(|res| res.unwrap().file_name().to_str().map(|s| s.to_string())) - .filter(|s| s.ends_with(".json")) - .filter(|s| !s.starts_with("STM32GBK")) // cursed weird STM32G4 - .map(|s| s.strip_suffix(".json").unwrap().to_string()) - .collect() - } - _ => panic!("usage: stm32-metapac-gen [chip?]"), - }; - - chips.sort(); - - let opts = Options { - out_dir, - data_dir, - chips, - }; - Gen::new(opts).gen(); -} diff --git a/stm32-metapac/Cargo.toml b/stm32-metapac/Cargo.toml deleted file mode 100644 index 9d5aba0c0..000000000 --- a/stm32-metapac/Cargo.toml +++ /dev/null @@ -1,1325 +0,0 @@ -[package] -name = "stm32-metapac" -version = "0.1.0" -edition = "2021" -license = "MIT OR Apache-2.0" -repository = "https://github.com/embassy-rs/embassy" -description = "Peripheral Access Crate (PAC) for all STM32 chips, including metadata." - -# `cargo publish` is unable to figure out which .rs files are needed due to the include! magic. -include = [ - "**/*.rs", - "**/*.x", - "Cargo.toml", -] - -[package.metadata.docs.rs] -features = ["stm32h755zi-cm7", "pac", "metadata"] -default-target = "thumbv7em-none-eabihf" -targets = [] - -[package.metadata.embassy_docs] -features = ["pac", "metadata"] -flavors = [ - { regex_feature = "stm32f0.*", target = "thumbv6m-none-eabi" }, - { regex_feature = "stm32f1.*", target = "thumbv7m-none-eabi" }, - { regex_feature = "stm32f2.*", target = "thumbv7m-none-eabi" }, - { regex_feature = "stm32f3.*", target = "thumbv7em-none-eabi" }, - { regex_feature = "stm32f4.*", target = "thumbv7em-none-eabi" }, - { regex_feature = "stm32f7.*", target = "thumbv7em-none-eabi" }, - { regex_feature = "stm32g0.*", target = "thumbv6m-none-eabi" }, - { regex_feature = "stm32g4.*", target = "thumbv7em-none-eabi" }, - { regex_feature = "stm32h7.*", target = "thumbv7em-none-eabi" }, - { regex_feature = "stm32l0.*", target = "thumbv6m-none-eabi" }, - { regex_feature = "stm32l1.*", target = "thumbv7m-none-eabi" }, - { regex_feature = "stm32l4.*", target = "thumbv7em-none-eabi" }, - { regex_feature = "stm32l5.*", target = "thumbv8m.main-none-eabihf" }, - { regex_feature = "stm32u5.*", target = "thumbv8m.main-none-eabihf" }, - { regex_feature = "stm32wb.*", target = "thumbv7em-none-eabi" }, - { regex_feature = "stm32wl.*", target = "thumbv7em-none-eabi" }, -] - -[dependencies] -cortex-m = "0.7.6" -cortex-m-rt = { version = ">=0.6.15,<0.8", optional = true } - -# BEGIN BUILD DEPENDENCIES -# These are removed when generating the pre-generated crate using the tool at gen/. -[build-dependencies] -stm32-metapac-gen = { path = "../stm32-metapac-gen" } -regex = "1.5.4" -# END BUILD DEPENDENCIES - -[features] -default = ["pac"] - -# Build the actual PAC. Set by default. -# If you just want the metadata, unset it with `default-features = false`. -pac = [] - -# Build the chip metadata. -# If set, a const `stm32_metapac::METADATA` will be exported, containing all the -# metadata for the currently selected chip. -metadata = [] - -rt = ["cortex-m-rt/device"] -memory-x = [] - -# BEGIN GENERATED FEATURES -# Generated by stm32-gen-features. DO NOT EDIT. -stm32f030c6 = [] -stm32f030c8 = [] -stm32f030cc = [] -stm32f030f4 = [] -stm32f030k6 = [] -stm32f030r8 = [] -stm32f030rc = [] -stm32f031c4 = [] -stm32f031c6 = [] -stm32f031e6 = [] -stm32f031f4 = [] -stm32f031f6 = [] -stm32f031g4 = [] -stm32f031g6 = [] -stm32f031k4 = [] -stm32f031k6 = [] -stm32f038c6 = [] -stm32f038e6 = [] -stm32f038f6 = [] -stm32f038g6 = [] -stm32f038k6 = [] -stm32f042c4 = [] -stm32f042c6 = [] -stm32f042f4 = [] -stm32f042f6 = [] -stm32f042g4 = [] -stm32f042g6 = [] -stm32f042k4 = [] -stm32f042k6 = [] -stm32f042t6 = [] -stm32f048c6 = [] -stm32f048g6 = [] -stm32f048t6 = [] -stm32f051c4 = [] -stm32f051c6 = [] -stm32f051c8 = [] -stm32f051k4 = [] -stm32f051k6 = [] -stm32f051k8 = [] -stm32f051r4 = [] -stm32f051r6 = [] -stm32f051r8 = [] -stm32f051t8 = [] -stm32f058c8 = [] -stm32f058r8 = [] -stm32f058t8 = [] -stm32f070c6 = [] -stm32f070cb = [] -stm32f070f6 = [] -stm32f070rb = [] -stm32f071c8 = [] -stm32f071cb = [] -stm32f071rb = [] -stm32f071v8 = [] -stm32f071vb = [] -stm32f072c8 = [] -stm32f072cb = [] -stm32f072r8 = [] -stm32f072rb = [] -stm32f072v8 = [] -stm32f072vb = [] -stm32f078cb = [] -stm32f078rb = [] -stm32f078vb = [] -stm32f091cb = [] -stm32f091cc = [] -stm32f091rb = [] -stm32f091rc = [] -stm32f091vb = [] -stm32f091vc = [] -stm32f098cc = [] -stm32f098rc = [] -stm32f098vc = [] -stm32f100c4 = [] -stm32f100c6 = [] -stm32f100c8 = [] -stm32f100cb = [] -stm32f100r4 = [] -stm32f100r6 = [] -stm32f100r8 = [] -stm32f100rb = [] -stm32f100rc = [] -stm32f100rd = [] -stm32f100re = [] -stm32f100v8 = [] -stm32f100vb = [] -stm32f100vc = [] -stm32f100vd = [] -stm32f100ve = [] -stm32f100zc = [] -stm32f100zd = [] -stm32f100ze = [] -stm32f101c4 = [] -stm32f101c6 = [] -stm32f101c8 = [] -stm32f101cb = [] -stm32f101r4 = [] -stm32f101r6 = [] -stm32f101r8 = [] -stm32f101rb = [] -stm32f101rc = [] -stm32f101rd = [] -stm32f101re = [] -stm32f101rf = [] -stm32f101rg = [] -stm32f101t4 = [] -stm32f101t6 = [] -stm32f101t8 = [] -stm32f101tb = [] -stm32f101v8 = [] -stm32f101vb = [] -stm32f101vc = [] -stm32f101vd = [] -stm32f101ve = [] -stm32f101vf = [] -stm32f101vg = [] -stm32f101zc = [] -stm32f101zd = [] -stm32f101ze = [] -stm32f101zf = [] -stm32f101zg = [] -stm32f102c4 = [] -stm32f102c6 = [] -stm32f102c8 = [] -stm32f102cb = [] -stm32f102r4 = [] -stm32f102r6 = [] -stm32f102r8 = [] -stm32f102rb = [] -stm32f103c4 = [] -stm32f103c6 = [] -stm32f103c8 = [] -stm32f103cb = [] -stm32f103r4 = [] -stm32f103r6 = [] -stm32f103r8 = [] -stm32f103rb = [] -stm32f103rc = [] -stm32f103rd = [] -stm32f103re = [] -stm32f103rf = [] -stm32f103rg = [] -stm32f103t4 = [] -stm32f103t6 = [] -stm32f103t8 = [] -stm32f103tb = [] -stm32f103v8 = [] -stm32f103vb = [] -stm32f103vc = [] -stm32f103vd = [] -stm32f103ve = [] -stm32f103vf = [] -stm32f103vg = [] -stm32f103zc = [] -stm32f103zd = [] -stm32f103ze = [] -stm32f103zf = [] -stm32f103zg = [] -stm32f105r8 = [] -stm32f105rb = [] -stm32f105rc = [] -stm32f105v8 = [] -stm32f105vb = [] -stm32f105vc = [] -stm32f107rb = [] -stm32f107rc = [] -stm32f107vb = [] -stm32f107vc = [] -stm32f205rb = [] -stm32f205rc = [] -stm32f205re = [] -stm32f205rf = [] -stm32f205rg = [] -stm32f205vb = [] -stm32f205vc = [] -stm32f205ve = [] -stm32f205vf = [] -stm32f205vg = [] -stm32f205zc = [] -stm32f205ze = [] -stm32f205zf = [] -stm32f205zg = [] -stm32f207ic = [] -stm32f207ie = [] -stm32f207if = [] -stm32f207ig = [] -stm32f207vc = [] -stm32f207ve = [] -stm32f207vf = [] -stm32f207vg = [] -stm32f207zc = [] -stm32f207ze = [] -stm32f207zf = [] -stm32f207zg = [] -stm32f215re = [] -stm32f215rg = [] -stm32f215ve = [] -stm32f215vg = [] -stm32f215ze = [] -stm32f215zg = [] -stm32f217ie = [] -stm32f217ig = [] -stm32f217ve = [] -stm32f217vg = [] -stm32f217ze = [] -stm32f217zg = [] -stm32f301c6 = [] -stm32f301c8 = [] -stm32f301k6 = [] -stm32f301k8 = [] -stm32f301r6 = [] -stm32f301r8 = [] -stm32f302c6 = [] -stm32f302c8 = [] -stm32f302cb = [] -stm32f302cc = [] -stm32f302k6 = [] -stm32f302k8 = [] -stm32f302r6 = [] -stm32f302r8 = [] -stm32f302rb = [] -stm32f302rc = [] -stm32f302rd = [] -stm32f302re = [] -stm32f302vb = [] -stm32f302vc = [] -stm32f302vd = [] -stm32f302ve = [] -stm32f302zd = [] -stm32f302ze = [] -stm32f303c6 = [] -stm32f303c8 = [] -stm32f303cb = [] -stm32f303cc = [] -stm32f303k6 = [] -stm32f303k8 = [] -stm32f303r6 = [] -stm32f303r8 = [] -stm32f303rb = [] -stm32f303rc = [] -stm32f303rd = [] -stm32f303re = [] -stm32f303vb = [] -stm32f303vc = [] -stm32f303vd = [] -stm32f303ve = [] -stm32f303zd = [] -stm32f303ze = [] -stm32f318c8 = [] -stm32f318k8 = [] -stm32f328c8 = [] -stm32f334c4 = [] -stm32f334c6 = [] -stm32f334c8 = [] -stm32f334k4 = [] -stm32f334k6 = [] -stm32f334k8 = [] -stm32f334r6 = [] -stm32f334r8 = [] -stm32f358cc = [] -stm32f358rc = [] -stm32f358vc = [] -stm32f373c8 = [] -stm32f373cb = [] -stm32f373cc = [] -stm32f373r8 = [] -stm32f373rb = [] -stm32f373rc = [] -stm32f373v8 = [] -stm32f373vb = [] -stm32f373vc = [] -stm32f378cc = [] -stm32f378rc = [] -stm32f378vc = [] -stm32f398ve = [] -stm32f401cb = [] -stm32f401cc = [] -stm32f401cd = [] -stm32f401ce = [] -stm32f401rb = [] -stm32f401rc = [] -stm32f401rd = [] -stm32f401re = [] -stm32f401vb = [] -stm32f401vc = [] -stm32f401vd = [] -stm32f401ve = [] -stm32f405oe = [] -stm32f405og = [] -stm32f405rg = [] -stm32f405vg = [] -stm32f405zg = [] -stm32f407ie = [] -stm32f407ig = [] -stm32f407ve = [] -stm32f407vg = [] -stm32f407ze = [] -stm32f407zg = [] -stm32f410c8 = [] -stm32f410cb = [] -stm32f410r8 = [] -stm32f410rb = [] -stm32f410t8 = [] -stm32f410tb = [] -stm32f411cc = [] -stm32f411ce = [] -stm32f411rc = [] -stm32f411re = [] -stm32f411vc = [] -stm32f411ve = [] -stm32f412ce = [] -stm32f412cg = [] -stm32f412re = [] -stm32f412rg = [] -stm32f412ve = [] -stm32f412vg = [] -stm32f412ze = [] -stm32f412zg = [] -stm32f413cg = [] -stm32f413ch = [] -stm32f413mg = [] -stm32f413mh = [] -stm32f413rg = [] -stm32f413rh = [] -stm32f413vg = [] -stm32f413vh = [] -stm32f413zg = [] -stm32f413zh = [] -stm32f415og = [] -stm32f415rg = [] -stm32f415vg = [] -stm32f415zg = [] -stm32f417ie = [] -stm32f417ig = [] -stm32f417ve = [] -stm32f417vg = [] -stm32f417ze = [] -stm32f417zg = [] -stm32f423ch = [] -stm32f423mh = [] -stm32f423rh = [] -stm32f423vh = [] -stm32f423zh = [] -stm32f427ag = [] -stm32f427ai = [] -stm32f427ig = [] -stm32f427ii = [] -stm32f427vg = [] -stm32f427vi = [] -stm32f427zg = [] -stm32f427zi = [] -stm32f429ag = [] -stm32f429ai = [] -stm32f429be = [] -stm32f429bg = [] -stm32f429bi = [] -stm32f429ie = [] -stm32f429ig = [] -stm32f429ii = [] -stm32f429ne = [] -stm32f429ng = [] -stm32f429ni = [] -stm32f429ve = [] -stm32f429vg = [] -stm32f429vi = [] -stm32f429ze = [] -stm32f429zg = [] -stm32f429zi = [] -stm32f437ai = [] -stm32f437ig = [] -stm32f437ii = [] -stm32f437vg = [] -stm32f437vi = [] -stm32f437zg = [] -stm32f437zi = [] -stm32f439ai = [] -stm32f439bg = [] -stm32f439bi = [] -stm32f439ig = [] -stm32f439ii = [] -stm32f439ng = [] -stm32f439ni = [] -stm32f439vg = [] -stm32f439vi = [] -stm32f439zg = [] -stm32f439zi = [] -stm32f446mc = [] -stm32f446me = [] -stm32f446rc = [] -stm32f446re = [] -stm32f446vc = [] -stm32f446ve = [] -stm32f446zc = [] -stm32f446ze = [] -stm32f469ae = [] -stm32f469ag = [] -stm32f469ai = [] -stm32f469be = [] -stm32f469bg = [] -stm32f469bi = [] -stm32f469ie = [] -stm32f469ig = [] -stm32f469ii = [] -stm32f469ne = [] -stm32f469ng = [] -stm32f469ni = [] -stm32f469ve = [] -stm32f469vg = [] -stm32f469vi = [] -stm32f469ze = [] -stm32f469zg = [] -stm32f469zi = [] -stm32f479ag = [] -stm32f479ai = [] -stm32f479bg = [] -stm32f479bi = [] -stm32f479ig = [] -stm32f479ii = [] -stm32f479ng = [] -stm32f479ni = [] -stm32f479vg = [] -stm32f479vi = [] -stm32f479zg = [] -stm32f479zi = [] -stm32f722ic = [] -stm32f722ie = [] -stm32f722rc = [] -stm32f722re = [] -stm32f722vc = [] -stm32f722ve = [] -stm32f722zc = [] -stm32f722ze = [] -stm32f723ic = [] -stm32f723ie = [] -stm32f723vc = [] -stm32f723ve = [] -stm32f723zc = [] -stm32f723ze = [] -stm32f730i8 = [] -stm32f730r8 = [] -stm32f730v8 = [] -stm32f730z8 = [] -stm32f732ie = [] -stm32f732re = [] -stm32f732ve = [] -stm32f732ze = [] -stm32f733ie = [] -stm32f733ve = [] -stm32f733ze = [] -stm32f745ie = [] -stm32f745ig = [] -stm32f745ve = [] -stm32f745vg = [] -stm32f745ze = [] -stm32f745zg = [] -stm32f746be = [] -stm32f746bg = [] -stm32f746ie = [] -stm32f746ig = [] -stm32f746ne = [] -stm32f746ng = [] -stm32f746ve = [] -stm32f746vg = [] -stm32f746ze = [] -stm32f746zg = [] -stm32f750n8 = [] -stm32f750v8 = [] -stm32f750z8 = [] -stm32f756bg = [] -stm32f756ig = [] -stm32f756ng = [] -stm32f756vg = [] -stm32f756zg = [] -stm32f765bg = [] -stm32f765bi = [] -stm32f765ig = [] -stm32f765ii = [] -stm32f765ng = [] -stm32f765ni = [] -stm32f765vg = [] -stm32f765vi = [] -stm32f765zg = [] -stm32f765zi = [] -stm32f767bg = [] -stm32f767bi = [] -stm32f767ig = [] -stm32f767ii = [] -stm32f767ng = [] -stm32f767ni = [] -stm32f767vg = [] -stm32f767vi = [] -stm32f767zg = [] -stm32f767zi = [] -stm32f768ai = [] -stm32f769ag = [] -stm32f769ai = [] -stm32f769bg = [] -stm32f769bi = [] -stm32f769ig = [] -stm32f769ii = [] -stm32f769ng = [] -stm32f769ni = [] -stm32f777bi = [] -stm32f777ii = [] -stm32f777ni = [] -stm32f777vi = [] -stm32f777zi = [] -stm32f778ai = [] -stm32f779ai = [] -stm32f779bi = [] -stm32f779ii = [] -stm32f779ni = [] -stm32g030c6 = [] -stm32g030c8 = [] -stm32g030f6 = [] -stm32g030j6 = [] -stm32g030k6 = [] -stm32g030k8 = [] -stm32g031c4 = [] -stm32g031c6 = [] -stm32g031c8 = [] -stm32g031f4 = [] -stm32g031f6 = [] -stm32g031f8 = [] -stm32g031g4 = [] -stm32g031g6 = [] -stm32g031g8 = [] -stm32g031j4 = [] -stm32g031j6 = [] -stm32g031k4 = [] -stm32g031k6 = [] -stm32g031k8 = [] -stm32g031y8 = [] -stm32g041c6 = [] -stm32g041c8 = [] -stm32g041f6 = [] -stm32g041f8 = [] -stm32g041g6 = [] -stm32g041g8 = [] -stm32g041j6 = [] -stm32g041k6 = [] -stm32g041k8 = [] -stm32g041y8 = [] -stm32g050c6 = [] -stm32g050c8 = [] -stm32g050f6 = [] -stm32g050k6 = [] -stm32g050k8 = [] -stm32g051c6 = [] -stm32g051c8 = [] -stm32g051f6 = [] -stm32g051f8 = [] -stm32g051g6 = [] -stm32g051g8 = [] -stm32g051k6 = [] -stm32g051k8 = [] -stm32g061c6 = [] -stm32g061c8 = [] -stm32g061f6 = [] -stm32g061f8 = [] -stm32g061g6 = [] -stm32g061g8 = [] -stm32g061k6 = [] -stm32g061k8 = [] -stm32g070cb = [] -stm32g070kb = [] -stm32g070rb = [] -stm32g071c6 = [] -stm32g071c8 = [] -stm32g071cb = [] -stm32g071eb = [] -stm32g071g6 = [] -stm32g071g8 = [] -stm32g071gb = [] -stm32g071k6 = [] -stm32g071k8 = [] -stm32g071kb = [] -stm32g071r6 = [] -stm32g071r8 = [] -stm32g071rb = [] -stm32g081cb = [] -stm32g081eb = [] -stm32g081gb = [] -stm32g081kb = [] -stm32g081rb = [] -stm32g0b0ce = [] -stm32g0b0ke = [] -stm32g0b0re = [] -stm32g0b0ve = [] -stm32g0b1cb = [] -stm32g0b1cc = [] -stm32g0b1ce = [] -stm32g0b1kb = [] -stm32g0b1kc = [] -stm32g0b1ke = [] -stm32g0b1mb = [] -stm32g0b1mc = [] -stm32g0b1me = [] -stm32g0b1ne = [] -stm32g0b1rb = [] -stm32g0b1rc = [] -stm32g0b1re = [] -stm32g0b1vb = [] -stm32g0b1vc = [] -stm32g0b1ve = [] -stm32g0c1cc = [] -stm32g0c1ce = [] -stm32g0c1kc = [] -stm32g0c1ke = [] -stm32g0c1mc = [] -stm32g0c1me = [] -stm32g0c1ne = [] -stm32g0c1rc = [] -stm32g0c1re = [] -stm32g0c1vc = [] -stm32g0c1ve = [] -stm32g431c6 = [] -stm32g431c8 = [] -stm32g431cb = [] -stm32g431k6 = [] -stm32g431k8 = [] -stm32g431kb = [] -stm32g431m6 = [] -stm32g431m8 = [] -stm32g431mb = [] -stm32g431r6 = [] -stm32g431r8 = [] -stm32g431rb = [] -stm32g431v6 = [] -stm32g431v8 = [] -stm32g431vb = [] -stm32g441cb = [] -stm32g441kb = [] -stm32g441mb = [] -stm32g441rb = [] -stm32g441vb = [] -stm32g471cc = [] -stm32g471ce = [] -stm32g471mc = [] -stm32g471me = [] -stm32g471qc = [] -stm32g471qe = [] -stm32g471rc = [] -stm32g471re = [] -stm32g471vc = [] -stm32g471ve = [] -stm32g473cb = [] -stm32g473cc = [] -stm32g473ce = [] -stm32g473mb = [] -stm32g473mc = [] -stm32g473me = [] -stm32g473pb = [] -stm32g473pc = [] -stm32g473pe = [] -stm32g473qb = [] -stm32g473qc = [] -stm32g473qe = [] -stm32g473rb = [] -stm32g473rc = [] -stm32g473re = [] -stm32g473vb = [] -stm32g473vc = [] -stm32g473ve = [] -stm32g474cb = [] -stm32g474cc = [] -stm32g474ce = [] -stm32g474mb = [] -stm32g474mc = [] -stm32g474me = [] -stm32g474pb = [] -stm32g474pc = [] -stm32g474pe = [] -stm32g474qb = [] -stm32g474qc = [] -stm32g474qe = [] -stm32g474rb = [] -stm32g474rc = [] -stm32g474re = [] -stm32g474vb = [] -stm32g474vc = [] -stm32g474ve = [] -stm32g483ce = [] -stm32g483me = [] -stm32g483pe = [] -stm32g483qe = [] -stm32g483re = [] -stm32g483ve = [] -stm32g484ce = [] -stm32g484me = [] -stm32g484pe = [] -stm32g484qe = [] -stm32g484re = [] -stm32g484ve = [] -stm32g491cc = [] -stm32g491ce = [] -stm32g491kc = [] -stm32g491ke = [] -stm32g491mc = [] -stm32g491me = [] -stm32g491rc = [] -stm32g491re = [] -stm32g491vc = [] -stm32g491ve = [] -stm32g4a1ce = [] -stm32g4a1ke = [] -stm32g4a1me = [] -stm32g4a1re = [] -stm32g4a1ve = [] -stm32h723ve = [] -stm32h723vg = [] -stm32h723ze = [] -stm32h723zg = [] -stm32h725ae = [] -stm32h725ag = [] -stm32h725ie = [] -stm32h725ig = [] -stm32h725re = [] -stm32h725rg = [] -stm32h725ve = [] -stm32h725vg = [] -stm32h725ze = [] -stm32h725zg = [] -stm32h730ab = [] -stm32h730ib = [] -stm32h730vb = [] -stm32h730zb = [] -stm32h733vg = [] -stm32h733zg = [] -stm32h735ag = [] -stm32h735ig = [] -stm32h735rg = [] -stm32h735vg = [] -stm32h735zg = [] -stm32h742ag = [] -stm32h742ai = [] -stm32h742bg = [] -stm32h742bi = [] -stm32h742ig = [] -stm32h742ii = [] -stm32h742vg = [] -stm32h742vi = [] -stm32h742xg = [] -stm32h742xi = [] -stm32h742zg = [] -stm32h742zi = [] -stm32h743ag = [] -stm32h743ai = [] -stm32h743bg = [] -stm32h743bi = [] -stm32h743ig = [] -stm32h743ii = [] -stm32h743vg = [] -stm32h743vi = [] -stm32h743xg = [] -stm32h743xi = [] -stm32h743zg = [] -stm32h743zi = [] -stm32h745bg-cm7 = [] -stm32h745bg-cm4 = [] -stm32h745bi-cm7 = [] -stm32h745bi-cm4 = [] -stm32h745ig-cm7 = [] -stm32h745ig-cm4 = [] -stm32h745ii-cm7 = [] -stm32h745ii-cm4 = [] -stm32h745xg-cm7 = [] -stm32h745xg-cm4 = [] -stm32h745xi-cm7 = [] -stm32h745xi-cm4 = [] -stm32h745zg-cm7 = [] -stm32h745zg-cm4 = [] -stm32h745zi-cm7 = [] -stm32h745zi-cm4 = [] -stm32h747ag-cm7 = [] -stm32h747ag-cm4 = [] -stm32h747ai-cm7 = [] -stm32h747ai-cm4 = [] -stm32h747bg-cm7 = [] -stm32h747bg-cm4 = [] -stm32h747bi-cm7 = [] -stm32h747bi-cm4 = [] -stm32h747ig-cm7 = [] -stm32h747ig-cm4 = [] -stm32h747ii-cm7 = [] -stm32h747ii-cm4 = [] -stm32h747xg-cm7 = [] -stm32h747xg-cm4 = [] -stm32h747xi-cm7 = [] -stm32h747xi-cm4 = [] -stm32h747zi-cm7 = [] -stm32h747zi-cm4 = [] -stm32h750ib = [] -stm32h750vb = [] -stm32h750xb = [] -stm32h750zb = [] -stm32h753ai = [] -stm32h753bi = [] -stm32h753ii = [] -stm32h753vi = [] -stm32h753xi = [] -stm32h753zi = [] -stm32h755bi-cm7 = [] -stm32h755bi-cm4 = [] -stm32h755ii-cm7 = [] -stm32h755ii-cm4 = [] -stm32h755xi-cm7 = [] -stm32h755xi-cm4 = [] -stm32h755zi-cm7 = [] -stm32h755zi-cm4 = [] -stm32h757ai-cm7 = [] -stm32h757ai-cm4 = [] -stm32h757bi-cm7 = [] -stm32h757bi-cm4 = [] -stm32h757ii-cm7 = [] -stm32h757ii-cm4 = [] -stm32h757xi-cm7 = [] -stm32h757xi-cm4 = [] -stm32h757zi-cm7 = [] -stm32h757zi-cm4 = [] -stm32h7a3ag = [] -stm32h7a3ai = [] -stm32h7a3ig = [] -stm32h7a3ii = [] -stm32h7a3lg = [] -stm32h7a3li = [] -stm32h7a3ng = [] -stm32h7a3ni = [] -stm32h7a3qi = [] -stm32h7a3rg = [] -stm32h7a3ri = [] -stm32h7a3vg = [] -stm32h7a3vi = [] -stm32h7a3zg = [] -stm32h7a3zi = [] -stm32h7b0ab = [] -stm32h7b0ib = [] -stm32h7b0rb = [] -stm32h7b0vb = [] -stm32h7b0zb = [] -stm32h7b3ai = [] -stm32h7b3ii = [] -stm32h7b3li = [] -stm32h7b3ni = [] -stm32h7b3qi = [] -stm32h7b3ri = [] -stm32h7b3vi = [] -stm32h7b3zi = [] -stm32l010c6 = [] -stm32l010f4 = [] -stm32l010k4 = [] -stm32l010k8 = [] -stm32l010r8 = [] -stm32l010rb = [] -stm32l011d3 = [] -stm32l011d4 = [] -stm32l011e3 = [] -stm32l011e4 = [] -stm32l011f3 = [] -stm32l011f4 = [] -stm32l011g3 = [] -stm32l011g4 = [] -stm32l011k3 = [] -stm32l011k4 = [] -stm32l021d4 = [] -stm32l021f4 = [] -stm32l021g4 = [] -stm32l021k4 = [] -stm32l031c4 = [] -stm32l031c6 = [] -stm32l031e4 = [] -stm32l031e6 = [] -stm32l031f4 = [] -stm32l031f6 = [] -stm32l031g4 = [] -stm32l031g6 = [] -stm32l031k4 = [] -stm32l031k6 = [] -stm32l041c4 = [] -stm32l041c6 = [] -stm32l041e6 = [] -stm32l041f6 = [] -stm32l041g6 = [] -stm32l041k6 = [] -stm32l051c6 = [] -stm32l051c8 = [] -stm32l051k6 = [] -stm32l051k8 = [] -stm32l051r6 = [] -stm32l051r8 = [] -stm32l051t6 = [] -stm32l051t8 = [] -stm32l052c6 = [] -stm32l052c8 = [] -stm32l052k6 = [] -stm32l052k8 = [] -stm32l052r6 = [] -stm32l052r8 = [] -stm32l052t6 = [] -stm32l052t8 = [] -stm32l053c6 = [] -stm32l053c8 = [] -stm32l053r6 = [] -stm32l053r8 = [] -stm32l062c8 = [] -stm32l062k8 = [] -stm32l063c8 = [] -stm32l063r8 = [] -stm32l071c8 = [] -stm32l071cb = [] -stm32l071cz = [] -stm32l071k8 = [] -stm32l071kb = [] -stm32l071kz = [] -stm32l071rb = [] -stm32l071rz = [] -stm32l071v8 = [] -stm32l071vb = [] -stm32l071vz = [] -stm32l072cb = [] -stm32l072cz = [] -stm32l072kb = [] -stm32l072kz = [] -stm32l072rb = [] -stm32l072rz = [] -stm32l072v8 = [] -stm32l072vb = [] -stm32l072vz = [] -stm32l073cb = [] -stm32l073cz = [] -stm32l073rb = [] -stm32l073rz = [] -stm32l073v8 = [] -stm32l073vb = [] -stm32l073vz = [] -stm32l081cb = [] -stm32l081cz = [] -stm32l081kz = [] -stm32l082cz = [] -stm32l082kb = [] -stm32l082kz = [] -stm32l083cb = [] -stm32l083cz = [] -stm32l083rb = [] -stm32l083rz = [] -stm32l083v8 = [] -stm32l083vb = [] -stm32l083vz = [] -stm32l100c6-a = [] -stm32l100c6 = [] -stm32l100r8-a = [] -stm32l100r8 = [] -stm32l100rb-a = [] -stm32l100rb = [] -stm32l100rc = [] -stm32l151c6-a = [] -stm32l151c6 = [] -stm32l151c8-a = [] -stm32l151c8 = [] -stm32l151cb-a = [] -stm32l151cb = [] -stm32l151cc = [] -stm32l151qc = [] -stm32l151qd = [] -stm32l151qe = [] -stm32l151r6-a = [] -stm32l151r6 = [] -stm32l151r8-a = [] -stm32l151r8 = [] -stm32l151rb-a = [] -stm32l151rb = [] -stm32l151rc-a = [] -stm32l151rc = [] -stm32l151rd = [] -stm32l151re = [] -stm32l151uc = [] -stm32l151v8-a = [] -stm32l151v8 = [] -stm32l151vb-a = [] -stm32l151vb = [] -stm32l151vc-a = [] -stm32l151vc = [] -stm32l151vd-x = [] -stm32l151vd = [] -stm32l151ve = [] -stm32l151zc = [] -stm32l151zd = [] -stm32l151ze = [] -stm32l152c6-a = [] -stm32l152c6 = [] -stm32l152c8-a = [] -stm32l152c8 = [] -stm32l152cb-a = [] -stm32l152cb = [] -stm32l152cc = [] -stm32l152qc = [] -stm32l152qd = [] -stm32l152qe = [] -stm32l152r6-a = [] -stm32l152r6 = [] -stm32l152r8-a = [] -stm32l152r8 = [] -stm32l152rb-a = [] -stm32l152rb = [] -stm32l152rc-a = [] -stm32l152rc = [] -stm32l152rd = [] -stm32l152re = [] -stm32l152uc = [] -stm32l152v8-a = [] -stm32l152v8 = [] -stm32l152vb-a = [] -stm32l152vb = [] -stm32l152vc-a = [] -stm32l152vc = [] -stm32l152vd-x = [] -stm32l152vd = [] -stm32l152ve = [] -stm32l152zc = [] -stm32l152zd = [] -stm32l152ze = [] -stm32l162qc = [] -stm32l162qd = [] -stm32l162rc-a = [] -stm32l162rc = [] -stm32l162rd = [] -stm32l162re = [] -stm32l162vc-a = [] -stm32l162vc = [] -stm32l162vd-x = [] -stm32l162vd = [] -stm32l162ve = [] -stm32l162zc = [] -stm32l162zd = [] -stm32l162ze = [] -stm32l412c8 = [] -stm32l412cb = [] -stm32l412k8 = [] -stm32l412kb = [] -stm32l412r8 = [] -stm32l412rb = [] -stm32l412t8 = [] -stm32l412tb = [] -stm32l422cb = [] -stm32l422kb = [] -stm32l422rb = [] -stm32l422tb = [] -stm32l431cb = [] -stm32l431cc = [] -stm32l431kb = [] -stm32l431kc = [] -stm32l431rb = [] -stm32l431rc = [] -stm32l431vc = [] -stm32l432kb = [] -stm32l432kc = [] -stm32l433cb = [] -stm32l433cc = [] -stm32l433rb = [] -stm32l433rc = [] -stm32l433vc = [] -stm32l442kc = [] -stm32l443cc = [] -stm32l443rc = [] -stm32l443vc = [] -stm32l451cc = [] -stm32l451ce = [] -stm32l451rc = [] -stm32l451re = [] -stm32l451vc = [] -stm32l451ve = [] -stm32l452cc = [] -stm32l452ce = [] -stm32l452rc = [] -stm32l452re = [] -stm32l452vc = [] -stm32l452ve = [] -stm32l462ce = [] -stm32l462re = [] -stm32l462ve = [] -stm32l471qe = [] -stm32l471qg = [] -stm32l471re = [] -stm32l471rg = [] -stm32l471ve = [] -stm32l471vg = [] -stm32l471ze = [] -stm32l471zg = [] -stm32l475rc = [] -stm32l475re = [] -stm32l475rg = [] -stm32l475vc = [] -stm32l475ve = [] -stm32l475vg = [] -stm32l476je = [] -stm32l476jg = [] -stm32l476me = [] -stm32l476mg = [] -stm32l476qe = [] -stm32l476qg = [] -stm32l476rc = [] -stm32l476re = [] -stm32l476rg = [] -stm32l476vc = [] -stm32l476ve = [] -stm32l476vg = [] -stm32l476ze = [] -stm32l476zg = [] -stm32l486jg = [] -stm32l486qg = [] -stm32l486rg = [] -stm32l486vg = [] -stm32l486zg = [] -stm32l496ae = [] -stm32l496ag = [] -stm32l496qe = [] -stm32l496qg = [] -stm32l496re = [] -stm32l496rg = [] -stm32l496ve = [] -stm32l496vg = [] -stm32l496wg = [] -stm32l496ze = [] -stm32l496zg = [] -stm32l4a6ag = [] -stm32l4a6qg = [] -stm32l4a6rg = [] -stm32l4a6vg = [] -stm32l4a6zg = [] -stm32l4p5ae = [] -stm32l4p5ag = [] -stm32l4p5ce = [] -stm32l4p5cg = [] -stm32l4p5qe = [] -stm32l4p5qg = [] -stm32l4p5re = [] -stm32l4p5rg = [] -stm32l4p5ve = [] -stm32l4p5vg = [] -stm32l4p5ze = [] -stm32l4p5zg = [] -stm32l4q5ag = [] -stm32l4q5cg = [] -stm32l4q5qg = [] -stm32l4q5rg = [] -stm32l4q5vg = [] -stm32l4q5zg = [] -stm32l4r5ag = [] -stm32l4r5ai = [] -stm32l4r5qg = [] -stm32l4r5qi = [] -stm32l4r5vg = [] -stm32l4r5vi = [] -stm32l4r5zg = [] -stm32l4r5zi = [] -stm32l4r7ai = [] -stm32l4r7vi = [] -stm32l4r7zi = [] -stm32l4r9ag = [] -stm32l4r9ai = [] -stm32l4r9vg = [] -stm32l4r9vi = [] -stm32l4r9zg = [] -stm32l4r9zi = [] -stm32l4s5ai = [] -stm32l4s5qi = [] -stm32l4s5vi = [] -stm32l4s5zi = [] -stm32l4s7ai = [] -stm32l4s7vi = [] -stm32l4s7zi = [] -stm32l4s9ai = [] -stm32l4s9vi = [] -stm32l4s9zi = [] -stm32l552cc = [] -stm32l552ce = [] -stm32l552me = [] -stm32l552qc = [] -stm32l552qe = [] -stm32l552rc = [] -stm32l552re = [] -stm32l552vc = [] -stm32l552ve = [] -stm32l552zc = [] -stm32l552ze = [] -stm32l562ce = [] -stm32l562me = [] -stm32l562qe = [] -stm32l562re = [] -stm32l562ve = [] -stm32l562ze = [] -stm32u575ag = [] -stm32u575ai = [] -stm32u575cg = [] -stm32u575ci = [] -stm32u575og = [] -stm32u575oi = [] -stm32u575qg = [] -stm32u575qi = [] -stm32u575rg = [] -stm32u575ri = [] -stm32u575vg = [] -stm32u575vi = [] -stm32u575zg = [] -stm32u575zi = [] -stm32u585ai = [] -stm32u585ci = [] -stm32u585oi = [] -stm32u585qe = [] -stm32u585qi = [] -stm32u585ri = [] -stm32u585vi = [] -stm32u585ze = [] -stm32u585zi = [] -stm32wb10cc = [] -stm32wb15cc = [] -stm32wb30ce = [] -stm32wb35cc = [] -stm32wb35ce = [] -stm32wb50cg = [] -stm32wb55cc = [] -stm32wb55ce = [] -stm32wb55cg = [] -stm32wb55rc = [] -stm32wb55re = [] -stm32wb55rg = [] -stm32wb55vc = [] -stm32wb55ve = [] -stm32wb55vg = [] -stm32wb55vy = [] -stm32wb5mmg = [] -stm32wl54cc-cm4 = [] -stm32wl54cc-cm0p = [] -stm32wl54jc-cm4 = [] -stm32wl54jc-cm0p = [] -stm32wl55cc-cm4 = [] -stm32wl55cc-cm0p = [] -stm32wl55jc-cm4 = [] -stm32wl55jc-cm0p = [] -stm32wl55uc-cm4 = [] -stm32wl55uc-cm0p = [] -stm32wle4c8 = [] -stm32wle4cb = [] -stm32wle4cc = [] -stm32wle4j8 = [] -stm32wle4jb = [] -stm32wle4jc = [] -stm32wle5c8 = [] -stm32wle5cb = [] -stm32wle5cc = [] -stm32wle5j8 = [] -stm32wle5jb = [] -stm32wle5jc = [] -stm32wle5u8 = [] -stm32wle5ub = [] -# END GENERATED FEATURES diff --git a/stm32-metapac/build.rs b/stm32-metapac/build.rs deleted file mode 100644 index 0c183fa21..000000000 --- a/stm32-metapac/build.rs +++ /dev/null @@ -1,77 +0,0 @@ -use std::env; -use std::path::PathBuf; - -use stm32_metapac_gen::*; - -fn parse_chip_core(chip_and_core: &str) -> (String, Option) { - let mut s = chip_and_core.split('-'); - let chip_name: String = s.next().unwrap().to_string(); - if let Some(c) = s.next() { - if c.starts_with("cm") { - return (chip_name, Some(c.to_ascii_lowercase())); - } - } - - (chip_and_core.to_string(), None) -} - -fn main() { - let out_dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()); - let data_dir = PathBuf::from("../stm32-data/data"); - - let chip_core_name = env::vars_os() - .map(|(a, _)| a.to_string_lossy().to_string()) - .find(|x| x.starts_with("CARGO_FEATURE_STM32")) - .expect("No stm32xx Cargo feature enabled") - .strip_prefix("CARGO_FEATURE_") - .unwrap() - .to_ascii_lowercase() - .replace('_', "-"); - - let (chip_name, _) = parse_chip_core(&chip_core_name); - - let opts = Options { - out_dir: out_dir.clone(), - data_dir: data_dir.clone(), - chips: vec![chip_name.to_ascii_uppercase()], - }; - Gen::new(opts).gen(); - - println!( - "cargo:rustc-link-search={}/src/chips/{}", - out_dir.display(), - chip_core_name, - ); - - #[cfg(feature = "memory-x")] - println!( - "cargo:rustc-link-search={}/src/chips/{}/memory_x/", - out_dir.display(), - chip_core_name - ); - println!( - "cargo:rustc-env=STM32_METAPAC_PAC_PATH={}/src/chips/{}/pac.rs", - out_dir.display(), - chip_core_name - ); - println!( - "cargo:rustc-env=STM32_METAPAC_METADATA_PATH={}/src/chips/{}/metadata.rs", - out_dir.display(), - chip_core_name - ); - println!( - "cargo:rustc-env=STM32_METAPAC_COMMON_PATH={}/src/common.rs", - out_dir.display(), - ); - - println!("cargo:rerun-if-changed=build.rs"); - - // When the stm32-data chip's JSON changes, we must rebuild - println!( - "cargo:rerun-if-changed={}/chips/{}.json", - data_dir.display(), - chip_name.to_uppercase() - ); - - println!("cargo:rerun-if-changed={}/registers", data_dir.display()); -} diff --git a/stm32-metapac/build_pregenerated.rs b/stm32-metapac/build_pregenerated.rs deleted file mode 100644 index 0f0358071..000000000 --- a/stm32-metapac/build_pregenerated.rs +++ /dev/null @@ -1,39 +0,0 @@ -use std::env; -use std::path::PathBuf; - -fn main() { - let crate_dir = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap()); - - let chip_core_name = env::vars_os() - .map(|(a, _)| a.to_string_lossy().to_string()) - .find(|x| x.starts_with("CARGO_FEATURE_STM32")) - .expect("No stm32xx Cargo feature enabled") - .strip_prefix("CARGO_FEATURE_") - .unwrap() - .to_ascii_lowercase() - .replace('_', "-"); - - println!( - "cargo:rustc-link-search={}/src/chips/{}", - crate_dir.display(), - chip_core_name, - ); - - #[cfg(feature = "memory-x")] - println!( - "cargo:rustc-link-search={}/src/chips/{}/memory_x/", - crate_dir.display(), - chip_core_name - ); - println!("cargo:rustc-env=STM32_METAPAC_PAC_PATH=chips/{}/pac.rs", chip_core_name); - println!( - "cargo:rustc-env=STM32_METAPAC_METADATA_PATH=chips/{}/metadata.rs", - chip_core_name - ); - println!( - "cargo:rustc-env=STM32_METAPAC_COMMON_PATH={}/src/common.rs", - crate_dir.display(), - ); - - println!("cargo:rerun-if-changed=build.rs"); -} diff --git a/stm32-metapac/src/lib.rs b/stm32-metapac/src/lib.rs deleted file mode 100644 index 58a1c5e45..000000000 --- a/stm32-metapac/src/lib.rs +++ /dev/null @@ -1,18 +0,0 @@ -#![no_std] -#![allow(non_snake_case)] -#![allow(unused)] -#![allow(non_camel_case_types)] -#![doc(html_no_source)] - -pub mod common { - include!(env!("STM32_METAPAC_COMMON_PATH")); -} - -#[cfg(feature = "pac")] -include!(env!("STM32_METAPAC_PAC_PATH")); - -#[cfg(feature = "metadata")] -pub mod metadata { - include!("metadata.rs"); - include!(env!("STM32_METAPAC_METADATA_PATH")); -} diff --git a/stm32-metapac/src/metadata.rs b/stm32-metapac/src/metadata.rs deleted file mode 100644 index d05830e94..000000000 --- a/stm32-metapac/src/metadata.rs +++ /dev/null @@ -1,106 +0,0 @@ -#[derive(Debug, Eq, PartialEq, Clone)] -pub struct Metadata { - pub name: &'static str, - pub family: &'static str, - pub line: &'static str, - pub memory: &'static [MemoryRegion], - pub peripherals: &'static [Peripheral], - pub interrupts: &'static [Interrupt], - pub dma_channels: &'static [DmaChannel], -} - -#[derive(Debug, Eq, PartialEq, Clone)] -pub struct MemoryRegion { - pub name: &'static str, - pub kind: MemoryRegionKind, - pub address: u32, - pub size: u32, - pub settings: Option, -} - -#[derive(Debug, Eq, PartialEq, Clone)] -pub struct FlashSettings { - pub erase_size: u32, - pub write_size: u32, - pub erase_value: u8, -} - -#[derive(Debug, Eq, PartialEq, Clone)] -pub enum MemoryRegionKind { - Flash, - Ram, -} - -#[derive(Debug, Eq, PartialEq, Clone)] -pub struct Interrupt { - pub name: &'static str, - pub number: u32, -} - -#[derive(Debug, Eq, PartialEq, Clone)] -pub struct Package { - pub name: &'static str, - pub package: &'static str, -} - -#[derive(Debug, Eq, PartialEq, Clone)] -pub struct Peripheral { - pub name: &'static str, - pub address: u64, - pub registers: Option, - pub rcc: Option, - pub pins: &'static [PeripheralPin], - pub dma_channels: &'static [PeripheralDmaChannel], - pub interrupts: &'static [PeripheralInterrupt], -} - -#[derive(Debug, Eq, PartialEq, Clone)] -pub struct PeripheralRegisters { - pub kind: &'static str, - pub version: &'static str, - pub block: &'static str, -} - -#[derive(Debug, Eq, PartialEq, Clone)] -pub struct PeripheralInterrupt { - pub signal: &'static str, - pub interrupt: &'static str, -} - -#[derive(Debug, Eq, PartialEq, Clone)] -pub struct PeripheralRcc { - pub clock: &'static str, - pub enable: Option, - pub reset: Option, -} - -#[derive(Debug, Eq, PartialEq, Clone)] -pub struct PeripheralRccRegister { - pub register: &'static str, - pub field: &'static str, -} - -#[derive(Debug, Eq, PartialEq, Clone)] -pub struct PeripheralPin { - pub pin: &'static str, - pub signal: &'static str, - pub af: Option, -} - -#[derive(Debug, Eq, PartialEq, Clone)] -pub struct DmaChannel { - pub name: &'static str, - pub dma: &'static str, - pub channel: u32, - pub dmamux: Option<&'static str>, - pub dmamux_channel: Option, -} - -#[derive(Debug, Eq, PartialEq, Clone)] -pub struct PeripheralDmaChannel { - pub signal: &'static str, - pub channel: Option<&'static str>, - pub dmamux: Option<&'static str>, - pub dma: Option<&'static str>, - pub request: Option, -} diff --git a/tests/nrf/.cargo/config.toml b/tests/nrf/.cargo/config.toml new file mode 100644 index 000000000..03995f963 --- /dev/null +++ b/tests/nrf/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +#runner = "teleprobe local run --chip nRF52840_xxAA --elf" +runner = "teleprobe client run" + +[build] +target = "thumbv7em-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/tests/nrf/Cargo.toml b/tests/nrf/Cargo.toml new file mode 100644 index 000000000..7ce51aa5e --- /dev/null +++ b/tests/nrf/Cargo.toml @@ -0,0 +1,26 @@ +[package] +edition = "2021" +name = "embassy-nrf-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +teleprobe-meta = "1" + +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt", "nightly"] } +embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "nightly", "integrated-timers"] } +embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "nightly", "unstable-traits", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nightly", "unstable-traits", "nrf52840", "time-driver-rtc1", "gpiote", "unstable-pac"] } +embedded-io = { version = "0.4.0", features = ["async"] } +embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet", "nightly"] } +embassy-net-esp-hosted = { version = "0.1.0", path = "../../embassy-net-esp-hosted", features = ["defmt"] } +embedded-hal-async = { version = "0.2.0-alpha.2" } +static_cell = { version = "1.1", features = [ "nightly" ] } + +defmt = "0.3" +defmt-rtt = "0.4" + +cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m-rt = "0.7.0" +panic-probe = { version = "0.3", features = ["print-defmt"] } \ No newline at end of file diff --git a/tests/nrf/build.rs b/tests/nrf/build.rs new file mode 100644 index 000000000..93e2a28cf --- /dev/null +++ b/tests/nrf/build.rs @@ -0,0 +1,17 @@ +use std::error::Error; +use std::path::PathBuf; +use std::{env, fs}; + +fn main() -> Result<(), Box> { + let out = PathBuf::from(env::var("OUT_DIR").unwrap()); + fs::write(out.join("link_ram.x"), include_bytes!("link_ram.x")).unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + println!("cargo:rerun-if-changed=link_ram.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink_ram.x"); + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + println!("cargo:rustc-link-arg-bins=-Tteleprobe.x"); + + Ok(()) +} diff --git a/tests/nrf/link_ram.x b/tests/nrf/link_ram.x new file mode 100644 index 000000000..26da86baa --- /dev/null +++ b/tests/nrf/link_ram.x @@ -0,0 +1,254 @@ +/* ##### EMBASSY NOTE + Originally from https://github.com/rust-embedded/cortex-m-rt/blob/master/link.x.in + Adjusted to put everything in RAM +*/ + +/* # Developer notes + +- Symbols that start with a double underscore (__) are considered "private" + +- Symbols that start with a single underscore (_) are considered "semi-public"; they can be + overridden in a user linker script, but should not be referred from user code (e.g. `extern "C" { + static mut __sbss }`). + +- `EXTERN` forces the linker to keep a symbol in the final binary. We use this to make sure a + symbol if not dropped if it appears in or near the front of the linker arguments and "it's not + needed" by any of the preceding objects (linker arguments) + +- `PROVIDE` is used to provide default values that can be overridden by a user linker script + +- On alignment: it's important for correctness that the VMA boundaries of both .bss and .data *and* + the LMA of .data are all 4-byte aligned. These alignments are assumed by the RAM initialization + routine. There's also a second benefit: 4-byte aligned boundaries means that you won't see + "Address (..) is out of bounds" in the disassembly produced by `objdump`. +*/ + +/* Provides information about the memory layout of the device */ +/* This will be provided by the user (see `memory.x`) or by a Board Support Crate */ +INCLUDE memory.x + +/* # Entry point = reset vector */ +EXTERN(__RESET_VECTOR); +EXTERN(Reset); +ENTRY(Reset); + +/* # Exception vectors */ +/* This is effectively weak aliasing at the linker level */ +/* The user can override any of these aliases by defining the corresponding symbol themselves (cf. + the `exception!` macro) */ +EXTERN(__EXCEPTIONS); /* depends on all the these PROVIDED symbols */ + +EXTERN(DefaultHandler); + +PROVIDE(NonMaskableInt = DefaultHandler); +EXTERN(HardFaultTrampoline); +PROVIDE(MemoryManagement = DefaultHandler); +PROVIDE(BusFault = DefaultHandler); +PROVIDE(UsageFault = DefaultHandler); +PROVIDE(SecureFault = DefaultHandler); +PROVIDE(SVCall = DefaultHandler); +PROVIDE(DebugMonitor = DefaultHandler); +PROVIDE(PendSV = DefaultHandler); +PROVIDE(SysTick = DefaultHandler); + +PROVIDE(DefaultHandler = DefaultHandler_); +PROVIDE(HardFault = HardFault_); + +/* # Interrupt vectors */ +EXTERN(__INTERRUPTS); /* `static` variable similar to `__EXCEPTIONS` */ + +/* # Pre-initialization function */ +/* If the user overrides this using the `pre_init!` macro or by creating a `__pre_init` function, + then the function this points to will be called before the RAM is initialized. */ +PROVIDE(__pre_init = DefaultPreInit); + +/* # Sections */ +SECTIONS +{ + PROVIDE(_stack_start = ORIGIN(RAM) + LENGTH(RAM)); + + /* ## Sections in RAM */ + /* ### Vector table */ + .vector_table ORIGIN(RAM) : + { + /* Initial Stack Pointer (SP) value */ + LONG(_stack_start); + + /* Reset vector */ + KEEP(*(.vector_table.reset_vector)); /* this is the `__RESET_VECTOR` symbol */ + __reset_vector = .; + + /* Exceptions */ + KEEP(*(.vector_table.exceptions)); /* this is the `__EXCEPTIONS` symbol */ + __eexceptions = .; + + /* Device specific interrupts */ + KEEP(*(.vector_table.interrupts)); /* this is the `__INTERRUPTS` symbol */ + } > RAM + + PROVIDE(_stext = ADDR(.vector_table) + SIZEOF(.vector_table)); + + /* ### .text */ + .text _stext : + { + __stext = .; + *(.Reset); + + *(.text .text.*); + + /* The HardFaultTrampoline uses the `b` instruction to enter `HardFault`, + so must be placed close to it. */ + *(.HardFaultTrampoline); + *(.HardFault.*); + + . = ALIGN(4); /* Pad .text to the alignment to workaround overlapping load section bug in old lld */ + __etext = .; + } > RAM + + /* ### .rodata */ + .rodata : ALIGN(4) + { + . = ALIGN(4); + __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); + __erodata = .; + } > RAM + + /* ## Sections in RAM */ + /* ### .data */ + .data : ALIGN(4) + { + . = ALIGN(4); + __sdata = .; + __edata = .; + *(.data .data.*); + . = ALIGN(4); /* 4-byte align the end (VMA) of this section */ + } > RAM + /* Allow sections from user `memory.x` injected using `INSERT AFTER .data` to + * use the .data loading mechanism by pushing __edata. Note: do not change + * output region or load region in those user sections! */ + . = ALIGN(4); + + /* LMA of .data */ + __sidata = LOADADDR(.data); + + /* ### .gnu.sgstubs + This section contains the TrustZone-M veneers put there by the Arm GNU linker. */ + /* Security Attribution Unit blocks must be 32 bytes aligned. */ + /* Note that this pads the RAM usage to 32 byte alignment. */ + .gnu.sgstubs : ALIGN(32) + { + . = ALIGN(32); + __veneer_base = .; + *(.gnu.sgstubs*) + . = ALIGN(32); + __veneer_limit = .; + } > RAM + + /* ### .bss */ + .bss (NOLOAD) : ALIGN(4) + { + . = ALIGN(4); + __sbss = .; + *(.bss .bss.*); + *(COMMON); /* Uninitialized C statics */ + . = ALIGN(4); /* 4-byte align the end (VMA) of this section */ + } > RAM + /* Allow sections from user `memory.x` injected using `INSERT AFTER .bss` to + * use the .bss zeroing mechanism by pushing __ebss. Note: do not change + * output region or load region in those user sections! */ + . = ALIGN(4); + __ebss = .; + + /* ### .uninit */ + .uninit (NOLOAD) : ALIGN(4) + { + . = ALIGN(4); + __suninit = .; + *(.uninit .uninit.*); + . = ALIGN(4); + __euninit = .; + } > RAM + + /* Place the heap right after `.uninit` in RAM */ + PROVIDE(__sheap = __euninit); + + /* ## .got */ + /* 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 (NOLOAD) : + { + KEEP(*(.got .got.*)); + } + + /* ## Discarded sections */ + /DISCARD/ : + { + /* Unused exception related info that only wastes space */ + *(.ARM.exidx); + *(.ARM.exidx.*); + *(.ARM.extab.*); + } +} + +/* Do not exceed this mark in the error messages below | */ +/* # Alignment checks */ +ASSERT(ORIGIN(RAM) % 4 == 0, " +ERROR(cortex-m-rt): the start of the RAM region must be 4-byte aligned"); + +ASSERT(__sdata % 4 == 0 && __edata % 4 == 0, " +BUG(cortex-m-rt): .data is not 4-byte aligned"); + +ASSERT(__sidata % 4 == 0, " +BUG(cortex-m-rt): the LMA of .data is not 4-byte aligned"); + +ASSERT(__sbss % 4 == 0 && __ebss % 4 == 0, " +BUG(cortex-m-rt): .bss is not 4-byte aligned"); + +ASSERT(__sheap % 4 == 0, " +BUG(cortex-m-rt): start of .heap is not 4-byte aligned"); + +/* # Position checks */ + +/* ## .vector_table */ +ASSERT(__reset_vector == ADDR(.vector_table) + 0x8, " +BUG(cortex-m-rt): the reset vector is missing"); + +ASSERT(__eexceptions == ADDR(.vector_table) + 0x40, " +BUG(cortex-m-rt): the exception vectors are missing"); + +ASSERT(SIZEOF(.vector_table) > 0x40, " +ERROR(cortex-m-rt): The interrupt vectors are missing. +Possible solutions, from most likely to less likely: +- Link to a svd2rust generated device crate +- Check that you actually use the device/hal/bsp crate in your code +- Disable the 'device' feature of cortex-m-rt to build a generic application (a dependency +may be enabling it) +- Supply the interrupt handlers yourself. Check the documentation for details."); + +/* ## .text */ +ASSERT(ADDR(.vector_table) + SIZEOF(.vector_table) <= _stext, " +ERROR(cortex-m-rt): The .text section can't be placed inside the .vector_table section +Set _stext to an address greater than the end of .vector_table (See output of `nm`)"); + +ASSERT(_stext + SIZEOF(.text) < ORIGIN(RAM) + LENGTH(RAM), " +ERROR(cortex-m-rt): The .text section must be placed inside the RAM memory. +Set _stext to an address smaller than 'ORIGIN(RAM) + LENGTH(RAM)'"); + +/* # Other checks */ +ASSERT(SIZEOF(.got) == 0, " +ERROR(cortex-m-rt): .got section detected in the input object files +Dynamic relocations are not supported. If you are linking to C code compiled using +the 'cc' crate then modify your build script to compile the C code _without_ +the -fPIC flag. See the documentation of the `cc::Build.pic` method for details."); +/* Do not exceed this mark in the error messages above | */ + + +/* Provides weak aliases (cf. PROVIDED) for device specific interrupt handlers */ +/* This will usually be provided by a device crate generated using svd2rust (see `device.x`) */ +INCLUDE device.x \ No newline at end of file diff --git a/tests/nrf/memory.x b/tests/nrf/memory.x new file mode 100644 index 000000000..58900a7bd --- /dev/null +++ b/tests/nrf/memory.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x00000000, LENGTH = 1024K + RAM : ORIGIN = 0x20000000, LENGTH = 256K +} diff --git a/tests/nrf/src/bin/buffered_uart.rs b/tests/nrf/src/bin/buffered_uart.rs new file mode 100644 index 000000000..72a4cb4ef --- /dev/null +++ b/tests/nrf/src/bin/buffered_uart.rs @@ -0,0 +1,80 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; + +use defmt::{assert_eq, *}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_nrf::buffered_uarte::{self, BufferedUarte}; +use embassy_nrf::{bind_interrupts, peripherals, uarte}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + UARTE0_UART0 => buffered_uarte::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + let mut config = uarte::Config::default(); + config.parity = uarte::Parity::EXCLUDED; + config.baudrate = uarte::Baudrate::BAUD1M; + + let mut tx_buffer = [0u8; 1024]; + let mut rx_buffer = [0u8; 1024]; + + let mut u = BufferedUarte::new( + p.UARTE0, + p.TIMER0, + p.PPI_CH0, + p.PPI_CH1, + p.PPI_GROUP0, + Irqs, + p.P1_03, + p.P1_02, + config.clone(), + &mut rx_buffer, + &mut tx_buffer, + ); + + info!("uarte initialized!"); + + let (mut rx, mut tx) = u.split(); + + const COUNT: usize = 40_000; + + let tx_fut = async { + let mut tx_buf = [0; 215]; + let mut i = 0; + while i < COUNT { + let n = tx_buf.len().min(COUNT - i); + let tx_buf = &mut tx_buf[..n]; + for (j, b) in tx_buf.iter_mut().enumerate() { + *b = (i + j) as u8; + } + let n = unwrap!(tx.write(tx_buf).await); + i += n; + } + }; + let rx_fut = async { + let mut i = 0; + while i < COUNT { + let buf = unwrap!(rx.fill_buf().await); + + for &b in buf { + assert_eq!(b, i as u8); + i = i + 1; + } + + let n = buf.len(); + rx.consume(n); + } + }; + + join(rx_fut, tx_fut).await; + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/tests/nrf/src/bin/buffered_uart_spam.rs b/tests/nrf/src/bin/buffered_uart_spam.rs new file mode 100644 index 000000000..50960206f --- /dev/null +++ b/tests/nrf/src/bin/buffered_uart_spam.rs @@ -0,0 +1,93 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; + +use core::mem; +use core::ptr::NonNull; + +use defmt::{assert_eq, *}; +use embassy_executor::Spawner; +use embassy_nrf::buffered_uarte::{self, BufferedUarte}; +use embassy_nrf::gpio::{Level, Output, OutputDrive}; +use embassy_nrf::ppi::{Event, Ppi, Task}; +use embassy_nrf::uarte::Uarte; +use embassy_nrf::{bind_interrupts, pac, peripherals, uarte}; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + UARTE0_UART0 => buffered_uarte::InterruptHandler; + UARTE1 => uarte::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut p = embassy_nrf::init(Default::default()); + let mut config = uarte::Config::default(); + config.parity = uarte::Parity::EXCLUDED; + config.baudrate = uarte::Baudrate::BAUD1M; + + let mut tx_buffer = [0u8; 1024]; + let mut rx_buffer = [0u8; 1024]; + + mem::forget(Output::new(&mut p.P1_02, Level::High, OutputDrive::Standard)); + + let mut u = BufferedUarte::new( + p.UARTE0, + p.TIMER0, + p.PPI_CH0, + p.PPI_CH1, + p.PPI_GROUP0, + Irqs, + p.P1_03, + p.P1_04, + config.clone(), + &mut rx_buffer, + &mut tx_buffer, + ); + + info!("uarte initialized!"); + + // uarte needs some quiet time to start rxing properly. + Timer::after(Duration::from_millis(10)).await; + + // Tx spam in a loop. + const NSPAM: usize = 17; + static mut TX_BUF: [u8; NSPAM] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; + let _spam = Uarte::new(p.UARTE1, Irqs, p.P1_01, p.P1_02, config.clone()); + let spam_peri: pac::UARTE1 = unsafe { mem::transmute(()) }; + let event = unsafe { Event::new_unchecked(NonNull::new_unchecked(&spam_peri.events_endtx as *const _ as _)) }; + let task = unsafe { Task::new_unchecked(NonNull::new_unchecked(&spam_peri.tasks_starttx as *const _ as _)) }; + let mut spam_ppi = Ppi::new_one_to_one(p.PPI_CH2, event, task); + spam_ppi.enable(); + let p = unsafe { TX_BUF.as_mut_ptr() }; + spam_peri.txd.ptr.write(|w| unsafe { w.ptr().bits(p as u32) }); + spam_peri.txd.maxcnt.write(|w| unsafe { w.maxcnt().bits(NSPAM as _) }); + spam_peri.tasks_starttx.write(|w| unsafe { w.bits(1) }); + + let mut i = 0; + let mut total = 0; + while total < 256 * 1024 { + let buf = unwrap!(u.fill_buf().await); + //info!("rx {}", buf); + + for &b in buf { + assert_eq!(b, unsafe { TX_BUF[i] }); + + i = i + 1; + if i == NSPAM { + i = 0; + } + } + + // Read bytes have to be explicitly consumed, otherwise fill_buf() will return them again + let n = buf.len(); + u.consume(n); + total += n; + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/tests/nrf/src/bin/timer.rs b/tests/nrf/src/bin/timer.rs new file mode 100644 index 000000000..607c5bbf1 --- /dev/null +++ b/tests/nrf/src/bin/timer.rs @@ -0,0 +1,27 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; + +use defmt::{assert, info}; +use embassy_executor::Spawner; +use embassy_time::{Duration, Instant, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let _p = embassy_nrf::init(Default::default()); + info!("Hello World!"); + + let start = Instant::now(); + Timer::after(Duration::from_millis(100)).await; + let end = Instant::now(); + let ms = (end - start).as_millis(); + info!("slept for {} ms", ms); + assert!(ms >= 99); + assert!(ms < 110); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/tests/nrf/src/bin/wifi_esp_hosted_perf.rs b/tests/nrf/src/bin/wifi_esp_hosted_perf.rs new file mode 100644 index 000000000..398ab9d27 --- /dev/null +++ b/tests/nrf/src/bin/wifi_esp_hosted_perf.rs @@ -0,0 +1,270 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +#[path = "../common.rs"] +mod common; + +use defmt::{error, info, unwrap}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Config, Ipv4Address, Stack, StackResources}; +use embassy_nrf::gpio::{AnyPin, Input, Level, Output, OutputDrive, Pin, Pull}; +use embassy_nrf::rng::Rng; +use embassy_nrf::spim::{self, Spim}; +use embassy_nrf::{bind_interrupts, peripherals}; +use embassy_time::{with_timeout, Delay, Duration, Timer}; +use embedded_hal_async::spi::ExclusiveDevice; +use static_cell::make_static; +use {defmt_rtt as _, embassy_net_esp_hosted as hosted, panic_probe as _}; + +teleprobe_meta::timeout!(120); + +bind_interrupts!(struct Irqs { + SPIM3 => spim::InterruptHandler; + RNG => embassy_nrf::rng::InterruptHandler; +}); + +#[embassy_executor::task] +async fn wifi_task( + runner: hosted::Runner< + 'static, + ExclusiveDevice, Output<'static, peripherals::P0_31>, Delay>, + Input<'static, AnyPin>, + Output<'static, peripherals::P1_05>, + >, +) -> ! { + runner.run().await +} + +type MyDriver = hosted::NetDriver<'static>; + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + info!("Hello World!"); + + let p = embassy_nrf::init(Default::default()); + + let miso = p.P0_28; + let sck = p.P0_29; + let mosi = p.P0_30; + let cs = Output::new(p.P0_31, Level::High, OutputDrive::HighDrive); + let handshake = Input::new(p.P1_01.degrade(), Pull::Up); + let ready = Input::new(p.P1_04.degrade(), Pull::None); + let reset = Output::new(p.P1_05, Level::Low, OutputDrive::Standard); + + let mut config = spim::Config::default(); + config.frequency = spim::Frequency::M32; + config.mode = spim::MODE_2; // !!! + let spi = spim::Spim::new(p.SPI3, Irqs, sck, miso, mosi, config); + let spi = ExclusiveDevice::new(spi, cs, Delay); + + let (device, mut control, runner) = embassy_net_esp_hosted::new( + make_static!(embassy_net_esp_hosted::State::new()), + spi, + handshake, + ready, + reset, + ) + .await; + + unwrap!(spawner.spawn(wifi_task(runner))); + + control.init().await; + control.join(WIFI_NETWORK, WIFI_PASSWORD).await; + + // Generate random seed + let mut rng = Rng::new(p.RNG, Irqs); + let mut seed = [0; 8]; + rng.blocking_fill_bytes(&mut seed); + let seed = u64::from_le_bytes(seed); + + // Init network stack + let stack = &*make_static!(Stack::new( + device, + Config::dhcpv4(Default::default()), + make_static!(StackResources::<2>::new()), + seed + )); + + unwrap!(spawner.spawn(net_task(stack))); + + info!("Waiting for DHCP up..."); + while stack.config_v4().is_none() { + Timer::after(Duration::from_millis(100)).await; + } + info!("IP addressing up!"); + + let down = test_download(stack).await; + let up = test_upload(stack).await; + let updown = test_upload_download(stack).await; + + assert!(down > TEST_EXPECTED_DOWNLOAD_KBPS); + assert!(up > TEST_EXPECTED_UPLOAD_KBPS); + assert!(updown > TEST_EXPECTED_UPLOAD_DOWNLOAD_KBPS); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} + +// Test-only wifi network, no internet access! +const WIFI_NETWORK: &str = "EmbassyTest"; +const WIFI_PASSWORD: &str = "V8YxhKt5CdIAJFud"; + +const TEST_DURATION: usize = 10; +const TEST_EXPECTED_DOWNLOAD_KBPS: usize = 150; +const TEST_EXPECTED_UPLOAD_KBPS: usize = 150; +const TEST_EXPECTED_UPLOAD_DOWNLOAD_KBPS: usize = 150; +const RX_BUFFER_SIZE: usize = 4096; +const TX_BUFFER_SIZE: usize = 4096; +const SERVER_ADDRESS: Ipv4Address = Ipv4Address::new(192, 168, 2, 2); +const DOWNLOAD_PORT: u16 = 4321; +const UPLOAD_PORT: u16 = 4322; +const UPLOAD_DOWNLOAD_PORT: u16 = 4323; + +async fn test_download(stack: &'static Stack) -> usize { + info!("Testing download..."); + + let mut rx_buffer = [0; RX_BUFFER_SIZE]; + let mut tx_buffer = [0; TX_BUFFER_SIZE]; + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + + info!("connecting to {:?}:{}...", SERVER_ADDRESS, DOWNLOAD_PORT); + if let Err(e) = socket.connect((SERVER_ADDRESS, DOWNLOAD_PORT)).await { + error!("connect error: {:?}", e); + return 0; + } + info!("connected, testing..."); + + let mut rx_buf = [0; 4096]; + let mut total: usize = 0; + with_timeout(Duration::from_secs(TEST_DURATION as _), async { + loop { + match socket.read(&mut rx_buf).await { + Ok(0) => { + error!("read EOF"); + return 0; + } + Ok(n) => total += n, + Err(e) => { + error!("read error: {:?}", e); + return 0; + } + } + } + }) + .await + .ok(); + + let kbps = (total + 512) / 1024 / TEST_DURATION; + info!("download: {} kB/s", kbps); + kbps +} + +async fn test_upload(stack: &'static Stack) -> usize { + info!("Testing upload..."); + + let mut rx_buffer = [0; RX_BUFFER_SIZE]; + let mut tx_buffer = [0; TX_BUFFER_SIZE]; + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + + info!("connecting to {:?}:{}...", SERVER_ADDRESS, UPLOAD_PORT); + if let Err(e) = socket.connect((SERVER_ADDRESS, UPLOAD_PORT)).await { + error!("connect error: {:?}", e); + return 0; + } + info!("connected, testing..."); + + let buf = [0; 4096]; + let mut total: usize = 0; + with_timeout(Duration::from_secs(TEST_DURATION as _), async { + loop { + match socket.write(&buf).await { + Ok(0) => { + error!("write zero?!??!?!"); + return 0; + } + Ok(n) => total += n, + Err(e) => { + error!("write error: {:?}", e); + return 0; + } + } + } + }) + .await + .ok(); + + let kbps = (total + 512) / 1024 / TEST_DURATION; + info!("upload: {} kB/s", kbps); + kbps +} + +async fn test_upload_download(stack: &'static Stack) -> usize { + info!("Testing upload+download..."); + + let mut rx_buffer = [0; RX_BUFFER_SIZE]; + let mut tx_buffer = [0; TX_BUFFER_SIZE]; + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + + info!("connecting to {:?}:{}...", SERVER_ADDRESS, UPLOAD_DOWNLOAD_PORT); + if let Err(e) = socket.connect((SERVER_ADDRESS, UPLOAD_DOWNLOAD_PORT)).await { + error!("connect error: {:?}", e); + return 0; + } + info!("connected, testing..."); + + let (mut reader, mut writer) = socket.split(); + + let tx_buf = [0; 4096]; + let mut rx_buf = [0; 4096]; + let mut total: usize = 0; + let tx_fut = async { + loop { + match writer.write(&tx_buf).await { + Ok(0) => { + error!("write zero?!??!?!"); + return 0; + } + Ok(_) => {} + Err(e) => { + error!("write error: {:?}", e); + return 0; + } + } + } + }; + + let rx_fut = async { + loop { + match reader.read(&mut rx_buf).await { + Ok(0) => { + error!("read EOF"); + return 0; + } + Ok(n) => total += n, + Err(e) => { + error!("read error: {:?}", e); + return 0; + } + } + } + }; + + with_timeout(Duration::from_secs(TEST_DURATION as _), join(tx_fut, rx_fut)) + .await + .ok(); + + let kbps = (total + 512) / 1024 / TEST_DURATION; + info!("upload+download: {} kB/s", kbps); + kbps +} diff --git a/tests/nrf/src/common.rs b/tests/nrf/src/common.rs new file mode 100644 index 000000000..1a05ac1c5 --- /dev/null +++ b/tests/nrf/src/common.rs @@ -0,0 +1 @@ +teleprobe_meta::target!(b"nrf52840-dk"); diff --git a/tests/perf-server/Cargo.toml b/tests/perf-server/Cargo.toml new file mode 100644 index 000000000..532039050 --- /dev/null +++ b/tests/perf-server/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "perf-server" +version = "0.1.0" +edition = "2021" + +[dependencies] +log = "0.4.17" +pretty_env_logger = "0.4.0" diff --git a/tests/perf-server/deploy.sh b/tests/perf-server/deploy.sh new file mode 100755 index 000000000..032e99c30 --- /dev/null +++ b/tests/perf-server/deploy.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +set -euxo pipefail + +HOST=root@192.168.1.3 + +cargo build --release +ssh $HOST -- systemctl stop perf-server +scp target/release/perf-server $HOST:/root +scp perf-server.service $HOST:/etc/systemd/system/ +ssh $HOST -- 'systemctl daemon-reload; systemctl restart perf-server' \ No newline at end of file diff --git a/tests/perf-server/perf-server.service b/tests/perf-server/perf-server.service new file mode 100644 index 000000000..c14c5d16f --- /dev/null +++ b/tests/perf-server/perf-server.service @@ -0,0 +1,16 @@ +[Unit] +Description=perf-server +After=network.target +StartLimitIntervalSec=0 + +[Service] +Type=simple +Restart=always +RestartSec=1 +User=root +ExecStart=/root/perf-server +Environment=RUST_BACKTRACE=1 +Environment=RUST_LOG=info + +[Install] +WantedBy=multi-user.target diff --git a/tests/perf-server/src/main.rs b/tests/perf-server/src/main.rs new file mode 100644 index 000000000..f6e7efc59 --- /dev/null +++ b/tests/perf-server/src/main.rs @@ -0,0 +1,90 @@ +use std::io::{Read, Write}; +use std::net::{TcpListener, TcpStream}; +use std::thread::spawn; +use std::time::Duration; + +use log::info; + +fn main() { + pretty_env_logger::init(); + spawn(|| rx_listen()); + spawn(|| rxtx_listen()); + tx_listen(); +} + +fn tx_listen() { + info!("tx: listening on 0.0.0.0:4321"); + let listener = TcpListener::bind("0.0.0.0:4321").unwrap(); + loop { + let (socket, addr) = listener.accept().unwrap(); + info!("tx: received connection from: {}", addr); + spawn(|| tx_conn(socket)); + } +} + +fn tx_conn(mut socket: TcpStream) { + socket.set_read_timeout(Some(Duration::from_secs(30))).unwrap(); + socket.set_write_timeout(Some(Duration::from_secs(30))).unwrap(); + + let buf = [0; 1024]; + loop { + if let Err(e) = socket.write_all(&buf) { + info!("tx: failed to write to socket; err = {:?}", e); + return; + } + } +} + +fn rx_listen() { + info!("rx: listening on 0.0.0.0:4322"); + let listener = TcpListener::bind("0.0.0.0:4322").unwrap(); + loop { + let (socket, addr) = listener.accept().unwrap(); + info!("rx: received connection from: {}", addr); + spawn(|| rx_conn(socket)); + } +} + +fn rx_conn(mut socket: TcpStream) { + socket.set_read_timeout(Some(Duration::from_secs(30))).unwrap(); + socket.set_write_timeout(Some(Duration::from_secs(30))).unwrap(); + + let mut buf = [0; 1024]; + loop { + if let Err(e) = socket.read_exact(&mut buf) { + info!("rx: failed to read from socket; err = {:?}", e); + return; + } + } +} + +fn rxtx_listen() { + info!("rxtx: listening on 0.0.0.0:4323"); + let listener = TcpListener::bind("0.0.0.0:4323").unwrap(); + loop { + let (socket, addr) = listener.accept().unwrap(); + info!("rxtx: received connection from: {}", addr); + spawn(|| rxtx_conn(socket)); + } +} + +fn rxtx_conn(mut socket: TcpStream) { + socket.set_read_timeout(Some(Duration::from_secs(30))).unwrap(); + socket.set_write_timeout(Some(Duration::from_secs(30))).unwrap(); + + let mut buf = [0; 1024]; + loop { + match socket.read(&mut buf) { + Ok(n) => { + if let Err(e) = socket.write_all(&buf[..n]) { + info!("rxtx: failed to write to socket; err = {:?}", e); + return; + } + } + Err(e) => { + info!("rxtx: failed to read from socket; err = {:?}", e); + return; + } + } + } +} diff --git a/tests/riscv32/.cargo/config.toml b/tests/riscv32/.cargo/config.toml new file mode 100644 index 000000000..58299b54e --- /dev/null +++ b/tests/riscv32/.cargo/config.toml @@ -0,0 +1,5 @@ +[target.riscv32imac-unknown-none-elf] +runner = "true" + +[build] +target = "riscv32imac-unknown-none-elf" diff --git a/tests/riscv32/Cargo.toml b/tests/riscv32/Cargo.toml new file mode 100644 index 000000000..61f886c0c --- /dev/null +++ b/tests/riscv32/Cargo.toml @@ -0,0 +1,46 @@ +[package] +edition = "2021" +name = "embassy-riscv-tests" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +critical-section = { version = "1.1.1", features = ["restore-state-bool"] } +embassy-sync = { version = "0.2.0", path = "../../embassy-sync" } +embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["arch-riscv32", "nightly", "executor-thread"] } +embassy-time = { version = "0.1.2", path = "../../embassy-time" } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } + +riscv-rt = "0.11" +riscv = { version = "0.10", features = ["critical-section-single-hart"] } + + +[profile.dev] +debug = 2 +debug-assertions = true +opt-level = 's' +overflow-checks = true + +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false +incremental = false +lto = "fat" +opt-level = 's' +overflow-checks = false + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false + +[profile.release.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false diff --git a/tests/riscv32/build.rs b/tests/riscv32/build.rs new file mode 100644 index 000000000..e4a26c4a1 --- /dev/null +++ b/tests/riscv32/build.rs @@ -0,0 +1,8 @@ +use std::error::Error; + +fn main() -> Result<(), Box> { + println!("cargo:rustc-link-arg-bins=-Tmemory.x"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + + Ok(()) +} diff --git a/tests/riscv32/memory.x b/tests/riscv32/memory.x new file mode 100644 index 000000000..316d577d4 --- /dev/null +++ b/tests/riscv32/memory.x @@ -0,0 +1,14 @@ +MEMORY +{ + ROM : ORIGIN = 0x80000000, LENGTH = 0x00020000 + RAM : ORIGIN = 0x84000000, LENGTH = 0x00008000 +} + +REGION_ALIAS("REGION_TEXT", ROM); +REGION_ALIAS("REGION_RODATA", ROM); +REGION_ALIAS("REGION_DATA", RAM); +REGION_ALIAS("REGION_BSS", RAM); +REGION_ALIAS("REGION_HEAP", RAM); +REGION_ALIAS("REGION_STACK", RAM); + +_stack_start = ORIGIN(RAM) + LENGTH(RAM) - 4; diff --git a/tests/riscv32/src/bin/empty.rs b/tests/riscv32/src/bin/empty.rs new file mode 100644 index 000000000..1874caec4 --- /dev/null +++ b/tests/riscv32/src/bin/empty.rs @@ -0,0 +1,16 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use embassy_executor::Spawner; + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + loop {} +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // Don't do anything, just make sure it compiles. + loop {} +} diff --git a/tests/rp/.cargo/config.toml b/tests/rp/.cargo/config.toml index 0330025e4..bc92e788b 100644 --- a/tests/rp/.cargo/config.toml +++ b/tests/rp/.cargo/config.toml @@ -1,10 +1,12 @@ [unstable] -build-std = ["core"] -build-std-features = ["panic_immediate_abort"] +# enabling these breaks the float tests during linking, with intrinsics +# duplicated between embassy-rp and compilter_builtins +#build-std = ["core"] +#build-std-features = ["panic_immediate_abort"] [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -#runner = "teleprobe client run --target bluepill-stm32f103c8 --elf" -runner = "teleprobe local run --chip RP2040 --elf" +runner = "teleprobe client run" +#runner = "teleprobe local run --chip RP2040 --elf" rustflags = [ # Code-size optimizations. diff --git a/tests/rp/Cargo.toml b/tests/rp/Cargo.toml index 4d6877ccd..f2c902787 100644 --- a/tests/rp/Cargo.toml +++ b/tests/rp/Cargo.toml @@ -2,23 +2,35 @@ edition = "2021" name = "embassy-rp-tests" version = "0.1.0" +license = "MIT OR Apache-2.0" [dependencies] -embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } -embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt"] } -embassy-rp = { version = "0.1.0", path = "../../embassy-rp", features = ["nightly", "defmt", "unstable-pac", "unstable-traits"] } +teleprobe-meta = "1.1" + +embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt"] } +embassy-rp = { version = "0.1.0", path = "../../embassy-rp", features = ["nightly", "defmt", "unstable-pac", "unstable-traits", "time-driver", "critical-section-impl", "intrinsics", "rom-v2-intrinsics", "run-from-ram"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +embassy-net = { version = "0.1.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "udp", "dhcpv4", "medium-ethernet"] } +cyw43 = { path = "../../cyw43", features = ["defmt", "firmware-logs"] } +cyw43-pio = { path = "../../cyw43-pio", features = ["defmt", "overclock"] } defmt = "0.3.0" -defmt-rtt = "0.3.0" +defmt-rtt = "0.4" -cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m = { version = "0.7.6" } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" -embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.8" } -embedded-hal-async = { version = "0.1.0-alpha.1" } +embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11" } +embedded-hal-async = { version = "=0.2.0-alpha.2" } panic-probe = { version = "0.3.0", features = ["print-defmt"] } futures = { version = "0.3.17", default-features = false, features = ["async-await"] } +embedded-io = { version = "0.4.0", features = ["async"] } +embedded-storage = { version = "0.3" } +static_cell = { version = "1.1", features = ["nightly"]} +pio = "0.2" +pio-proc = "0.2" [profile.dev] debug = 2 diff --git a/tests/rp/build.rs b/tests/rp/build.rs index 6f4872249..93e2a28cf 100644 --- a/tests/rp/build.rs +++ b/tests/rp/build.rs @@ -11,6 +11,7 @@ fn main() -> Result<(), Box> { println!("cargo:rustc-link-arg-bins=--nmagic"); println!("cargo:rustc-link-arg-bins=-Tlink_ram.x"); println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + println!("cargo:rustc-link-arg-bins=-Tteleprobe.x"); Ok(()) } diff --git a/tests/rp/src/bin/adc.rs b/tests/rp/src/bin/adc.rs new file mode 100644 index 000000000..e659844ae --- /dev/null +++ b/tests/rp/src/bin/adc.rs @@ -0,0 +1,86 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::adc::{Adc, Config, InterruptHandler, Pin}; +use embassy_rp::bind_interrupts; +use embassy_rp::gpio::Pull; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + ADC_IRQ_FIFO => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut p = embassy_rp::init(Default::default()); + let mut adc = Adc::new(p.ADC, Irqs, Config::default()); + + { + { + let mut p = Pin::new(&mut p.PIN_26, Pull::Down); + defmt::assert!(adc.blocking_read(&mut p).unwrap() < 0b01_0000_0000); + defmt::assert!(adc.read(&mut p).await.unwrap() < 0b01_0000_0000); + } + { + let mut p = Pin::new(&mut p.PIN_26, Pull::Up); + defmt::assert!(adc.blocking_read(&mut p).unwrap() > 0b11_0000_0000); + defmt::assert!(adc.read(&mut p).await.unwrap() > 0b11_0000_0000); + } + } + // not bothering with async reads from now on + { + { + let mut p = Pin::new(&mut p.PIN_27, Pull::Down); + defmt::assert!(adc.blocking_read(&mut p).unwrap() < 0b01_0000_0000); + } + { + let mut p = Pin::new(&mut p.PIN_27, Pull::Up); + defmt::assert!(adc.blocking_read(&mut p).unwrap() > 0b11_0000_0000); + } + } + { + { + let mut p = Pin::new(&mut p.PIN_28, Pull::Down); + defmt::assert!(adc.blocking_read(&mut p).unwrap() < 0b01_0000_0000); + } + { + let mut p = Pin::new(&mut p.PIN_28, Pull::Up); + defmt::assert!(adc.blocking_read(&mut p).unwrap() > 0b11_0000_0000); + } + } + { + // gp29 is connected to vsys through a 200k/100k divider, + // adding pulls should change the value + let low = { + let mut p = Pin::new(&mut p.PIN_29, Pull::Down); + adc.blocking_read(&mut p).unwrap() + }; + let none = { + let mut p = Pin::new(&mut p.PIN_29, Pull::None); + adc.blocking_read(&mut p).unwrap() + }; + let up = { + let mut p = Pin::new(&mut p.PIN_29, Pull::Up); + adc.blocking_read(&mut p).unwrap() + }; + defmt::assert!(low < none); + defmt::assert!(none < up); + } + + let temp = convert_to_celsius(adc.read_temperature().await.unwrap()); + defmt::assert!(temp > 0.0); + defmt::assert!(temp < 60.0); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} + +fn convert_to_celsius(raw_temp: u16) -> f32 { + // According to chapter 4.9.5. Temperature Sensor in RP2040 datasheet + 27.0 - (raw_temp as f32 * 3.3 / 4096.0 - 0.706) / 0.001721 as f32 +} diff --git a/tests/rp/src/bin/cyw43-perf.rs b/tests/rp/src/bin/cyw43-perf.rs new file mode 100644 index 000000000..bc127e2e5 --- /dev/null +++ b/tests/rp/src/bin/cyw43-perf.rs @@ -0,0 +1,264 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; + +use cyw43_pio::PioSpi; +use defmt::{assert, panic, *}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_net::tcp::TcpSocket; +use embassy_net::{Config, Ipv4Address, Stack, StackResources}; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::{DMA_CH0, PIN_23, PIN_25, PIO0}; +use embassy_rp::pio::{InterruptHandler, Pio}; +use embassy_rp::{bind_interrupts, rom_data}; +use embassy_time::{with_timeout, Duration, Timer}; +use static_cell::make_static; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +teleprobe_meta::timeout!(120); + +#[embassy_executor::task] +async fn wifi_task( + runner: cyw43::Runner<'static, Output<'static, PIN_23>, PioSpi<'static, PIN_25, PIO0, 0, DMA_CH0>>, +) -> ! { + runner.run().await +} + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack>) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + info!("Hello World!"); + let p = embassy_rp::init(Default::default()); + + // needed for reading the firmware from flash via XIP. + unsafe { + rom_data::flash_exit_xip(); + rom_data::flash_enter_cmd_xip(); + } + + // cyw43 firmware needs to be flashed manually: + // probe-rs download 43439A0.bin --format bin --chip RP2040 --base-address 0x101c0000 + // probe-rs download 43439A0_clm.bin --format bin --chip RP2040 --base-address 0x101f8000 + let fw = unsafe { core::slice::from_raw_parts(0x101c0000 as *const u8, 224190) }; + let clm = unsafe { core::slice::from_raw_parts(0x101f8000 as *const u8, 4752) }; + + let pwr = Output::new(p.PIN_23, Level::Low); + let cs = Output::new(p.PIN_25, Level::High); + let mut pio = Pio::new(p.PIO0, Irqs); + let spi = PioSpi::new(&mut pio.common, pio.sm0, pio.irq0, cs, p.PIN_24, p.PIN_29, p.DMA_CH0); + + let state = make_static!(cyw43::State::new()); + let (net_device, mut control, runner) = cyw43::new(state, pwr, spi, fw).await; + unwrap!(spawner.spawn(wifi_task(runner))); + + control.init(clm).await; + control + .set_power_management(cyw43::PowerManagementMode::PowerSave) + .await; + + // Generate random seed + let seed = 0x0123_4567_89ab_cdef; // chosen by fair dice roll. guarenteed to be random. + + // Init network stack + let stack = &*make_static!(Stack::new( + net_device, + Config::dhcpv4(Default::default()), + make_static!(StackResources::<2>::new()), + seed + )); + + unwrap!(spawner.spawn(net_task(stack))); + + loop { + match control.join_wpa2(WIFI_NETWORK, WIFI_PASSWORD).await { + Ok(_) => break, + Err(err) => { + panic!("join failed with status={}", err.status); + } + } + } + + info!("Waiting for DHCP up..."); + while stack.config_v4().is_none() { + Timer::after(Duration::from_millis(100)).await; + } + info!("IP addressing up!"); + + let down = test_download(stack).await; + let up = test_upload(stack).await; + let updown = test_upload_download(stack).await; + + assert!(down > TEST_EXPECTED_DOWNLOAD_KBPS); + assert!(up > TEST_EXPECTED_UPLOAD_KBPS); + assert!(updown > TEST_EXPECTED_UPLOAD_DOWNLOAD_KBPS); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} + +// Test-only wifi network, no internet access! +const WIFI_NETWORK: &str = "EmbassyTest"; +const WIFI_PASSWORD: &str = "V8YxhKt5CdIAJFud"; + +const TEST_DURATION: usize = 10; +const TEST_EXPECTED_DOWNLOAD_KBPS: usize = 300; +const TEST_EXPECTED_UPLOAD_KBPS: usize = 300; +const TEST_EXPECTED_UPLOAD_DOWNLOAD_KBPS: usize = 300; +const RX_BUFFER_SIZE: usize = 4096; +const TX_BUFFER_SIZE: usize = 4096; +const SERVER_ADDRESS: Ipv4Address = Ipv4Address::new(192, 168, 2, 2); +const DOWNLOAD_PORT: u16 = 4321; +const UPLOAD_PORT: u16 = 4322; +const UPLOAD_DOWNLOAD_PORT: u16 = 4323; + +async fn test_download(stack: &'static Stack>) -> usize { + info!("Testing download..."); + + let mut rx_buffer = [0; RX_BUFFER_SIZE]; + let mut tx_buffer = [0; TX_BUFFER_SIZE]; + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + + info!("connecting to {:?}:{}...", SERVER_ADDRESS, DOWNLOAD_PORT); + if let Err(e) = socket.connect((SERVER_ADDRESS, DOWNLOAD_PORT)).await { + error!("connect error: {:?}", e); + return 0; + } + info!("connected, testing..."); + + let mut rx_buf = [0; 4096]; + let mut total: usize = 0; + with_timeout(Duration::from_secs(TEST_DURATION as _), async { + loop { + match socket.read(&mut rx_buf).await { + Ok(0) => { + error!("read EOF"); + return 0; + } + Ok(n) => total += n, + Err(e) => { + error!("read error: {:?}", e); + return 0; + } + } + } + }) + .await + .ok(); + + let kbps = (total + 512) / 1024 / TEST_DURATION; + info!("download: {} kB/s", kbps); + kbps +} + +async fn test_upload(stack: &'static Stack>) -> usize { + info!("Testing upload..."); + + let mut rx_buffer = [0; RX_BUFFER_SIZE]; + let mut tx_buffer = [0; TX_BUFFER_SIZE]; + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + + info!("connecting to {:?}:{}...", SERVER_ADDRESS, UPLOAD_PORT); + if let Err(e) = socket.connect((SERVER_ADDRESS, UPLOAD_PORT)).await { + error!("connect error: {:?}", e); + return 0; + } + info!("connected, testing..."); + + let buf = [0; 4096]; + let mut total: usize = 0; + with_timeout(Duration::from_secs(TEST_DURATION as _), async { + loop { + match socket.write(&buf).await { + Ok(0) => { + error!("write zero?!??!?!"); + return 0; + } + Ok(n) => total += n, + Err(e) => { + error!("write error: {:?}", e); + return 0; + } + } + } + }) + .await + .ok(); + + let kbps = (total + 512) / 1024 / TEST_DURATION; + info!("upload: {} kB/s", kbps); + kbps +} + +async fn test_upload_download(stack: &'static Stack>) -> usize { + info!("Testing upload+download..."); + + let mut rx_buffer = [0; RX_BUFFER_SIZE]; + let mut tx_buffer = [0; TX_BUFFER_SIZE]; + let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer); + socket.set_timeout(Some(Duration::from_secs(10))); + + info!("connecting to {:?}:{}...", SERVER_ADDRESS, UPLOAD_DOWNLOAD_PORT); + if let Err(e) = socket.connect((SERVER_ADDRESS, UPLOAD_DOWNLOAD_PORT)).await { + error!("connect error: {:?}", e); + return 0; + } + info!("connected, testing..."); + + let (mut reader, mut writer) = socket.split(); + + let tx_buf = [0; 4096]; + let mut rx_buf = [0; 4096]; + let mut total: usize = 0; + let tx_fut = async { + loop { + match writer.write(&tx_buf).await { + Ok(0) => { + error!("write zero?!??!?!"); + return 0; + } + Ok(_) => {} + Err(e) => { + error!("write error: {:?}", e); + return 0; + } + } + } + }; + + let rx_fut = async { + loop { + match reader.read(&mut rx_buf).await { + Ok(0) => { + error!("read EOF"); + return 0; + } + Ok(n) => total += n, + Err(e) => { + error!("read error: {:?}", e); + return 0; + } + } + } + }; + + with_timeout(Duration::from_secs(TEST_DURATION as _), join(tx_fut, rx_fut)) + .await + .ok(); + + let kbps = (total + 512) / 1024 / TEST_DURATION; + info!("upload+download: {} kB/s", kbps); + kbps +} diff --git a/tests/rp/src/bin/dma_copy_async.rs b/tests/rp/src/bin/dma_copy_async.rs new file mode 100644 index 000000000..2c0b559a9 --- /dev/null +++ b/tests/rp/src/bin/dma_copy_async.rs @@ -0,0 +1,43 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; + +use defmt::{assert_eq, *}; +use embassy_executor::Spawner; +use embassy_rp::dma::copy; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + // Check `u8` copy + { + let data: [u8; 2] = [0xC0, 0xDE]; + let mut buf = [0; 2]; + unsafe { copy(p.DMA_CH0, &data, &mut buf).await }; + assert_eq!(buf, data); + } + + // Check `u16` copy + { + let data: [u16; 2] = [0xC0BE, 0xDEAD]; + let mut buf = [0; 2]; + unsafe { copy(p.DMA_CH1, &data, &mut buf).await }; + assert_eq!(buf, data); + } + + // Check `u32` copy + { + let data: [u32; 2] = [0xC0BEDEAD, 0xDEADAAFF]; + let mut buf = [0; 2]; + unsafe { copy(p.DMA_CH2, &data, &mut buf).await }; + assert_eq!(buf, data); + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/tests/rp/src/bin/flash.rs b/tests/rp/src/bin/flash.rs new file mode 100644 index 000000000..cf9b86df5 --- /dev/null +++ b/tests/rp/src/bin/flash.rs @@ -0,0 +1,65 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::flash::{ERASE_SIZE, FLASH_BASE}; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +const ADDR_OFFSET: u32 = 0x4000; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + // add some delay to give an attached debug probe time to parse the + // defmt RTT header. Reading that header might touch flash memory, which + // interferes with flash write operations. + // https://github.com/knurling-rs/defmt/pull/683 + Timer::after(Duration::from_millis(10)).await; + + let mut flash = embassy_rp::flash::Flash::<_, { 2 * 1024 * 1024 }>::new(p.FLASH); + + // Get JEDEC id + let jedec = defmt::unwrap!(flash.jedec_id()); + info!("jedec id: 0x{:x}", jedec); + + // Get unique id + let mut uid = [0; 8]; + defmt::unwrap!(flash.unique_id(&mut uid)); + info!("unique id: {:?}", uid); + + let mut buf = [0u8; ERASE_SIZE]; + defmt::unwrap!(flash.read(ADDR_OFFSET, &mut buf)); + + info!("Addr of flash block is {:x}", ADDR_OFFSET + FLASH_BASE as u32); + info!("Contents start with {=[u8]}", buf[0..4]); + + defmt::unwrap!(flash.erase(ADDR_OFFSET, ADDR_OFFSET + ERASE_SIZE as u32)); + + defmt::unwrap!(flash.read(ADDR_OFFSET, &mut buf)); + info!("Contents after erase starts with {=[u8]}", buf[0..4]); + if buf.iter().any(|x| *x != 0xFF) { + defmt::panic!("unexpected"); + } + + for b in buf.iter_mut() { + *b = 0xDA; + } + + defmt::unwrap!(flash.write(ADDR_OFFSET, &mut buf)); + + defmt::unwrap!(flash.read(ADDR_OFFSET, &mut buf)); + info!("Contents after write starts with {=[u8]}", buf[0..4]); + if buf.iter().any(|x| *x != 0xDA) { + defmt::panic!("unexpected"); + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/tests/rp/src/bin/float.rs b/tests/rp/src/bin/float.rs new file mode 100644 index 000000000..0e0de85fa --- /dev/null +++ b/tests/rp/src/bin/float.rs @@ -0,0 +1,53 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; + +use defmt::*; +use embassy_executor::Spawner; +use embassy_rp::pac; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + embassy_rp::init(Default::default()); + info!("Hello World!"); + + const PI_F: f32 = 3.1415926535f32; + const PI_D: f64 = 3.14159265358979323846f64; + + pac::BUSCTRL + .perfsel(0) + .write(|r| r.set_perfsel(pac::busctrl::vals::Perfsel::ROM)); + + for i in 0..=360 { + let rad_f = (i as f32) * PI_F / 180.0; + info!( + "{}° float: {=f32} / {=f32} / {=f32} / {=f32}", + i, + rad_f, + rad_f - PI_F, + rad_f + PI_F, + rad_f % PI_F + ); + let rad_d = (i as f64) * PI_D / 180.0; + info!( + "{}° double: {=f64} / {=f64} / {=f64} / {=f64}", + i, + rad_d, + rad_d - PI_D, + rad_d + PI_D, + rad_d % PI_D + ); + Timer::after(Duration::from_millis(10)).await; + } + + let rom_accesses = pac::BUSCTRL.perfctr(0).read().perfctr(); + // every float operation used here uses at least 10 cycles + defmt::assert!(rom_accesses >= 360 * 12 * 10); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/tests/rp/src/bin/gpio.rs b/tests/rp/src/bin/gpio.rs index af22fe27d..946b7dc88 100644 --- a/tests/rp/src/bin/gpio.rs +++ b/tests/rp/src/bin/gpio.rs @@ -1,6 +1,8 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; use defmt::{assert, *}; use embassy_executor::Spawner; @@ -19,14 +21,46 @@ async fn main(_spawner: Spawner) { let b = Input::new(&mut b, Pull::None); { - let _a = Output::new(&mut a, Level::Low); + let a = Output::new(&mut a, Level::Low); delay(); assert!(b.is_low()); + assert!(!b.is_high()); + assert!(a.is_set_low()); + assert!(!a.is_set_high()); } { - let _a = Output::new(&mut a, Level::High); + let mut a = Output::new(&mut a, Level::High); + delay(); + assert!(!b.is_low()); + assert!(b.is_high()); + assert!(!a.is_set_low()); + assert!(a.is_set_high()); + + // Test is_set_low / is_set_high + a.set_low(); + delay(); + assert!(b.is_low()); + assert!(a.is_set_low()); + assert!(!a.is_set_high()); + + a.set_high(); delay(); assert!(b.is_high()); + assert!(!a.is_set_low()); + assert!(a.is_set_high()); + + // Test toggle + a.toggle(); + delay(); + assert!(b.is_low()); + assert!(a.is_set_low()); + assert!(!a.is_set_high()); + + a.toggle(); + delay(); + assert!(b.is_high()); + assert!(!a.is_set_low()); + assert!(a.is_set_high()); } } @@ -78,6 +112,7 @@ async fn main(_spawner: Spawner) { a.set_as_input(); // When an OutputOpenDrain is high, it doesn't drive the pin. + b.set_high(); a.set_pull(Pull::Up); delay(); assert!(a.is_high()); @@ -85,9 +120,8 @@ async fn main(_spawner: Spawner) { delay(); assert!(a.is_low()); - b.set_low(); - // When an OutputOpenDrain is low, it drives the pin low. + b.set_low(); a.set_pull(Pull::Up); delay(); assert!(a.is_low()); @@ -95,14 +129,36 @@ async fn main(_spawner: Spawner) { delay(); assert!(a.is_low()); + // Check high again b.set_high(); - a.set_pull(Pull::Up); delay(); assert!(a.is_high()); a.set_pull(Pull::Down); delay(); assert!(a.is_low()); + + // When an OutputOpenDrain is high, it reads the input value in the pin. + b.set_high(); + a.set_as_input(); + a.set_pull(Pull::Up); + delay(); + assert!(b.is_high()); + a.set_as_output(); + a.set_low(); + delay(); + assert!(b.is_low()); + + // When an OutputOpenDrain is low, it always reads low. + b.set_low(); + a.set_as_input(); + a.set_pull(Pull::Up); + delay(); + assert!(b.is_low()); + a.set_as_output(); + a.set_low(); + delay(); + assert!(b.is_low()); } // FLEX diff --git a/tests/rp/src/bin/gpio_async.rs b/tests/rp/src/bin/gpio_async.rs index 1eeaac1f6..532494de5 100644 --- a/tests/rp/src/bin/gpio_async.rs +++ b/tests/rp/src/bin/gpio_async.rs @@ -1,12 +1,14 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; use defmt::{assert, *}; use embassy_executor::Spawner; +use embassy_futures::join::join; use embassy_rp::gpio::{Input, Level, Output, Pull}; use embassy_time::{Duration, Instant, Timer}; -use futures::future::join; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] diff --git a/tests/rp/src/bin/gpio_multicore.rs b/tests/rp/src/bin/gpio_multicore.rs new file mode 100644 index 000000000..780112bc1 --- /dev/null +++ b/tests/rp/src/bin/gpio_multicore.rs @@ -0,0 +1,65 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; + +use defmt::{info, unwrap}; +use embassy_executor::Executor; +use embassy_executor::_export::StaticCell; +use embassy_rp::gpio::{Input, Level, Output, Pull}; +use embassy_rp::multicore::{spawn_core1, Stack}; +use embassy_rp::peripherals::{PIN_0, PIN_1}; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::channel::Channel; +use {defmt_rtt as _, panic_probe as _}; + +static mut CORE1_STACK: Stack<1024> = Stack::new(); +static EXECUTOR0: StaticCell = StaticCell::new(); +static EXECUTOR1: StaticCell = StaticCell::new(); +static CHANNEL0: Channel = Channel::new(); +static CHANNEL1: Channel = Channel::new(); + +#[cortex_m_rt::entry] +fn main() -> ! { + let p = embassy_rp::init(Default::default()); + spawn_core1(p.CORE1, unsafe { &mut CORE1_STACK }, move || { + let executor1 = EXECUTOR1.init(Executor::new()); + executor1.run(|spawner| unwrap!(spawner.spawn(core1_task(p.PIN_1)))); + }); + let executor0 = EXECUTOR0.init(Executor::new()); + executor0.run(|spawner| unwrap!(spawner.spawn(core0_task(p.PIN_0)))); +} + +#[embassy_executor::task] +async fn core0_task(p: PIN_0) { + info!("CORE0 is running"); + + let mut pin = Output::new(p, Level::Low); + + CHANNEL0.send(()).await; + CHANNEL1.recv().await; + + pin.set_high(); + + CHANNEL1.recv().await; + + info!("Test OK"); + cortex_m::asm::bkpt(); +} + +#[embassy_executor::task] +async fn core1_task(p: PIN_1) { + info!("CORE1 is running"); + + CHANNEL0.recv().await; + + let mut pin = Input::new(p, Pull::Down); + let wait = pin.wait_for_rising_edge(); + + CHANNEL1.send(()).await; + + wait.await; + + CHANNEL1.send(()).await; +} diff --git a/tests/rp/src/bin/multicore.rs b/tests/rp/src/bin/multicore.rs new file mode 100644 index 000000000..114889dec --- /dev/null +++ b/tests/rp/src/bin/multicore.rs @@ -0,0 +1,49 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; + +use defmt::{info, unwrap}; +use embassy_executor::Executor; +use embassy_executor::_export::StaticCell; +use embassy_rp::multicore::{spawn_core1, Stack}; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::channel::Channel; +use {defmt_rtt as _, panic_probe as _}; + +static mut CORE1_STACK: Stack<1024> = Stack::new(); +static EXECUTOR0: StaticCell = StaticCell::new(); +static EXECUTOR1: StaticCell = StaticCell::new(); +static CHANNEL0: Channel = Channel::new(); +static CHANNEL1: Channel = Channel::new(); + +#[cortex_m_rt::entry] +fn main() -> ! { + let p = embassy_rp::init(Default::default()); + spawn_core1(p.CORE1, unsafe { &mut CORE1_STACK }, move || { + let executor1 = EXECUTOR1.init(Executor::new()); + executor1.run(|spawner| unwrap!(spawner.spawn(core1_task()))); + }); + let executor0 = EXECUTOR0.init(Executor::new()); + executor0.run(|spawner| unwrap!(spawner.spawn(core0_task()))); +} + +#[embassy_executor::task] +async fn core0_task() { + info!("CORE0 is running"); + let ping = true; + CHANNEL0.send(ping).await; + let pong = CHANNEL1.recv().await; + assert_eq!(ping, pong); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} + +#[embassy_executor::task] +async fn core1_task() { + info!("CORE1 is running"); + let ping = CHANNEL0.recv().await; + CHANNEL1.send(ping).await; +} diff --git a/tests/rp/src/bin/pio_irq.rs b/tests/rp/src/bin/pio_irq.rs new file mode 100644 index 000000000..45004424a --- /dev/null +++ b/tests/rp/src/bin/pio_irq.rs @@ -0,0 +1,55 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; + +use defmt::info; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{Config, InterruptHandler, Pio}; +use embassy_rp::relocate::RelocatedProgram; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let pio = p.PIO0; + let Pio { + mut common, + sm0: mut sm, + irq_flags, + .. + } = Pio::new(pio, Irqs); + + let prg = pio_proc::pio_asm!( + "irq set 0", + "irq wait 0", + "irq set 1", + // pause execution here + "irq wait 1", + ); + + let relocated = RelocatedProgram::new(&prg.program); + let mut cfg = Config::default(); + cfg.use_program(&common.load_program(&relocated), &[]); + sm.set_config(&cfg); + sm.set_enable(true); + + // not using the wait futures on purpose because they clear the irq bits, + // and we want to see in which order they are set. + while !irq_flags.check(0) {} + cortex_m::asm::nop(); + assert!(!irq_flags.check(1)); + irq_flags.clear(0); + cortex_m::asm::nop(); + assert!(irq_flags.check(1)); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/tests/rp/src/bin/pwm.rs b/tests/rp/src/bin/pwm.rs new file mode 100644 index 000000000..c71d21ef9 --- /dev/null +++ b/tests/rp/src/bin/pwm.rs @@ -0,0 +1,144 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; + +use defmt::{assert, assert_eq, assert_ne, *}; +use embassy_executor::Spawner; +use embassy_rp::gpio::{Input, Level, Output, Pull}; +use embassy_rp::pwm::{Config, InputMode, Pwm}; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + // Connections on CI device: 6 -> 9, 7 -> 11 + let (mut p6, mut p7, mut p9, mut p11) = (p.PIN_6, p.PIN_7, p.PIN_9, p.PIN_11); + + let cfg = { + let mut c = Config::default(); + c.divider = 125.into(); + c.top = 10000; + c.compare_a = 5000; + c.compare_b = 5000; + c + }; + + // Test free-running clock + { + let pwm = Pwm::new_free(&mut p.PWM_CH3, cfg.clone()); + cortex_m::asm::delay(125); + let ctr = pwm.counter(); + assert!(ctr > 0); + assert!(ctr < 100); + cortex_m::asm::delay(125); + assert!(ctr < pwm.counter()); + } + + for invert_a in [false, true] { + info!("free-running, invert A: {}", invert_a); + let mut cfg = cfg.clone(); + cfg.invert_a = invert_a; + cfg.invert_b = !invert_a; + + // Test output from A + { + let pin1 = Input::new(&mut p9, Pull::None); + let _pwm = Pwm::new_output_a(&mut p.PWM_CH3, &mut p6, cfg.clone()); + Timer::after(Duration::from_millis(1)).await; + assert_eq!(pin1.is_low(), invert_a); + Timer::after(Duration::from_millis(5)).await; + assert_eq!(pin1.is_high(), invert_a); + Timer::after(Duration::from_millis(5)).await; + assert_eq!(pin1.is_low(), invert_a); + Timer::after(Duration::from_millis(5)).await; + assert_eq!(pin1.is_high(), invert_a); + } + + // Test output from B + { + let pin2 = Input::new(&mut p11, Pull::None); + let _pwm = Pwm::new_output_b(&mut p.PWM_CH3, &mut p7, cfg.clone()); + Timer::after(Duration::from_millis(1)).await; + assert_ne!(pin2.is_low(), invert_a); + Timer::after(Duration::from_millis(5)).await; + assert_ne!(pin2.is_high(), invert_a); + Timer::after(Duration::from_millis(5)).await; + assert_ne!(pin2.is_low(), invert_a); + Timer::after(Duration::from_millis(5)).await; + assert_ne!(pin2.is_high(), invert_a); + } + + // Test output from A+B + { + let pin1 = Input::new(&mut p9, Pull::None); + let pin2 = Input::new(&mut p11, Pull::None); + let _pwm = Pwm::new_output_ab(&mut p.PWM_CH3, &mut p6, &mut p7, cfg.clone()); + Timer::after(Duration::from_millis(1)).await; + assert_eq!(pin1.is_low(), invert_a); + assert_ne!(pin2.is_low(), invert_a); + Timer::after(Duration::from_millis(5)).await; + assert_eq!(pin1.is_high(), invert_a); + assert_ne!(pin2.is_high(), invert_a); + Timer::after(Duration::from_millis(5)).await; + assert_eq!(pin1.is_low(), invert_a); + assert_ne!(pin2.is_low(), invert_a); + Timer::after(Duration::from_millis(5)).await; + assert_eq!(pin1.is_high(), invert_a); + assert_ne!(pin2.is_high(), invert_a); + } + } + + // Test level-gated + { + let mut pin2 = Output::new(&mut p11, Level::Low); + let pwm = Pwm::new_input(&mut p.PWM_CH3, &mut p7, InputMode::Level, cfg.clone()); + assert_eq!(pwm.counter(), 0); + Timer::after(Duration::from_millis(5)).await; + assert_eq!(pwm.counter(), 0); + pin2.set_high(); + Timer::after(Duration::from_millis(1)).await; + pin2.set_low(); + let ctr = pwm.counter(); + assert!(ctr >= 1000); + Timer::after(Duration::from_millis(1)).await; + assert_eq!(pwm.counter(), ctr); + } + + // Test rising-gated + { + let mut pin2 = Output::new(&mut p11, Level::Low); + let pwm = Pwm::new_input(&mut p.PWM_CH3, &mut p7, InputMode::RisingEdge, cfg.clone()); + assert_eq!(pwm.counter(), 0); + Timer::after(Duration::from_millis(5)).await; + assert_eq!(pwm.counter(), 0); + pin2.set_high(); + Timer::after(Duration::from_millis(1)).await; + pin2.set_low(); + assert_eq!(pwm.counter(), 1); + Timer::after(Duration::from_millis(1)).await; + assert_eq!(pwm.counter(), 1); + } + + // Test falling-gated + { + let mut pin2 = Output::new(&mut p11, Level::High); + let pwm = Pwm::new_input(&mut p.PWM_CH3, &mut p7, InputMode::FallingEdge, cfg.clone()); + assert_eq!(pwm.counter(), 0); + Timer::after(Duration::from_millis(5)).await; + assert_eq!(pwm.counter(), 0); + pin2.set_low(); + Timer::after(Duration::from_millis(1)).await; + pin2.set_high(); + assert_eq!(pwm.counter(), 1); + Timer::after(Duration::from_millis(1)).await; + assert_eq!(pwm.counter(), 1); + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/tests/rp/src/bin/spi.rs b/tests/rp/src/bin/spi.rs new file mode 100644 index 000000000..84dfa5a2c --- /dev/null +++ b/tests/rp/src/bin/spi.rs @@ -0,0 +1,30 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; + +use defmt::{assert_eq, *}; +use embassy_executor::Spawner; +use embassy_rp::spi::{Config, Spi}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + let clk = p.PIN_2; + let mosi = p.PIN_3; + let miso = p.PIN_4; + + let mut spi = Spi::new_blocking(p.SPI0, clk, mosi, miso, Config::default()); + + let tx_buf = [1_u8, 2, 3, 4, 5, 6]; + let mut rx_buf = [0_u8; 6]; + spi.blocking_transfer(&mut rx_buf, &tx_buf).unwrap(); + assert_eq!(rx_buf, tx_buf); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/tests/rp/src/bin/spi_async.rs b/tests/rp/src/bin/spi_async.rs new file mode 100644 index 000000000..a4080b03d --- /dev/null +++ b/tests/rp/src/bin/spi_async.rs @@ -0,0 +1,86 @@ +//! Make sure to connect GPIO pins 3 (`PIN_3`) and 4 (`PIN_4`) together +//! to run this test. +//! +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; + +use defmt::{assert_eq, *}; +use embassy_executor::Spawner; +use embassy_rp::spi::{Config, Spi}; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + let clk = p.PIN_2; + let mosi = p.PIN_3; + let miso = p.PIN_4; + + let mut spi = Spi::new(p.SPI0, clk, mosi, miso, p.DMA_CH0, p.DMA_CH1, Config::default()); + + // equal rx & tx buffers + { + let tx_buf = [1_u8, 2, 3, 4, 5, 6]; + let mut rx_buf = [0_u8; 6]; + spi.transfer(&mut rx_buf, &tx_buf).await.unwrap(); + assert_eq!(rx_buf, tx_buf); + } + + // tx > rx buffer + { + let tx_buf = [7_u8, 8, 9, 10, 11, 12]; + + let mut rx_buf = [0_u8; 3]; + spi.transfer(&mut rx_buf, &tx_buf).await.unwrap(); + assert_eq!(rx_buf, tx_buf[..3]); + + defmt::info!("tx > rx buffer - OK"); + } + + // we make sure to that clearing FIFO works after the uneven buffers + + // equal rx & tx buffers + { + let tx_buf = [13_u8, 14, 15, 16, 17, 18]; + let mut rx_buf = [0_u8; 6]; + spi.transfer(&mut rx_buf, &tx_buf).await.unwrap(); + assert_eq!(rx_buf, tx_buf); + + defmt::info!("buffer rx length == tx length - OK"); + } + + // rx > tx buffer + { + let tx_buf = [19_u8, 20, 21]; + let mut rx_buf = [0_u8; 6]; + + // we should have written dummy data to tx buffer to sync clock. + spi.transfer(&mut rx_buf, &tx_buf).await.unwrap(); + + assert_eq!( + rx_buf[..3], + tx_buf, + "only the first 3 TX bytes should have been received in the RX buffer" + ); + assert_eq!(rx_buf[3..], [0, 0, 0], "the rest of the RX bytes should be empty"); + defmt::info!("buffer rx length > tx length - OK"); + } + + // equal rx & tx buffers + { + let tx_buf = [22_u8, 23, 24, 25, 26, 27]; + let mut rx_buf = [0_u8; 6]; + spi.transfer(&mut rx_buf, &tx_buf).await.unwrap(); + + assert_eq!(rx_buf, tx_buf); + defmt::info!("buffer rx length = tx length - OK"); + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/tests/rp/src/bin/uart.rs b/tests/rp/src/bin/uart.rs new file mode 100644 index 000000000..2331c7d36 --- /dev/null +++ b/tests/rp/src/bin/uart.rs @@ -0,0 +1,171 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; + +use defmt::{assert_eq, *}; +use embassy_executor::Spawner; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::uart::{Blocking, Config, Error, Instance, Parity, Uart, UartRx}; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +fn read(uart: &mut Uart<'_, impl Instance, Blocking>) -> Result<[u8; N], Error> { + let mut buf = [255; N]; + uart.blocking_read(&mut buf)?; + Ok(buf) +} + +fn read1(uart: &mut UartRx<'_, impl Instance, Blocking>) -> Result<[u8; N], Error> { + let mut buf = [255; N]; + uart.blocking_read(&mut buf)?; + Ok(buf) +} + +async fn send(pin: &mut Output<'_, impl embassy_rp::gpio::Pin>, v: u8, parity: Option) { + pin.set_low(); + Timer::after(Duration::from_millis(1)).await; + for i in 0..8 { + if v & (1 << i) == 0 { + pin.set_low(); + } else { + pin.set_high(); + } + Timer::after(Duration::from_millis(1)).await; + } + if let Some(b) = parity { + if b { + pin.set_high(); + } else { + pin.set_low(); + } + Timer::after(Duration::from_millis(1)).await; + } + pin.set_high(); + Timer::after(Duration::from_millis(1)).await; +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + let (mut tx, mut rx, mut uart) = (p.PIN_0, p.PIN_1, p.UART0); + + { + let config = Config::default(); + let mut uart = Uart::new_blocking(&mut uart, &mut tx, &mut rx, config); + + // We can't send too many bytes, they have to fit in the FIFO. + // This is because we aren't sending+receiving at the same time. + + let data = [0xC0, 0xDE]; + uart.blocking_write(&data).unwrap(); + assert_eq!(read(&mut uart).unwrap(), data); + } + + info!("test overflow detection"); + { + let config = Config::default(); + let mut uart = Uart::new_blocking(&mut uart, &mut tx, &mut rx, config); + + let data = [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, + ]; + let overflow = [ + 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, + ]; + uart.blocking_write(&data).unwrap(); + uart.blocking_write(&overflow).unwrap(); + while uart.busy() {} + + // prefix in fifo is valid + assert_eq!(read(&mut uart).unwrap(), data); + // next received character causes overrun error and is discarded + uart.blocking_write(&[1, 2, 3]).unwrap(); + assert_eq!(read::<1>(&mut uart).unwrap_err(), Error::Overrun); + assert_eq!(read(&mut uart).unwrap(), [2, 3]); + } + + info!("test break detection"); + { + let config = Config::default(); + let mut uart = Uart::new_blocking(&mut uart, &mut tx, &mut rx, config); + + // break on empty fifo + uart.send_break(20).await; + uart.blocking_write(&[64]).unwrap(); + assert_eq!(read::<1>(&mut uart).unwrap_err(), Error::Break); + assert_eq!(read(&mut uart).unwrap(), [64]); + + // break on partially filled fifo + uart.blocking_write(&[65; 2]).unwrap(); + uart.send_break(20).await; + uart.blocking_write(&[66]).unwrap(); + assert_eq!(read(&mut uart).unwrap(), [65; 2]); + assert_eq!(read::<1>(&mut uart).unwrap_err(), Error::Break); + assert_eq!(read(&mut uart).unwrap(), [66]); + } + + // parity detection. here we bitbang to not require two uarts. + info!("test parity error detection"); + { + let mut pin = Output::new(&mut tx, Level::High); + let mut config = Config::default(); + config.baudrate = 1000; + config.parity = Parity::ParityEven; + let mut uart = UartRx::new_blocking(&mut uart, &mut rx, config); + + async fn chr(pin: &mut Output<'_, impl embassy_rp::gpio::Pin>, v: u8, parity: u8) { + send(pin, v, Some(parity != 0)).await; + } + + // first check that we can send correctly + chr(&mut pin, 64, 1).await; + assert_eq!(read1(&mut uart).unwrap(), [64]); + + // all good, check real errors + chr(&mut pin, 2, 1).await; + chr(&mut pin, 3, 0).await; + chr(&mut pin, 4, 0).await; + chr(&mut pin, 5, 0).await; + assert_eq!(read1(&mut uart).unwrap(), [2, 3]); + assert_eq!(read1::<1>(&mut uart).unwrap_err(), Error::Parity); + assert_eq!(read1(&mut uart).unwrap(), [5]); + } + + // framing error detection. here we bitbang because there's no other way. + info!("test framing error detection"); + { + let mut pin = Output::new(&mut tx, Level::High); + let mut config = Config::default(); + config.baudrate = 1000; + let mut uart = UartRx::new_blocking(&mut uart, &mut rx, config); + + async fn chr(pin: &mut Output<'_, impl embassy_rp::gpio::Pin>, v: u8, good: bool) { + if good { + send(pin, v, None).await; + } else { + send(pin, v, Some(false)).await; + } + } + + // first check that we can send correctly + chr(&mut pin, 64, true).await; + assert_eq!(read1(&mut uart).unwrap(), [64]); + + // all good, check real errors + chr(&mut pin, 2, true).await; + chr(&mut pin, 3, true).await; + chr(&mut pin, 4, false).await; + chr(&mut pin, 5, true).await; + assert_eq!(read1(&mut uart).unwrap(), [2, 3]); + assert_eq!(read1::<1>(&mut uart).unwrap_err(), Error::Framing); + assert_eq!(read1(&mut uart).unwrap(), [5]); + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/tests/rp/src/bin/uart_buffered.rs b/tests/rp/src/bin/uart_buffered.rs new file mode 100644 index 000000000..e74e9986c --- /dev/null +++ b/tests/rp/src/bin/uart_buffered.rs @@ -0,0 +1,256 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; + +use defmt::{assert_eq, panic, *}; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::UART0; +use embassy_rp::uart::{BufferedInterruptHandler, BufferedUart, BufferedUartRx, Config, Error, Instance, Parity}; +use embassy_time::{Duration, Timer}; +use embedded_io::asynch::{Read, ReadExactError, Write}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + UART0_IRQ => BufferedInterruptHandler; +}); + +async fn read(uart: &mut BufferedUart<'_, impl Instance>) -> Result<[u8; N], Error> { + let mut buf = [255; N]; + match uart.read_exact(&mut buf).await { + Ok(()) => Ok(buf), + // we should not ever produce an Eof condition + Err(ReadExactError::UnexpectedEof) => panic!(), + Err(ReadExactError::Other(e)) => Err(e), + } +} + +async fn read1(uart: &mut BufferedUartRx<'_, impl Instance>) -> Result<[u8; N], Error> { + let mut buf = [255; N]; + match uart.read_exact(&mut buf).await { + Ok(()) => Ok(buf), + // we should not ever produce an Eof condition + Err(ReadExactError::UnexpectedEof) => panic!(), + Err(ReadExactError::Other(e)) => Err(e), + } +} + +async fn send(pin: &mut Output<'_, impl embassy_rp::gpio::Pin>, v: u8, parity: Option) { + pin.set_low(); + Timer::after(Duration::from_millis(1)).await; + for i in 0..8 { + if v & (1 << i) == 0 { + pin.set_low(); + } else { + pin.set_high(); + } + Timer::after(Duration::from_millis(1)).await; + } + if let Some(b) = parity { + if b { + pin.set_high(); + } else { + pin.set_low(); + } + Timer::after(Duration::from_millis(1)).await; + } + pin.set_high(); + Timer::after(Duration::from_millis(1)).await; +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + let (mut tx, mut rx, mut uart) = (p.PIN_0, p.PIN_1, p.UART0); + + { + let config = Config::default(); + let tx_buf = &mut [0u8; 16]; + let rx_buf = &mut [0u8; 16]; + let mut uart = BufferedUart::new(&mut uart, Irqs, &mut tx, &mut rx, tx_buf, rx_buf, config); + + // Make sure we send more bytes than fits in the FIFO, to test the actual + // bufferedUart. + + let data = [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + ]; + uart.write_all(&data).await.unwrap(); + info!("Done writing"); + + assert_eq!(read(&mut uart).await.unwrap(), data); + } + + info!("test overflow detection"); + { + let config = Config::default(); + let tx_buf = &mut [0u8; 16]; + let rx_buf = &mut [0u8; 16]; + let mut uart = BufferedUart::new(&mut uart, Irqs, &mut tx, &mut rx, tx_buf, rx_buf, config); + + // Make sure we send more bytes than fits in the FIFO, to test the actual + // bufferedUart. + + let data = [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + ]; + let overflow = [ + 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, + ]; + // give each block time to settle into the fifo. we want the overrun to occur at a well-defined point. + uart.write_all(&data).await.unwrap(); + uart.blocking_flush().unwrap(); + while uart.busy() {} + uart.write_all(&overflow).await.unwrap(); + uart.blocking_flush().unwrap(); + while uart.busy() {} + + // already buffered/fifod prefix is valid + assert_eq!(read(&mut uart).await.unwrap(), data); + // next received character causes overrun error and is discarded + uart.write_all(&[1, 2, 3]).await.unwrap(); + uart.blocking_flush().unwrap(); + assert_eq!(read::<1>(&mut uart).await.unwrap_err(), Error::Overrun); + assert_eq!(read(&mut uart).await.unwrap(), [2, 3]); + } + + info!("test break detection"); + { + let mut config = Config::default(); + config.baudrate = 1000; + let tx_buf = &mut [0u8; 16]; + let rx_buf = &mut [0u8; 16]; + let mut uart = BufferedUart::new(&mut uart, Irqs, &mut tx, &mut rx, tx_buf, rx_buf, config); + + // break on empty buffer + uart.send_break(20).await; + assert_eq!(read::<1>(&mut uart).await.unwrap_err(), Error::Break); + uart.write_all(&[64]).await.unwrap(); + assert_eq!(read(&mut uart).await.unwrap(), [64]); + + // break on partially filled buffer + uart.write_all(&[65; 2]).await.unwrap(); + uart.send_break(20).await; + uart.write_all(&[66]).await.unwrap(); + assert_eq!(read(&mut uart).await.unwrap(), [65; 2]); + assert_eq!(read::<1>(&mut uart).await.unwrap_err(), Error::Break); + assert_eq!(read(&mut uart).await.unwrap(), [66]); + + // break on full buffer + uart.write_all(&[64; 16]).await.unwrap(); + uart.send_break(20).await; + uart.write_all(&[65]).await.unwrap(); + assert_eq!(read(&mut uart).await.unwrap(), [64; 16]); + assert_eq!(read::<1>(&mut uart).await.unwrap_err(), Error::Break); + assert_eq!(read(&mut uart).await.unwrap(), [65]); + } + + // parity detection. here we bitbang to not require two uarts. + info!("test parity error detection"); + { + let mut pin = Output::new(&mut tx, Level::High); + // choose a very slow baud rate to make tests reliable even with O0 + let mut config = Config::default(); + config.baudrate = 1000; + config.parity = Parity::ParityEven; + let rx_buf = &mut [0u8; 16]; + let mut uart = BufferedUartRx::new(&mut uart, Irqs, &mut rx, rx_buf, config); + + async fn chr(pin: &mut Output<'_, impl embassy_rp::gpio::Pin>, v: u8, parity: u32) { + send(pin, v, Some(parity != 0)).await; + } + + // first check that we can send correctly + chr(&mut pin, 64, 1).await; + assert_eq!(read1(&mut uart).await.unwrap(), [64]); + + // parity on empty buffer + chr(&mut pin, 64, 0).await; + chr(&mut pin, 4, 1).await; + assert_eq!(read1::<1>(&mut uart).await.unwrap_err(), Error::Parity); + assert_eq!(read1(&mut uart).await.unwrap(), [4]); + + // parity on partially filled buffer + chr(&mut pin, 64, 1).await; + chr(&mut pin, 32, 1).await; + chr(&mut pin, 64, 0).await; + chr(&mut pin, 65, 0).await; + assert_eq!(read1(&mut uart).await.unwrap(), [64, 32]); + assert_eq!(read1::<1>(&mut uart).await.unwrap_err(), Error::Parity); + assert_eq!(read1(&mut uart).await.unwrap(), [65]); + + // parity on full buffer + for i in 0..16 { + chr(&mut pin, i, i.count_ones() % 2).await; + } + chr(&mut pin, 64, 0).await; + chr(&mut pin, 65, 0).await; + assert_eq!( + read1(&mut uart).await.unwrap(), + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] + ); + assert_eq!(read1::<1>(&mut uart).await.unwrap_err(), Error::Parity); + assert_eq!(read1(&mut uart).await.unwrap(), [65]); + } + + // framing error detection. here we bitbang because there's no other way. + info!("test framing error detection"); + { + let mut pin = Output::new(&mut tx, Level::High); + // choose a very slow baud rate to make tests reliable even with O0 + let mut config = Config::default(); + config.baudrate = 1000; + let rx_buf = &mut [0u8; 16]; + let mut uart = BufferedUartRx::new(&mut uart, Irqs, &mut rx, rx_buf, config); + + async fn chr(pin: &mut Output<'_, impl embassy_rp::gpio::Pin>, v: u8, good: bool) { + if good { + send(pin, v, None).await; + } else { + send(pin, v, Some(false)).await; + } + } + + // first check that we can send correctly + chr(&mut pin, 64, true).await; + assert_eq!(read1(&mut uart).await.unwrap(), [64]); + + // framing on empty buffer + chr(&mut pin, 64, false).await; + assert_eq!(read1::<1>(&mut uart).await.unwrap_err(), Error::Framing); + chr(&mut pin, 65, true).await; + assert_eq!(read1(&mut uart).await.unwrap(), [65]); + + // framing on partially filled buffer + chr(&mut pin, 64, true).await; + chr(&mut pin, 32, true).await; + chr(&mut pin, 64, false).await; + chr(&mut pin, 65, true).await; + assert_eq!(read1(&mut uart).await.unwrap(), [64, 32]); + assert_eq!(read1::<1>(&mut uart).await.unwrap_err(), Error::Framing); + assert_eq!(read1(&mut uart).await.unwrap(), [65]); + + // framing on full buffer + for i in 0..16 { + chr(&mut pin, i, true).await; + } + chr(&mut pin, 64, false).await; + chr(&mut pin, 65, true).await; + assert_eq!( + read1(&mut uart).await.unwrap(), + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] + ); + assert_eq!(read1::<1>(&mut uart).await.unwrap_err(), Error::Framing); + assert_eq!(read1(&mut uart).await.unwrap(), [65]); + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/tests/rp/src/bin/uart_dma.rs b/tests/rp/src/bin/uart_dma.rs new file mode 100644 index 000000000..fee6c825d --- /dev/null +++ b/tests/rp/src/bin/uart_dma.rs @@ -0,0 +1,252 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; + +use defmt::{assert_eq, *}; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::gpio::{Level, Output}; +use embassy_rp::peripherals::UART0; +use embassy_rp::uart::{Async, Config, Error, Instance, InterruptHandler, Parity, Uart, UartRx}; +use embassy_time::{Duration, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + UART0_IRQ => InterruptHandler; +}); + +async fn read(uart: &mut Uart<'_, impl Instance, Async>) -> Result<[u8; N], Error> { + let mut buf = [255; N]; + uart.read(&mut buf).await?; + Ok(buf) +} + +async fn read1(uart: &mut UartRx<'_, impl Instance, Async>) -> Result<[u8; N], Error> { + let mut buf = [255; N]; + uart.read(&mut buf).await?; + Ok(buf) +} + +async fn send(pin: &mut Output<'_, impl embassy_rp::gpio::Pin>, v: u8, parity: Option) { + pin.set_low(); + Timer::after(Duration::from_millis(1)).await; + for i in 0..8 { + if v & (1 << i) == 0 { + pin.set_low(); + } else { + pin.set_high(); + } + Timer::after(Duration::from_millis(1)).await; + } + if let Some(b) = parity { + if b { + pin.set_high(); + } else { + pin.set_low(); + } + Timer::after(Duration::from_millis(1)).await; + } + pin.set_high(); + Timer::after(Duration::from_millis(1)).await; +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + let (mut tx, mut rx, mut uart) = (p.PIN_0, p.PIN_1, p.UART0); + + // We can't send too many bytes, they have to fit in the FIFO. + // This is because we aren't sending+receiving at the same time. + { + let config = Config::default(); + let mut uart = Uart::new( + &mut uart, + &mut tx, + &mut rx, + Irqs, + &mut p.DMA_CH0, + &mut p.DMA_CH1, + config, + ); + + let data = [0xC0, 0xDE]; + uart.write(&data).await.unwrap(); + + let mut buf = [0; 2]; + uart.read(&mut buf).await.unwrap(); + assert_eq!(buf, data); + } + + info!("test overflow detection"); + { + let config = Config::default(); + let mut uart = Uart::new( + &mut uart, + &mut tx, + &mut rx, + Irqs, + &mut p.DMA_CH0, + &mut p.DMA_CH1, + config, + ); + + uart.blocking_write(&[42; 32]).unwrap(); + uart.blocking_write(&[1, 2, 3]).unwrap(); + uart.blocking_flush().unwrap(); + + // can receive regular fifo contents + assert_eq!(read(&mut uart).await, Ok([42; 16])); + assert_eq!(read(&mut uart).await, Ok([42; 16])); + // receiving the rest fails with overrun + assert_eq!(read::<16>(&mut uart).await, Err(Error::Overrun)); + // new data is accepted, latest overrunning byte first + assert_eq!(read(&mut uart).await, Ok([3])); + uart.blocking_write(&[8, 9]).unwrap(); + Timer::after(Duration::from_millis(1)).await; + assert_eq!(read(&mut uart).await, Ok([8, 9])); + } + + info!("test break detection"); + { + let config = Config::default(); + let (mut tx, mut rx) = Uart::new( + &mut uart, + &mut tx, + &mut rx, + Irqs, + &mut p.DMA_CH0, + &mut p.DMA_CH1, + config, + ) + .split(); + + // break before read + tx.send_break(20).await; + tx.write(&[64]).await.unwrap(); + assert_eq!(read1::<1>(&mut rx).await.unwrap_err(), Error::Break); + assert_eq!(read1(&mut rx).await.unwrap(), [64]); + + // break during read + { + let r = read1::<2>(&mut rx); + tx.write(&[2]).await.unwrap(); + tx.send_break(20).await; + tx.write(&[3]).await.unwrap(); + assert_eq!(r.await.unwrap_err(), Error::Break); + assert_eq!(read1(&mut rx).await.unwrap(), [3]); + } + + // break after read + { + let r = read1(&mut rx); + tx.write(&[2]).await.unwrap(); + tx.send_break(20).await; + tx.write(&[3]).await.unwrap(); + assert_eq!(r.await.unwrap(), [2]); + assert_eq!(read1::<1>(&mut rx).await.unwrap_err(), Error::Break); + assert_eq!(read1(&mut rx).await.unwrap(), [3]); + } + } + + // parity detection. here we bitbang to not require two uarts. + info!("test parity error detection"); + { + let mut pin = Output::new(&mut tx, Level::High); + // choose a very slow baud rate to make tests reliable even with O0 + let mut config = Config::default(); + config.baudrate = 1000; + config.parity = Parity::ParityEven; + let mut uart = UartRx::new(&mut uart, &mut rx, Irqs, &mut p.DMA_CH0, config); + + async fn chr(pin: &mut Output<'_, impl embassy_rp::gpio::Pin>, v: u8, parity: u32) { + send(pin, v, Some(parity != 0)).await; + } + + // first check that we can send correctly + chr(&mut pin, 32, 1).await; + assert_eq!(read1(&mut uart).await.unwrap(), [32]); + + // parity error before read + chr(&mut pin, 32, 0).await; + chr(&mut pin, 31, 1).await; + assert_eq!(read1::<1>(&mut uart).await.unwrap_err(), Error::Parity); + assert_eq!(read1(&mut uart).await.unwrap(), [31]); + + // parity error during read + { + let r = read1::<2>(&mut uart); + chr(&mut pin, 2, 1).await; + chr(&mut pin, 32, 0).await; + chr(&mut pin, 3, 0).await; + assert_eq!(r.await.unwrap_err(), Error::Parity); + assert_eq!(read1(&mut uart).await.unwrap(), [3]); + } + + // parity error after read + { + let r = read1(&mut uart); + chr(&mut pin, 2, 1).await; + chr(&mut pin, 32, 0).await; + chr(&mut pin, 3, 0).await; + assert_eq!(r.await.unwrap(), [2]); + assert_eq!(read1::<1>(&mut uart).await.unwrap_err(), Error::Parity); + assert_eq!(read1(&mut uart).await.unwrap(), [3]); + } + } + + // framing error detection. here we bitbang because there's no other way. + info!("test framing error detection"); + { + let mut pin = Output::new(&mut tx, Level::High); + // choose a very slow baud rate to make tests reliable even with O0 + let mut config = Config::default(); + config.baudrate = 1000; + let mut uart = UartRx::new(&mut uart, &mut rx, Irqs, &mut p.DMA_CH0, config); + + async fn chr(pin: &mut Output<'_, impl embassy_rp::gpio::Pin>, v: u8, good: bool) { + if good { + send(pin, v, None).await; + } else { + send(pin, v, Some(false)).await; + } + } + + // first check that we can send correctly + chr(&mut pin, 32, true).await; + assert_eq!(read1(&mut uart).await.unwrap(), [32]); + + // parity error before read + chr(&mut pin, 32, false).await; + chr(&mut pin, 31, true).await; + assert_eq!(read1::<1>(&mut uart).await.unwrap_err(), Error::Framing); + assert_eq!(read1(&mut uart).await.unwrap(), [31]); + + // parity error during read + { + let r = read1::<2>(&mut uart); + chr(&mut pin, 2, true).await; + chr(&mut pin, 32, false).await; + chr(&mut pin, 3, true).await; + assert_eq!(r.await.unwrap_err(), Error::Framing); + assert_eq!(read1(&mut uart).await.unwrap(), [3]); + } + + // parity error after read + { + let r = read1(&mut uart); + chr(&mut pin, 2, true).await; + chr(&mut pin, 32, false).await; + chr(&mut pin, 3, true).await; + assert_eq!(r.await.unwrap(), [2]); + assert_eq!(read1::<1>(&mut uart).await.unwrap_err(), Error::Framing); + assert_eq!(read1(&mut uart).await.unwrap(), [3]); + } + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/tests/rp/src/bin/uart_upgrade.rs b/tests/rp/src/bin/uart_upgrade.rs new file mode 100644 index 000000000..760e53954 --- /dev/null +++ b/tests/rp/src/bin/uart_upgrade.rs @@ -0,0 +1,60 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; + +use defmt::{assert_eq, *}; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::UART0; +use embassy_rp::uart::{BufferedInterruptHandler, Config, Uart}; +use embedded_io::asynch::{Read, Write}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + UART0_IRQ => BufferedInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + info!("Hello World!"); + + let (tx, rx, uart) = (p.PIN_0, p.PIN_1, p.UART0); + + let config = Config::default(); + let mut uart = Uart::new_blocking(uart, tx, rx, config); + + // We can't send too many bytes, they have to fit in the FIFO. + // This is because we aren't sending+receiving at the same time. + + let data = [0xC0, 0xDE]; + uart.blocking_write(&data).unwrap(); + + let mut buf = [0; 2]; + uart.blocking_read(&mut buf).unwrap(); + assert_eq!(buf, data); + + let tx_buf = &mut [0u8; 16]; + let rx_buf = &mut [0u8; 16]; + + let mut uart = uart.into_buffered(Irqs, tx_buf, rx_buf); + + // Make sure we send more bytes than fits in the FIFO, to test the actual + // bufferedUart. + + let data = [ + 1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, + ]; + uart.write_all(&data).await.unwrap(); + info!("Done writing"); + + let mut buf = [0; 31]; + uart.read_exact(&mut buf).await.unwrap(); + assert_eq!(buf, data); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/tests/rp/src/common.rs b/tests/rp/src/common.rs new file mode 100644 index 000000000..955674f27 --- /dev/null +++ b/tests/rp/src/common.rs @@ -0,0 +1 @@ +teleprobe_meta::target!(b"rpi-pico"); diff --git a/tests/stm32/.cargo/config.toml b/tests/stm32/.cargo/config.toml index 29c4799a1..07761b01c 100644 --- a/tests/stm32/.cargo/config.toml +++ b/tests/stm32/.cargo/config.toml @@ -3,7 +3,7 @@ build-std = ["core"] build-std-features = ["panic_immediate_abort"] [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -runner = "teleprobe client run --target bluepill-stm32f103c8 --elf" +runner = "teleprobe client run" #runner = "teleprobe local run --chip STM32F103C8 --elf" rustflags = [ @@ -17,4 +17,4 @@ rustflags = [ target = "thumbv7m-none-eabi" [env] -DEFMT_LOG = "trace" +DEFMT_LOG = "trace" \ No newline at end of file diff --git a/tests/stm32/Cargo.toml b/tests/stm32/Cargo.toml index f1441d00c..3007cd1e6 100644 --- a/tests/stm32/Cargo.toml +++ b/tests/stm32/Cargo.toml @@ -2,31 +2,115 @@ edition = "2021" name = "embassy-stm32-tests" version = "0.1.0" +license = "MIT OR Apache-2.0" +autobins = false [features] -stm32f103c8 = ["embassy-stm32/stm32f103c8"] # Blue Pill -stm32f429zi = ["embassy-stm32/stm32f429zi"] # Nucleo -stm32g071rb = ["embassy-stm32/stm32g071rb"] # Nucleo -stm32g491re = ["embassy-stm32/stm32g491re"] # Nucleo -stm32h755zi = ["embassy-stm32/stm32h755zi-cm7"] # Nucleo -stm32wb55rg = ["embassy-stm32/stm32wb55rg"] # Nucleo +stm32f103c8 = ["embassy-stm32/stm32f103c8", "not-gpdma"] # Blue Pill +stm32f429zi = ["embassy-stm32/stm32f429zi", "chrono", "can", "not-gpdma"] # Nucleo "sdmmc" +stm32g071rb = ["embassy-stm32/stm32g071rb", "not-gpdma"] # Nucleo +stm32c031c6 = ["embassy-stm32/stm32c031c6", "not-gpdma"] # Nucleo +stm32g491re = ["embassy-stm32/stm32g491re", "not-gpdma"] # Nucleo +stm32h755zi = ["embassy-stm32/stm32h755zi-cm7", "not-gpdma"] # Nucleo +stm32wb55rg = ["embassy-stm32/stm32wb55rg", "not-gpdma", "ble", "mac" ] # Nucleo +stm32h563zi = ["embassy-stm32/stm32h563zi"] # Nucleo stm32u585ai = ["embassy-stm32/stm32u585ai"] # IoT board +sdmmc = [] +chrono = ["embassy-stm32/chrono", "dep:chrono"] +can = [] +ble = ["dep:embassy-stm32-wpan", "embassy-stm32-wpan/ble"] +mac = ["dep:embassy-stm32-wpan", "embassy-stm32-wpan/mac"] +embassy-stm32-wpan = [] +not-gpdma = [] + [dependencies] -embassy-sync = { version = "0.1.0", path = "../../embassy-sync", features = ["defmt"] } -embassy-executor = { version = "0.1.0", path = "../../embassy-executor", features = ["defmt", "integrated-timers"] } -embassy-time = { version = "0.1.0", path = "../../embassy-time", features = ["defmt", "tick-32768hz"] } -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "memory-x", "time-driver-tim2"] } +teleprobe-meta = "1" + +embassy-sync = { version = "0.2.0", path = "../../embassy-sync", features = ["defmt"] } +embassy-executor = { version = "0.2.0", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } +embassy-time = { version = "0.1.2", path = "../../embassy-time", features = ["defmt", "tick-hz-32_768", "defmt-timestamp-uptime"] } +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["nightly", "defmt", "unstable-pac", "memory-x", "time-driver-any"] } +embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } +embassy-stm32-wpan = { version = "0.1.0", path = "../../embassy-stm32-wpan", optional = true, features = ["defmt", "stm32wb55rg", "ble"] } defmt = "0.3.0" -defmt-rtt = "0.3.0" +defmt-rtt = "0.4" cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" -embedded-hal-1 = { package = "embedded-hal", version = "1.0.0-alpha.8" } -embedded-hal-async = { version = "0.1.0-alpha.1" } +embedded-hal-1 = { package = "embedded-hal", version = "=1.0.0-alpha.11" } +embedded-hal-async = { version = "=0.2.0-alpha.2" } panic-probe = { version = "0.3.0", features = ["print-defmt"] } +rand_core = { version = "0.6", default-features = false } +rand_chacha = { version = "0.3", default-features = false } + +chrono = { version = "^0.4", default-features = false, optional = true} + +# BEGIN TESTS +# Generated by gen_test.py. DO NOT EDIT. +[[bin]] +name = "can" +path = "src/bin/can.rs" +required-features = [ "can",] + +[[bin]] +name = "gpio" +path = "src/bin/gpio.rs" +required-features = [] + +[[bin]] +name = "rtc" +path = "src/bin/rtc.rs" +required-features = [ "chrono",] + +[[bin]] +name = "sdmmc" +path = "src/bin/sdmmc.rs" +required-features = [ "sdmmc",] + +[[bin]] +name = "spi" +path = "src/bin/spi.rs" +required-features = [] + +[[bin]] +name = "spi_dma" +path = "src/bin/spi_dma.rs" +required-features = [] + +[[bin]] +name = "timer" +path = "src/bin/timer.rs" +required-features = [] + +[[bin]] +name = "usart" +path = "src/bin/usart.rs" +required-features = [] + +[[bin]] +name = "usart_dma" +path = "src/bin/usart_dma.rs" +required-features = [] + +[[bin]] +name = "usart_rx_ringbuffered" +path = "src/bin/usart_rx_ringbuffered.rs" +required-features = [ "not-gpdma",] + +[[bin]] +name = "wpan_ble" +path = "src/bin/wpan_ble.rs" +required-features = [ "ble",] + +[[bin]] +name = "wpan_mac" +path = "src/bin/wpan_mac.rs" +required-features = [ "mac",] + +# END TESTS [profile.dev] debug = 2 diff --git a/tests/stm32/build.rs b/tests/stm32/build.rs index 6f4872249..2e71954d7 100644 --- a/tests/stm32/build.rs +++ b/tests/stm32/build.rs @@ -6,11 +6,27 @@ fn main() -> Result<(), Box> { let out = PathBuf::from(env::var("OUT_DIR").unwrap()); fs::write(out.join("link_ram.x"), include_bytes!("link_ram.x")).unwrap(); println!("cargo:rustc-link-search={}", out.display()); - println!("cargo:rerun-if-changed=link_ram.x"); - println!("cargo:rustc-link-arg-bins=--nmagic"); - println!("cargo:rustc-link-arg-bins=-Tlink_ram.x"); + + // too little RAM to run from RAM. + if cfg!(any( + feature = "stm32f103c8", + feature = "stm32c031c6", + feature = "stm32wb55rg" + )) { + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + println!("cargo:rerun-if-changed=link.x"); + } else { + println!("cargo:rustc-link-arg-bins=-Tlink_ram.x"); + println!("cargo:rerun-if-changed=link_ram.x"); + } + + if cfg!(feature = "stm32wb55rg") { + println!("cargo:rustc-link-arg-bins=-Ttl_mbox.x"); + } + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + println!("cargo:rustc-link-arg-bins=-Tteleprobe.x"); Ok(()) } diff --git a/tests/stm32/gen_test.py b/tests/stm32/gen_test.py new file mode 100644 index 000000000..8ff156c0e --- /dev/null +++ b/tests/stm32/gen_test.py @@ -0,0 +1,44 @@ +import os +import toml +from glob import glob + +abspath = os.path.abspath(__file__) +dname = os.path.dirname(abspath) +os.chdir(dname) + +# ======= load test list +tests = {} +for f in sorted(glob('./src/bin/*.rs')): + name = os.path.splitext(os.path.basename(f))[0] + features = [] + with open(f, 'r') as f: + for line in f: + if line.startswith('// required-features:'): + features = line.split(':', 2)[1].strip().split(',') + + tests[name] = features + +# ========= Update Cargo.toml + +things = { + 'bin': [ + { + 'name': f'{name}', + 'path': f'src/bin/{name}.rs', + 'required-features': features, + } + for name, features in tests.items() + ] +} + +SEPARATOR_START = '# BEGIN TESTS\n' +SEPARATOR_END = '# END TESTS\n' +HELP = '# Generated by gen_test.py. DO NOT EDIT.\n' +with open('Cargo.toml', 'r') as f: + data = f.read() +before, data = data.split(SEPARATOR_START, maxsplit=1) +_, after = data.split(SEPARATOR_END, maxsplit=1) +data = before + SEPARATOR_START + HELP + \ + toml.dumps(things) + SEPARATOR_END + after +with open('Cargo.toml', 'w') as f: + f.write(data) diff --git a/tests/stm32/src/bin/can.rs b/tests/stm32/src/bin/can.rs new file mode 100644 index 000000000..8bdd3c24f --- /dev/null +++ b/tests/stm32/src/bin/can.rs @@ -0,0 +1,81 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +// required-features: can + +#[path = "../common.rs"] +mod common; +use common::*; +use embassy_executor::Spawner; +use embassy_stm32::bind_interrupts; +use embassy_stm32::can::bxcan::filter::Mask32; +use embassy_stm32::can::bxcan::{Fifo, Frame, StandardId}; +use embassy_stm32::can::{Can, Rx0InterruptHandler, Rx1InterruptHandler, SceInterruptHandler, TxInterruptHandler}; +use embassy_stm32::gpio::{Input, Pull}; +use embassy_stm32::peripherals::CAN1; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + CAN1_RX0 => Rx0InterruptHandler; + CAN1_RX1 => Rx1InterruptHandler; + CAN1_SCE => SceInterruptHandler; + CAN1_TX => TxInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut p = embassy_stm32::init(config()); + info!("Hello World!"); + + // HW is connected as follows: + // PB13 -> PD0 + // PB12 -> PD1 + + // The next two lines are a workaround for testing without transceiver. + // To synchronise to the bus the RX input needs to see a high level. + // Use `mem::forget()` to release the borrow on the pin but keep the + // pull-up resistor enabled. + let rx_pin = Input::new(&mut p.PD0, Pull::Up); + core::mem::forget(rx_pin); + + let mut can = Can::new(p.CAN1, p.PD0, p.PD1, Irqs); + + info!("Configuring can..."); + + can.as_mut() + .modify_filters() + .enable_bank(0, Fifo::Fifo0, Mask32::accept_all()); + + can.set_bitrate(1_000_000); + can.as_mut() + .modify_config() + .set_loopback(true) // Receive own frames + .set_silent(true) + // .set_bit_timing(0x001c0003) + .enable(); + + info!("Can configured"); + + let mut i: u8 = 0; + loop { + let tx_frame = Frame::new_data(unwrap!(StandardId::new(i as _)), [i]); + + info!("Transmitting frame..."); + can.write(&tx_frame).await; + + info!("Receiving frame..."); + let (time, rx_frame) = can.read().await.unwrap(); + + info!("loopback time {}", time); + info!("loopback frame {=u8}", rx_frame.data().unwrap()[0]); + + i += 1; + if i > 10 { + break; + } + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/tests/stm32/src/bin/gpio.rs b/tests/stm32/src/bin/gpio.rs index 18fd85d44..aad174431 100644 --- a/tests/stm32/src/bin/gpio.rs +++ b/tests/stm32/src/bin/gpio.rs @@ -1,13 +1,13 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; -#[path = "../example_common.rs"] -mod example_common; +use common::*; use defmt::assert; use embassy_executor::Spawner; use embassy_stm32::gpio::{Flex, Input, Level, Output, OutputOpenDrain, Pull, Speed}; -use example_common::*; #[embassy_executor::main] async fn main(_spawner: Spawner) { @@ -30,20 +30,56 @@ async fn main(_spawner: Spawner) { let (mut a, mut b) = (p.PB6, p.PB7); #[cfg(feature = "stm32u585ai")] let (mut a, mut b) = (p.PD9, p.PD8); + #[cfg(feature = "stm32h563zi")] + let (mut a, mut b) = (p.PB6, p.PB7); + #[cfg(feature = "stm32c031c6")] + let (mut a, mut b) = (p.PB6, p.PB7); // Test initial output { let b = Input::new(&mut b, Pull::None); { - let _a = Output::new(&mut a, Level::Low, Speed::Low); + let a = Output::new(&mut a, Level::Low, Speed::Low); delay(); assert!(b.is_low()); + assert!(!b.is_high()); + assert!(a.is_set_low()); + assert!(!a.is_set_high()); } { - let _a = Output::new(&mut a, Level::High, Speed::Low); + let mut a = Output::new(&mut a, Level::High, Speed::Low); + delay(); + assert!(!b.is_low()); + assert!(b.is_high()); + assert!(!a.is_set_low()); + assert!(a.is_set_high()); + + // Test is_set_low / is_set_high + a.set_low(); + delay(); + assert!(b.is_low()); + assert!(a.is_set_low()); + assert!(!a.is_set_high()); + + a.set_high(); delay(); assert!(b.is_high()); + assert!(!a.is_set_low()); + assert!(a.is_set_high()); + + // Test toggle + a.toggle(); + delay(); + assert!(b.is_low()); + assert!(a.is_set_low()); + assert!(!a.is_set_high()); + + a.toggle(); + delay(); + assert!(b.is_high()); + assert!(!a.is_set_low()); + assert!(a.is_set_high()); } } diff --git a/tests/stm32/src/bin/rtc.rs b/tests/stm32/src/bin/rtc.rs new file mode 100644 index 000000000..194b153d5 --- /dev/null +++ b/tests/stm32/src/bin/rtc.rs @@ -0,0 +1,50 @@ +// required-features: chrono + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; + +use chrono::{NaiveDate, NaiveDateTime}; +use common::*; +use defmt::assert; +use embassy_executor::Spawner; +use embassy_stm32::pac; +use embassy_stm32::rtc::{Rtc, RtcConfig}; +use embassy_time::{Duration, Timer}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(config()); + info!("Hello World!"); + + let now = NaiveDate::from_ymd_opt(2020, 5, 15) + .unwrap() + .and_hms_opt(10, 30, 15) + .unwrap(); + + info!("Starting LSI"); + + pac::RCC.csr().modify(|w| w.set_lsion(true)); + while !pac::RCC.csr().read().lsirdy() {} + + info!("Started LSI"); + + let mut rtc = Rtc::new(p.RTC, RtcConfig::default()); + + rtc.set_datetime(now.into()).expect("datetime not set"); + + info!("Waiting 5 seconds"); + Timer::after(Duration::from_millis(5000)).await; + + let then: NaiveDateTime = rtc.now().unwrap().into(); + let seconds = (then - now).num_seconds(); + + defmt::info!("measured = {}", seconds); + + assert!(seconds > 3 && seconds < 7); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/tests/stm32/src/bin/sdmmc.rs b/tests/stm32/src/bin/sdmmc.rs new file mode 100644 index 000000000..515025386 --- /dev/null +++ b/tests/stm32/src/bin/sdmmc.rs @@ -0,0 +1,145 @@ +// required-features: sdmmc +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; + +use defmt::{assert_eq, *}; +use embassy_executor::Spawner; +use embassy_stm32::sdmmc::{DataBlock, Sdmmc}; +use embassy_stm32::time::mhz; +use embassy_stm32::{bind_interrupts, peripherals, sdmmc, Config}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + SDIO => sdmmc::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello World!"); + + let mut config = Config::default(); + config.rcc.sys_ck = Some(mhz(48)); + config.rcc.pll48 = true; + let p = embassy_stm32::init(config); + + #[cfg(feature = "stm32f429zi")] + let (mut sdmmc, mut dma, mut clk, mut cmd, mut d0, mut d1, mut d2, mut d3) = + (p.SDIO, p.DMA2_CH3, p.PC12, p.PD2, p.PC8, p.PC9, p.PC10, p.PC11); + + // Arbitrary block index + let block_idx = 16; + + let mut pattern1 = DataBlock([0u8; 512]); + let mut pattern2 = DataBlock([0u8; 512]); + for i in 0..512 { + pattern1[i] = i as u8; + pattern2[i] = !i as u8; + } + + let mut block = DataBlock([0u8; 512]); + + // ======== Try 4bit. ============== + info!("initializing in 4-bit mode..."); + let mut s = Sdmmc::new_4bit( + &mut sdmmc, + Irqs, + &mut dma, + &mut clk, + &mut cmd, + &mut d0, + &mut d1, + &mut d2, + &mut d3, + Default::default(), + ); + + let mut err = None; + loop { + match s.init_card(mhz(24)).await { + Ok(_) => break, + Err(e) => { + if err != Some(e) { + info!("waiting for card: {:?}", e); + err = Some(e); + } + } + } + } + + let card = unwrap!(s.card()); + + info!("Card: {:#?}", Debug2Format(card)); + info!("Clock: {}", s.clock()); + + info!("writing pattern1..."); + s.write_block(block_idx, &pattern1).await.unwrap(); + + info!("reading..."); + s.read_block(block_idx, &mut block).await.unwrap(); + assert_eq!(block, pattern1); + + info!("writing pattern2..."); + s.write_block(block_idx, &pattern2).await.unwrap(); + + info!("reading..."); + s.read_block(block_idx, &mut block).await.unwrap(); + assert_eq!(block, pattern2); + + drop(s); + + // ======== Try 1bit. ============== + info!("initializing in 1-bit mode..."); + let mut s = Sdmmc::new_1bit( + &mut sdmmc, + Irqs, + &mut dma, + &mut clk, + &mut cmd, + &mut d0, + Default::default(), + ); + + let mut err = None; + loop { + match s.init_card(mhz(24)).await { + Ok(_) => break, + Err(e) => { + if err != Some(e) { + info!("waiting for card: {:?}", e); + err = Some(e); + } + } + } + } + + let card = unwrap!(s.card()); + + info!("Card: {:#?}", Debug2Format(card)); + info!("Clock: {}", s.clock()); + + info!("reading pattern2 written in 4bit mode..."); + s.read_block(block_idx, &mut block).await.unwrap(); + assert_eq!(block, pattern2); + + info!("writing pattern1..."); + s.write_block(block_idx, &pattern1).await.unwrap(); + + info!("reading..."); + s.read_block(block_idx, &mut block).await.unwrap(); + assert_eq!(block, pattern1); + + info!("writing pattern2..."); + s.write_block(block_idx, &pattern2).await.unwrap(); + + info!("reading..."); + s.read_block(block_idx, &mut block).await.unwrap(); + assert_eq!(block, pattern2); + + drop(s); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/tests/stm32/src/bin/spi.rs b/tests/stm32/src/bin/spi.rs index 1c5dc87c0..819ecae3c 100644 --- a/tests/stm32/src/bin/spi.rs +++ b/tests/stm32/src/bin/spi.rs @@ -1,15 +1,15 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; -#[path = "../example_common.rs"] -mod example_common; +use common::*; use defmt::assert_eq; use embassy_executor::Spawner; use embassy_stm32::dma::NoDma; use embassy_stm32::spi::{self, Spi}; use embassy_stm32::time::Hertz; -use example_common::*; #[embassy_executor::main] async fn main(_spawner: Spawner) { @@ -17,22 +17,26 @@ async fn main(_spawner: Spawner) { info!("Hello World!"); #[cfg(feature = "stm32f103c8")] - let (sck, mosi, miso) = (p.PA5, p.PA7, p.PA6); + let (spi, sck, mosi, miso) = (p.SPI1, p.PA5, p.PA7, p.PA6); #[cfg(feature = "stm32f429zi")] - let (sck, mosi, miso) = (p.PA5, p.PA7, p.PA6); + let (spi, sck, mosi, miso) = (p.SPI1, p.PA5, p.PA7, p.PA6); #[cfg(feature = "stm32h755zi")] - let (sck, mosi, miso) = (p.PA5, p.PB5, p.PA6); + let (spi, sck, mosi, miso) = (p.SPI1, p.PA5, p.PB5, p.PA6); #[cfg(feature = "stm32g491re")] - let (sck, mosi, miso) = (p.PA5, p.PA7, p.PA6); + let (spi, sck, mosi, miso) = (p.SPI1, p.PA5, p.PA7, p.PA6); #[cfg(feature = "stm32g071rb")] - let (sck, mosi, miso) = (p.PA5, p.PA7, p.PA6); + let (spi, sck, mosi, miso) = (p.SPI1, p.PA5, p.PA7, p.PA6); #[cfg(feature = "stm32wb55rg")] - let (sck, mosi, miso) = (p.PA5, p.PA7, p.PA6); + let (spi, sck, mosi, miso) = (p.SPI1, p.PA5, p.PA7, p.PA6); #[cfg(feature = "stm32u585ai")] - let (sck, mosi, miso) = (p.PE13, p.PE15, p.PE14); + let (spi, sck, mosi, miso) = (p.SPI1, p.PE13, p.PE15, p.PE14); + #[cfg(feature = "stm32h563zi")] + let (spi, sck, mosi, miso) = (p.SPI4, p.PE12, p.PE14, p.PE13); + #[cfg(feature = "stm32c031c6")] + let (spi, sck, mosi, miso) = (p.SPI1, p.PA5, p.PA7, p.PA6); let mut spi = Spi::new( - p.SPI1, + spi, sck, // Arduino D13 mosi, // Arduino D11 miso, // Arduino D12 @@ -46,7 +50,7 @@ async fn main(_spawner: Spawner) { // Arduino pins D11 and D12 (MOSI-MISO) are connected together with a 1K resistor. // so we should get the data we sent back. - let mut buf = data; + let mut buf = [0; 9]; spi.blocking_transfer(&mut buf, &data).unwrap(); assert_eq!(buf, data); diff --git a/tests/stm32/src/bin/spi_dma.rs b/tests/stm32/src/bin/spi_dma.rs index cb2152e0b..78aad24e1 100644 --- a/tests/stm32/src/bin/spi_dma.rs +++ b/tests/stm32/src/bin/spi_dma.rs @@ -1,14 +1,14 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; -#[path = "../example_common.rs"] -mod example_common; +use common::*; use defmt::assert_eq; use embassy_executor::Spawner; use embassy_stm32::spi::{self, Spi}; use embassy_stm32::time::Hertz; -use example_common::*; #[embassy_executor::main] async fn main(_spawner: Spawner) { @@ -16,22 +16,26 @@ async fn main(_spawner: Spawner) { info!("Hello World!"); #[cfg(feature = "stm32f103c8")] - let (sck, mosi, miso, tx_dma, rx_dma) = (p.PA5, p.PA7, p.PA6, p.DMA1_CH3, p.DMA1_CH2); + let (spi, sck, mosi, miso, tx_dma, rx_dma) = (p.SPI1, p.PA5, p.PA7, p.PA6, p.DMA1_CH3, p.DMA1_CH2); #[cfg(feature = "stm32f429zi")] - let (sck, mosi, miso, tx_dma, rx_dma) = (p.PA5, p.PA7, p.PA6, p.DMA2_CH3, p.DMA2_CH2); + let (spi, sck, mosi, miso, tx_dma, rx_dma) = (p.SPI1, p.PA5, p.PA7, p.PA6, p.DMA2_CH3, p.DMA2_CH2); #[cfg(feature = "stm32h755zi")] - let (sck, mosi, miso, tx_dma, rx_dma) = (p.PA5, p.PB5, p.PA6, p.DMA1_CH0, p.DMA1_CH1); + let (spi, sck, mosi, miso, tx_dma, rx_dma) = (p.SPI1, p.PA5, p.PB5, p.PA6, p.DMA1_CH0, p.DMA1_CH1); #[cfg(feature = "stm32g491re")] - let (sck, mosi, miso, tx_dma, rx_dma) = (p.PA5, p.PA7, p.PA6, p.DMA1_CH1, p.DMA1_CH2); + let (spi, sck, mosi, miso, tx_dma, rx_dma) = (p.SPI1, p.PA5, p.PA7, p.PA6, p.DMA1_CH1, p.DMA1_CH2); #[cfg(feature = "stm32g071rb")] - let (sck, mosi, miso, tx_dma, rx_dma) = (p.PA5, p.PA7, p.PA6, p.DMA1_CH1, p.DMA1_CH2); + let (spi, sck, mosi, miso, tx_dma, rx_dma) = (p.SPI1, p.PA5, p.PA7, p.PA6, p.DMA1_CH1, p.DMA1_CH2); #[cfg(feature = "stm32wb55rg")] - let (sck, mosi, miso, tx_dma, rx_dma) = (p.PA5, p.PA7, p.PA6, p.DMA1_CH1, p.DMA1_CH2); + let (spi, sck, mosi, miso, tx_dma, rx_dma) = (p.SPI1, p.PA5, p.PA7, p.PA6, p.DMA1_CH1, p.DMA1_CH2); #[cfg(feature = "stm32u585ai")] - let (sck, mosi, miso, tx_dma, rx_dma) = (p.PE13, p.PE15, p.PE14, p.GPDMA1_CH0, p.GPDMA1_CH1); + let (spi, sck, mosi, miso, tx_dma, rx_dma) = (p.SPI1, p.PE13, p.PE15, p.PE14, p.GPDMA1_CH0, p.GPDMA1_CH1); + #[cfg(feature = "stm32h563zi")] + let (spi, sck, mosi, miso, tx_dma, rx_dma) = (p.SPI4, p.PE12, p.PE14, p.PE13, p.GPDMA1_CH0, p.GPDMA1_CH1); + #[cfg(feature = "stm32c031c6")] + let (spi, sck, mosi, miso, tx_dma, rx_dma) = (p.SPI1, p.PA5, p.PA7, p.PA6, p.DMA1_CH1, p.DMA1_CH2); let mut spi = Spi::new( - p.SPI1, + spi, sck, // Arduino D13 mosi, // Arduino D11 miso, // Arduino D12 diff --git a/tests/stm32/src/bin/timer.rs b/tests/stm32/src/bin/timer.rs index e00e43bf1..f8b453cda 100644 --- a/tests/stm32/src/bin/timer.rs +++ b/tests/stm32/src/bin/timer.rs @@ -1,13 +1,13 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; -#[path = "../example_common.rs"] -mod example_common; +use common::*; use defmt::assert; use embassy_executor::Spawner; use embassy_time::{Duration, Instant, Timer}; -use example_common::*; #[embassy_executor::main] async fn main(_spawner: Spawner) { diff --git a/tests/stm32/src/bin/usart.rs b/tests/stm32/src/bin/usart.rs index 7673bfe6d..394005b82 100644 --- a/tests/stm32/src/bin/usart.rs +++ b/tests/stm32/src/bin/usart.rs @@ -1,14 +1,42 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; -#[path = "../example_common.rs"] -mod example_common; +use common::*; use defmt::assert_eq; use embassy_executor::Spawner; use embassy_stm32::dma::NoDma; -use embassy_stm32::usart::{Config, Uart}; -use example_common::*; +use embassy_stm32::usart::{Config, Error, Uart}; +use embassy_stm32::{bind_interrupts, peripherals, usart}; +use embassy_time::{Duration, Instant}; + +#[cfg(any( + feature = "stm32f103c8", + feature = "stm32g491re", + feature = "stm32g071rb", + feature = "stm32h755zi", + feature = "stm32c031c6", +))] +bind_interrupts!(struct Irqs { + USART1 => usart::InterruptHandler; +}); + +#[cfg(feature = "stm32u585ai")] +bind_interrupts!(struct Irqs { + USART3 => usart::InterruptHandler; +}); + +#[cfg(feature = "stm32f429zi")] +bind_interrupts!(struct Irqs { + USART6 => usart::InterruptHandler; +}); + +#[cfg(any(feature = "stm32wb55rg", feature = "stm32h563zi"))] +bind_interrupts!(struct Irqs { + LPUART1 => usart::InterruptHandler; +}); #[embassy_executor::main] async fn main(_spawner: Spawner) { @@ -18,32 +46,96 @@ async fn main(_spawner: Spawner) { // Arduino pins D0 and D1 // They're connected together with a 1K resistor. #[cfg(feature = "stm32f103c8")] - let (tx, rx, usart) = (p.PA9, p.PA10, p.USART1); + let (mut tx, mut rx, mut usart) = (p.PA9, p.PA10, p.USART1); #[cfg(feature = "stm32g491re")] - let (tx, rx, usart) = (p.PC4, p.PC5, p.USART1); + let (mut tx, mut rx, mut usart) = (p.PC4, p.PC5, p.USART1); #[cfg(feature = "stm32g071rb")] - let (tx, rx, usart) = (p.PC4, p.PC5, p.USART1); + let (mut tx, mut rx, mut usart) = (p.PC4, p.PC5, p.USART1); #[cfg(feature = "stm32f429zi")] - let (tx, rx, usart) = (p.PG14, p.PG9, p.USART6); + let (mut tx, mut rx, mut usart) = (p.PG14, p.PG9, p.USART6); #[cfg(feature = "stm32wb55rg")] - let (tx, rx, usart) = (p.PA2, p.PA3, p.LPUART1); + let (mut tx, mut rx, mut usart) = (p.PA2, p.PA3, p.LPUART1); #[cfg(feature = "stm32h755zi")] - let (tx, rx, usart) = (p.PB6, p.PB7, p.USART1); + let (mut tx, mut rx, mut usart) = (p.PB6, p.PB7, p.USART1); #[cfg(feature = "stm32u585ai")] - let (tx, rx, usart) = (p.PD8, p.PD9, p.USART3); + let (mut tx, mut rx, mut usart) = (p.PD8, p.PD9, p.USART3); + #[cfg(feature = "stm32h563zi")] + let (mut tx, mut rx, mut usart) = (p.PB6, p.PB7, p.LPUART1); + #[cfg(feature = "stm32c031c6")] + let (mut tx, mut rx, mut usart) = (p.PB6, p.PB7, p.USART1); - let config = Config::default(); - let mut usart = Uart::new(usart, rx, tx, NoDma, NoDma, config); + { + let config = Config::default(); + let mut usart = Uart::new(&mut usart, &mut rx, &mut tx, Irqs, NoDma, NoDma, config); - // We can't send too many bytes, they have to fit in the FIFO. - // This is because we aren't sending+receiving at the same time. + // We can't send too many bytes, they have to fit in the FIFO. + // This is because we aren't sending+receiving at the same time. - let data = [0xC0, 0xDE]; - usart.blocking_write(&data).unwrap(); + let data = [0xC0, 0xDE]; + usart.blocking_write(&data).unwrap(); - let mut buf = [0; 2]; - usart.blocking_read(&mut buf).unwrap(); - assert_eq!(buf, data); + let mut buf = [0; 2]; + usart.blocking_read(&mut buf).unwrap(); + assert_eq!(buf, data); + } + + // Test error handling with with an overflow error + { + let config = Config::default(); + let mut usart = Uart::new(&mut usart, &mut rx, &mut tx, Irqs, NoDma, NoDma, config); + + // Send enough bytes to fill the RX FIFOs off all USART versions. + let data = [0xC0, 0xDE, 0x12, 0x23, 0x34]; + usart.blocking_write(&data).unwrap(); + usart.blocking_flush().unwrap(); + + // The error should be reported first. + let mut buf = [0; 1]; + let err = usart.blocking_read(&mut buf); + assert_eq!(err, Err(Error::Overrun)); + + // At least the first data byte should still be available on all USART versions. + usart.blocking_read(&mut buf).unwrap(); + assert_eq!(buf[0], data[0]); + } + + // Test that baudrate divider is calculated correctly. + // Do it by comparing the time it takes to send a known number of bytes. + for baudrate in [ + 300, + 9600, + 115200, + 250_000, + 337_934, + #[cfg(not(feature = "stm32f103c8"))] + 1_000_000, + #[cfg(not(feature = "stm32f103c8"))] + 2_000_000, + ] { + info!("testing baudrate {}", baudrate); + + let mut config = Config::default(); + config.baudrate = baudrate; + let mut usart = Uart::new(&mut usart, &mut rx, &mut tx, Irqs, NoDma, NoDma, config); + + let n = (baudrate as usize / 100).max(64); + + let start = Instant::now(); + for _ in 0..n { + usart.blocking_write(&[0x00]).unwrap(); + } + let dur = Instant::now() - start; + let want_dur = Duration::from_micros(n as u64 * 10 * 1_000_000 / (baudrate as u64)); + let fuzz = want_dur / 5; + if dur < want_dur - fuzz || dur > want_dur + fuzz { + defmt::panic!( + "bad duration for baudrate {}: got {:?} want {:?}", + baudrate, + dur, + want_dur + ); + } + } info!("Test OK"); cortex_m::asm::bkpt(); diff --git a/tests/stm32/src/bin/usart_dma.rs b/tests/stm32/src/bin/usart_dma.rs index e0389446f..c34d9574b 100644 --- a/tests/stm32/src/bin/usart_dma.rs +++ b/tests/stm32/src/bin/usart_dma.rs @@ -1,13 +1,41 @@ #![no_std] #![no_main] #![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; -#[path = "../example_common.rs"] -mod example_common; +use common::*; use defmt::assert_eq; use embassy_executor::Spawner; +use embassy_futures::join::join; use embassy_stm32::usart::{Config, Uart}; -use example_common::*; +use embassy_stm32::{bind_interrupts, peripherals, usart}; + +#[cfg(any( + feature = "stm32f103c8", + feature = "stm32g491re", + feature = "stm32g071rb", + feature = "stm32h755zi", + feature = "stm32c031c6", +))] +bind_interrupts!(struct Irqs { + USART1 => usart::InterruptHandler; +}); + +#[cfg(feature = "stm32u585ai")] +bind_interrupts!(struct Irqs { + USART3 => usart::InterruptHandler; +}); + +#[cfg(feature = "stm32f429zi")] +bind_interrupts!(struct Irqs { + USART6 => usart::InterruptHandler; +}); + +#[cfg(any(feature = "stm32wb55rg", feature = "stm32h563zi"))] +bind_interrupts!(struct Irqs { + LPUART1 => usart::InterruptHandler; +}); #[embassy_executor::main] async fn main(_spawner: Spawner) { @@ -17,33 +45,51 @@ async fn main(_spawner: Spawner) { // Arduino pins D0 and D1 // They're connected together with a 1K resistor. #[cfg(feature = "stm32f103c8")] - let (tx, rx, usart, tx_dma, rx_dma) = (p.PA9, p.PA10, p.USART1, p.DMA1_CH4, p.DMA1_CH5); + let (tx, rx, usart, irq, tx_dma, rx_dma) = (p.PA9, p.PA10, p.USART1, Irqs, p.DMA1_CH4, p.DMA1_CH5); #[cfg(feature = "stm32g491re")] - let (tx, rx, usart, tx_dma, rx_dma) = (p.PC4, p.PC5, p.USART1, p.DMA1_CH1, p.DMA1_CH2); + let (tx, rx, usart, irq, tx_dma, rx_dma) = (p.PC4, p.PC5, p.USART1, Irqs, p.DMA1_CH1, p.DMA1_CH2); #[cfg(feature = "stm32g071rb")] - let (tx, rx, usart, tx_dma, rx_dma) = (p.PC4, p.PC5, p.USART1, p.DMA1_CH1, p.DMA1_CH2); + let (tx, rx, usart, irq, tx_dma, rx_dma) = (p.PC4, p.PC5, p.USART1, Irqs, p.DMA1_CH1, p.DMA1_CH2); #[cfg(feature = "stm32f429zi")] - let (tx, rx, usart, tx_dma, rx_dma) = (p.PG14, p.PG9, p.USART6, p.DMA2_CH6, p.DMA2_CH1); + let (tx, rx, usart, irq, tx_dma, rx_dma) = (p.PG14, p.PG9, p.USART6, Irqs, p.DMA2_CH6, p.DMA2_CH1); #[cfg(feature = "stm32wb55rg")] - let (tx, rx, usart, tx_dma, rx_dma) = (p.PA2, p.PA3, p.LPUART1, p.DMA1_CH1, p.DMA1_CH2); + let (tx, rx, usart, irq, tx_dma, rx_dma) = (p.PA2, p.PA3, p.LPUART1, Irqs, p.DMA1_CH1, p.DMA1_CH2); #[cfg(feature = "stm32h755zi")] - let (tx, rx, usart, tx_dma, rx_dma) = (p.PB6, p.PB7, p.USART1, p.DMA1_CH0, p.DMA1_CH1); + let (tx, rx, usart, irq, tx_dma, rx_dma) = (p.PB6, p.PB7, p.USART1, Irqs, p.DMA1_CH0, p.DMA1_CH1); #[cfg(feature = "stm32u585ai")] - let (tx, rx, usart, tx_dma, rx_dma) = (p.PD8, p.PD9, p.USART3, p.GPDMA1_CH0, p.GPDMA1_CH1); + let (tx, rx, usart, irq, tx_dma, rx_dma) = (p.PD8, p.PD9, p.USART3, Irqs, p.GPDMA1_CH0, p.GPDMA1_CH1); + #[cfg(feature = "stm32h563zi")] + let (tx, rx, usart, irq, tx_dma, rx_dma) = (p.PB6, p.PB7, p.LPUART1, Irqs, p.GPDMA1_CH0, p.GPDMA1_CH1); + #[cfg(feature = "stm32c031c6")] + let (tx, rx, usart, irq, tx_dma, rx_dma) = (p.PB6, p.PB7, p.USART1, Irqs, p.DMA1_CH1, p.DMA1_CH2); let config = Config::default(); - let mut usart = Uart::new(usart, rx, tx, tx_dma, rx_dma, config); + let usart = Uart::new(usart, rx, tx, irq, tx_dma, rx_dma, config); - // We can't send too many bytes, they have to fit in the FIFO. - // This is because we aren't sending+receiving at the same time. - // For whatever reason, blocking works with 2 bytes but DMA only with 1?? + const LEN: usize = 128; + let mut tx_buf = [0; LEN]; + let mut rx_buf = [0; LEN]; - let data = [0x42]; - usart.write(&data).await.unwrap(); + let (mut tx, mut rx) = usart.split(); - let mut buf = [0; 1]; - usart.read(&mut buf).await.unwrap(); - assert_eq!(buf, data); + for n in 0..42 { + for i in 0..LEN { + tx_buf[i] = (i ^ n) as u8; + } + + let tx_fut = async { + tx.write(&tx_buf).await.unwrap(); + }; + let rx_fut = async { + rx.read(&mut rx_buf).await.unwrap(); + }; + + // note: rx needs to be polled first, to workaround this bug: + // https://github.com/embassy-rs/embassy/issues/1426 + join(rx_fut, tx_fut).await; + + assert_eq!(tx_buf, rx_buf); + } info!("Test OK"); cortex_m::asm::bkpt(); diff --git a/tests/stm32/src/bin/usart_rx_ringbuffered.rs b/tests/stm32/src/bin/usart_rx_ringbuffered.rs new file mode 100644 index 000000000..c8dd2643b --- /dev/null +++ b/tests/stm32/src/bin/usart_rx_ringbuffered.rs @@ -0,0 +1,198 @@ +// required-features: not-gpdma + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; + +use common::*; +use defmt::{assert_eq, panic}; +use embassy_executor::Spawner; +use embassy_stm32::usart::{Config, DataBits, Parity, RingBufferedUartRx, StopBits, Uart, UartTx}; +use embassy_stm32::{bind_interrupts, peripherals, usart}; +use embassy_time::{Duration, Timer}; +use rand_chacha::ChaCha8Rng; +use rand_core::{RngCore, SeedableRng}; + +#[cfg(any( + feature = "stm32f103c8", + feature = "stm32g491re", + feature = "stm32g071rb", + feature = "stm32h755zi", + feature = "stm32c031c6", +))] +bind_interrupts!(struct Irqs { + USART1 => usart::InterruptHandler; +}); + +#[cfg(feature = "stm32u585ai")] +bind_interrupts!(struct Irqs { + USART3 => usart::InterruptHandler; +}); + +#[cfg(feature = "stm32f429zi")] +bind_interrupts!(struct Irqs { + USART1 => usart::InterruptHandler; + USART6 => usart::InterruptHandler; +}); + +#[cfg(any(feature = "stm32wb55rg", feature = "stm32h563zi"))] +bind_interrupts!(struct Irqs { + LPUART1 => usart::InterruptHandler; +}); + +#[cfg(feature = "stm32f103c8")] +mod board { + pub type Uart = embassy_stm32::peripherals::USART1; + pub type TxDma = embassy_stm32::peripherals::DMA1_CH4; + pub type RxDma = embassy_stm32::peripherals::DMA1_CH5; +} +#[cfg(feature = "stm32g491re")] +mod board { + pub type Uart = embassy_stm32::peripherals::USART1; + pub type TxDma = embassy_stm32::peripherals::DMA1_CH1; + pub type RxDma = embassy_stm32::peripherals::DMA1_CH2; +} +#[cfg(feature = "stm32g071rb")] +mod board { + pub type Uart = embassy_stm32::peripherals::USART1; + pub type TxDma = embassy_stm32::peripherals::DMA1_CH1; + pub type RxDma = embassy_stm32::peripherals::DMA1_CH2; +} +#[cfg(feature = "stm32f429zi")] +mod board { + pub type Uart = embassy_stm32::peripherals::USART6; + pub type TxDma = embassy_stm32::peripherals::DMA2_CH6; + pub type RxDma = embassy_stm32::peripherals::DMA2_CH1; +} +#[cfg(feature = "stm32wb55rg")] +mod board { + pub type Uart = embassy_stm32::peripherals::LPUART1; + pub type TxDma = embassy_stm32::peripherals::DMA1_CH1; + pub type RxDma = embassy_stm32::peripherals::DMA1_CH2; +} +#[cfg(feature = "stm32h755zi")] +mod board { + pub type Uart = embassy_stm32::peripherals::USART1; + pub type TxDma = embassy_stm32::peripherals::DMA1_CH0; + pub type RxDma = embassy_stm32::peripherals::DMA1_CH1; +} +#[cfg(feature = "stm32u585ai")] +mod board { + pub type Uart = embassy_stm32::peripherals::USART3; + pub type TxDma = embassy_stm32::peripherals::GPDMA1_CH0; + pub type RxDma = embassy_stm32::peripherals::GPDMA1_CH1; +} +#[cfg(feature = "stm32c031c6")] +mod board { + pub type Uart = embassy_stm32::peripherals::USART1; + pub type TxDma = embassy_stm32::peripherals::DMA1_CH1; + pub type RxDma = embassy_stm32::peripherals::DMA1_CH2; +} + +const DMA_BUF_SIZE: usize = 256; + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_stm32::init(config()); + info!("Hello World!"); + + // Arduino pins D0 and D1 + // They're connected together with a 1K resistor. + #[cfg(feature = "stm32f103c8")] + let (tx, rx, usart, tx_dma, rx_dma) = (p.PA9, p.PA10, p.USART1, p.DMA1_CH4, p.DMA1_CH5); + #[cfg(feature = "stm32g491re")] + let (tx, rx, usart, tx_dma, rx_dma) = (p.PC4, p.PC5, p.USART1, p.DMA1_CH1, p.DMA1_CH2); + #[cfg(feature = "stm32g071rb")] + let (tx, rx, usart, tx_dma, rx_dma) = (p.PC4, p.PC5, p.USART1, p.DMA1_CH1, p.DMA1_CH2); + #[cfg(feature = "stm32f429zi")] + let (tx, rx, usart, tx_dma, rx_dma) = (p.PG14, p.PG9, p.USART6, p.DMA2_CH6, p.DMA2_CH1); + #[cfg(feature = "stm32wb55rg")] + let (tx, rx, usart, tx_dma, rx_dma) = (p.PA2, p.PA3, p.LPUART1, p.DMA1_CH1, p.DMA1_CH2); + #[cfg(feature = "stm32h755zi")] + let (tx, rx, usart, tx_dma, rx_dma) = (p.PB6, p.PB7, p.USART1, p.DMA1_CH0, p.DMA1_CH1); + #[cfg(feature = "stm32u585ai")] + let (tx, rx, usart, tx_dma, rx_dma) = (p.PD8, p.PD9, p.USART3, p.GPDMA1_CH0, p.GPDMA1_CH1); + #[cfg(feature = "stm32c031c6")] + let (tx, rx, usart, tx_dma, rx_dma) = (p.PB6, p.PB7, p.USART1, p.DMA1_CH1, p.DMA1_CH2); + + // To run this test, use the saturating_serial test utility to saturate the serial port + + let mut config = Config::default(); + // this is the fastest we can go without tuning RCC + // some chips have default pclk=8mhz, and uart can run at max pclk/16 + config.baudrate = 500_000; + config.data_bits = DataBits::DataBits8; + config.stop_bits = StopBits::STOP1; + config.parity = Parity::ParityNone; + + let usart = Uart::new(usart, rx, tx, Irqs, tx_dma, rx_dma, config); + let (tx, rx) = usart.split(); + static mut DMA_BUF: [u8; DMA_BUF_SIZE] = [0; DMA_BUF_SIZE]; + let dma_buf = unsafe { DMA_BUF.as_mut() }; + let rx = rx.into_ring_buffered(dma_buf); + + info!("Spawning tasks"); + spawner.spawn(transmit_task(tx)).unwrap(); + spawner.spawn(receive_task(rx)).unwrap(); +} + +#[embassy_executor::task] +async fn transmit_task(mut tx: UartTx<'static, board::Uart, board::TxDma>) { + // workaround https://github.com/embassy-rs/embassy/issues/1426 + Timer::after(Duration::from_millis(100) as _).await; + + let mut rng = ChaCha8Rng::seed_from_u64(1337); + + info!("Starting random transmissions into void..."); + + let mut i: u8 = 0; + loop { + let mut buf = [0; 256]; + let len = 1 + (rng.next_u32() as usize % buf.len()); + for b in &mut buf[..len] { + *b = i; + i = i.wrapping_add(1); + } + + tx.write(&buf[..len]).await.unwrap(); + Timer::after(Duration::from_micros((rng.next_u32() % 1000) as _)).await; + } +} + +#[embassy_executor::task] +async fn receive_task(mut rx: RingBufferedUartRx<'static, board::Uart, board::RxDma>) { + info!("Ready to receive..."); + + let mut rng = ChaCha8Rng::seed_from_u64(1337); + + let mut i = 0; + let mut expected = 0; + loop { + let mut buf = [0; 256]; + let max_len = 1 + (rng.next_u32() as usize % buf.len()); + let received = match rx.read(&mut buf[..max_len]).await { + Ok(r) => r, + Err(e) => { + panic!("Test fail! read error: {:?}", e); + } + }; + + for byte in &buf[..received] { + assert_eq!(*byte, expected); + expected = expected.wrapping_add(1); + } + + if received < max_len { + Timer::after(Duration::from_micros((rng.next_u32() % 1000) as _)).await; + } + + i += received; + + if i > 100000 { + info!("Test OK!"); + cortex_m::asm::bkpt(); + } + } +} diff --git a/tests/stm32/src/bin/wpan_ble.rs b/tests/stm32/src/bin/wpan_ble.rs new file mode 100644 index 000000000..3ad8aca4e --- /dev/null +++ b/tests/stm32/src/bin/wpan_ble.rs @@ -0,0 +1,251 @@ +// required-features: ble + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; + +use core::time::Duration; + +use common::*; +use embassy_executor::Spawner; +use embassy_stm32::bind_interrupts; +use embassy_stm32::ipcc::{Config, ReceiveInterruptHandler, TransmitInterruptHandler}; +use embassy_stm32_wpan::hci::host::uart::UartHci; +use embassy_stm32_wpan::hci::host::{AdvertisingFilterPolicy, EncryptionKey, HostHci, OwnAddressType}; +use embassy_stm32_wpan::hci::types::AdvertisingType; +use embassy_stm32_wpan::hci::vendor::stm32wb::command::gap::{ + AdvertisingDataType, DiscoverableParameters, GapCommands, Role, +}; +use embassy_stm32_wpan::hci::vendor::stm32wb::command::gatt::GattCommands; +use embassy_stm32_wpan::hci::vendor::stm32wb::command::hal::{ConfigData, HalCommands, PowerLevel}; +use embassy_stm32_wpan::hci::BdAddr; +use embassy_stm32_wpan::lhci::LhciC1DeviceInformationCcrp; +use embassy_stm32_wpan::sub::mm; +use embassy_stm32_wpan::TlMbox; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs{ + IPCC_C1_RX => ReceiveInterruptHandler; + IPCC_C1_TX => TransmitInterruptHandler; +}); + +const BLE_GAP_DEVICE_NAME_LENGTH: u8 = 7; + +#[embassy_executor::task] +async fn run_mm_queue(memory_manager: mm::MemoryManager) { + memory_manager.run_queue().await; +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_stm32::init(config()); + info!("Hello World!"); + + let config = Config::default(); + let mut mbox = TlMbox::init(p.IPCC, Irqs, config); + + spawner.spawn(run_mm_queue(mbox.mm_subsystem)).unwrap(); + + let sys_event = mbox.sys_subsystem.read().await; + info!("sys event: {}", sys_event.payload()); + + let fw_info = mbox.sys_subsystem.wireless_fw_info().unwrap(); + let version_major = fw_info.version_major(); + let version_minor = fw_info.version_minor(); + let subversion = fw_info.subversion(); + + let sram2a_size = fw_info.sram2a_size(); + let sram2b_size = fw_info.sram2b_size(); + + info!( + "version {}.{}.{} - SRAM2a {} - SRAM2b {}", + version_major, version_minor, subversion, sram2a_size, sram2b_size + ); + + let _ = mbox.sys_subsystem.shci_c2_ble_init(Default::default()).await; + + info!("resetting BLE..."); + mbox.ble_subsystem.reset().await; + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); + + info!("config public address..."); + mbox.ble_subsystem + .write_config_data(&ConfigData::public_address(get_bd_addr()).build()) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); + + info!("config random address..."); + mbox.ble_subsystem + .write_config_data(&ConfigData::random_address(get_random_addr()).build()) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); + + info!("config identity root..."); + mbox.ble_subsystem + .write_config_data(&ConfigData::identity_root(&get_irk()).build()) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); + + info!("config encryption root..."); + mbox.ble_subsystem + .write_config_data(&ConfigData::encryption_root(&get_erk()).build()) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); + + info!("config tx power level..."); + mbox.ble_subsystem.set_tx_power_level(PowerLevel::ZerodBm).await; + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); + + info!("GATT init..."); + mbox.ble_subsystem.init_gatt().await; + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); + + info!("GAP init..."); + mbox.ble_subsystem + .init_gap(Role::PERIPHERAL, false, BLE_GAP_DEVICE_NAME_LENGTH) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); + + // info!("set scan response..."); + // mbox.ble_subsystem.le_set_scan_response_data(&[]).await.unwrap(); + // let response = mbox.ble_subsystem.read().await.unwrap(); + // info!("{}", response); + + info!("set discoverable..."); + mbox.ble_subsystem + .set_discoverable(&DiscoverableParameters { + advertising_type: AdvertisingType::NonConnectableUndirected, + advertising_interval: Some((Duration::from_millis(250), Duration::from_millis(250))), + address_type: OwnAddressType::Public, + filter_policy: AdvertisingFilterPolicy::AllowConnectionAndScan, + local_name: None, + advertising_data: &[], + conn_interval: (None, None), + }) + .await + .unwrap(); + + let response = mbox.ble_subsystem.read().await; + info!("{}", response); + + // remove some advertisement to decrease the packet size + info!("delete tx power ad type..."); + mbox.ble_subsystem + .delete_ad_type(AdvertisingDataType::TxPowerLevel) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); + + info!("delete conn interval ad type..."); + mbox.ble_subsystem + .delete_ad_type(AdvertisingDataType::PeripheralConnectionInterval) + .await; + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); + + info!("update advertising data..."); + mbox.ble_subsystem + .update_advertising_data(&eddystone_advertising_data()) + .await + .unwrap(); + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); + + info!("update advertising data type..."); + mbox.ble_subsystem + .update_advertising_data(&[3, AdvertisingDataType::UuidCompleteList16 as u8, 0xaa, 0xfe]) + .await + .unwrap(); + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); + + info!("update advertising data flags..."); + mbox.ble_subsystem + .update_advertising_data(&[ + 2, + AdvertisingDataType::Flags as u8, + (0x02 | 0x04) as u8, // BLE general discoverable, without BR/EDR support + ]) + .await + .unwrap(); + let response = mbox.ble_subsystem.read().await.unwrap(); + info!("{}", response); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} + +fn get_bd_addr() -> BdAddr { + let mut bytes = [0u8; 6]; + + let lhci_info = LhciC1DeviceInformationCcrp::new(); + bytes[0] = (lhci_info.uid64 & 0xff) as u8; + bytes[1] = ((lhci_info.uid64 >> 8) & 0xff) as u8; + bytes[2] = ((lhci_info.uid64 >> 16) & 0xff) as u8; + bytes[3] = lhci_info.device_type_id; + bytes[4] = (lhci_info.st_company_id & 0xff) as u8; + bytes[5] = (lhci_info.st_company_id >> 8 & 0xff) as u8; + + BdAddr(bytes) +} + +fn get_random_addr() -> BdAddr { + let mut bytes = [0u8; 6]; + + let lhci_info = LhciC1DeviceInformationCcrp::new(); + bytes[0] = (lhci_info.uid64 & 0xff) as u8; + bytes[1] = ((lhci_info.uid64 >> 8) & 0xff) as u8; + bytes[2] = ((lhci_info.uid64 >> 16) & 0xff) as u8; + bytes[3] = 0; + bytes[4] = 0x6E; + bytes[5] = 0xED; + + BdAddr(bytes) +} + +const BLE_CFG_IRK: [u8; 16] = [ + 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, +]; +const BLE_CFG_ERK: [u8; 16] = [ + 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21, 0xfe, 0xdc, 0xba, 0x09, 0x87, 0x65, 0x43, 0x21, +]; + +fn get_irk() -> EncryptionKey { + EncryptionKey(BLE_CFG_IRK) +} + +fn get_erk() -> EncryptionKey { + EncryptionKey(BLE_CFG_ERK) +} + +fn eddystone_advertising_data() -> [u8; 24] { + const EDDYSTONE_URL: &[u8] = b"www.rust-lang.com"; + + let mut service_data = [0u8; 24]; + let url_len = EDDYSTONE_URL.len(); + + service_data[0] = 6 + url_len as u8; + service_data[1] = AdvertisingDataType::ServiceData as u8; + + // 16-bit eddystone uuid + service_data[2] = 0xaa; + service_data[3] = 0xFE; + + service_data[4] = 0x10; // URL frame type + service_data[5] = 22_i8 as u8; // calibrated TX power at 0m + service_data[6] = 0x03; // eddystone url prefix = https + + service_data[7..(7 + url_len)].copy_from_slice(EDDYSTONE_URL); + + service_data +} diff --git a/tests/stm32/src/bin/wpan_mac.rs b/tests/stm32/src/bin/wpan_mac.rs new file mode 100644 index 000000000..d64a5ef81 --- /dev/null +++ b/tests/stm32/src/bin/wpan_mac.rs @@ -0,0 +1,120 @@ +// required-features: mac + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] +#[path = "../common.rs"] +mod common; + +use common::*; +use embassy_executor::Spawner; +use embassy_stm32::bind_interrupts; +use embassy_stm32::ipcc::{Config, ReceiveInterruptHandler, TransmitInterruptHandler}; +use embassy_stm32_wpan::mac::commands::{AssociateRequest, GetRequest, ResetRequest, SetRequest}; +use embassy_stm32_wpan::mac::event::MacEvent; +use embassy_stm32_wpan::mac::typedefs::{ + AddressMode, Capabilities, KeyIdMode, MacAddress, MacChannel, PanId, PibId, SecurityLevel, +}; +use embassy_stm32_wpan::sub::mm; +use embassy_stm32_wpan::TlMbox; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs{ + IPCC_C1_RX => ReceiveInterruptHandler; + IPCC_C1_TX => TransmitInterruptHandler; +}); + +#[embassy_executor::task] +async fn run_mm_queue(memory_manager: mm::MemoryManager) { + memory_manager.run_queue().await; +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_stm32::init(config()); + info!("Hello World!"); + + let config = Config::default(); + let mbox = TlMbox::init(p.IPCC, Irqs, config); + + spawner.spawn(run_mm_queue(mbox.mm_subsystem)).unwrap(); + + let sys_event = mbox.sys_subsystem.read().await; + info!("sys event: {}", sys_event.payload()); + + core::mem::drop(sys_event); + + let result = mbox.sys_subsystem.shci_c2_mac_802_15_4_init().await; + info!("initialized mac: {}", result); + + info!("resetting"); + mbox.mac_subsystem + .send_command(&ResetRequest { + set_default_pib: true, + ..Default::default() + }) + .await + .unwrap(); + { + let evt = mbox.mac_subsystem.read().await; + info!("{:#x}", evt.mac_event()); + } + + info!("setting extended address"); + let extended_address: u64 = 0xACDE480000000002; + mbox.mac_subsystem + .send_command(&SetRequest { + pib_attribute_ptr: &extended_address as *const _ as *const u8, + pib_attribute: PibId::ExtendedAddress, + }) + .await + .unwrap(); + { + let evt = mbox.mac_subsystem.read().await; + info!("{:#x}", evt.mac_event()); + } + + info!("getting extended address"); + mbox.mac_subsystem + .send_command(&GetRequest { + pib_attribute: PibId::ExtendedAddress, + ..Default::default() + }) + .await + .unwrap(); + { + let evt = mbox.mac_subsystem.read().await; + info!("{:#x}", evt.mac_event()); + + if let Ok(MacEvent::MlmeGetCnf(evt)) = evt.mac_event() { + if evt.pib_attribute_value_len == 8 { + let value = unsafe { core::ptr::read_unaligned(evt.pib_attribute_value_ptr as *const u64) }; + + info!("value {:#x}", value) + } + } + } + + info!("assocation request"); + let a = AssociateRequest { + channel_number: MacChannel::Channel16, + channel_page: 0, + coord_addr_mode: AddressMode::Short, + coord_address: MacAddress { short: [34, 17] }, + capability_information: Capabilities::ALLOCATE_ADDRESS, + coord_pan_id: PanId([0x1A, 0xAA]), + security_level: SecurityLevel::Unsecure, + key_id_mode: KeyIdMode::Implicite, + key_source: [0; 8], + key_index: 152, + }; + info!("{}", a); + mbox.mac_subsystem.send_command(&a).await.unwrap(); + { + let evt = mbox.mac_subsystem.read().await; + info!("{:#x}", evt.mac_event()); + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/tests/stm32/src/common.rs b/tests/stm32/src/common.rs new file mode 100644 index 000000000..3d2a9b8ef --- /dev/null +++ b/tests/stm32/src/common.rs @@ -0,0 +1,44 @@ +#![macro_use] + +pub use defmt::*; +#[allow(unused)] +use embassy_stm32::time::Hertz; +use embassy_stm32::Config; +use {defmt_rtt as _, panic_probe as _}; + +#[cfg(feature = "stm32f103c8")] +teleprobe_meta::target!(b"bluepill-stm32f103c8"); +#[cfg(feature = "stm32g491re")] +teleprobe_meta::target!(b"nucleo-stm32g491re"); +#[cfg(feature = "stm32g071rb")] +teleprobe_meta::target!(b"nucleo-stm32g071rb"); +#[cfg(feature = "stm32f429zi")] +teleprobe_meta::target!(b"nucleo-stm32f429zi"); +#[cfg(feature = "stm32wb55rg")] +teleprobe_meta::target!(b"nucleo-stm32wb55rg"); +#[cfg(feature = "stm32h755zi")] +teleprobe_meta::target!(b"nucleo-stm32h755zi"); +#[cfg(feature = "stm32u585ai")] +teleprobe_meta::target!(b"iot-stm32u585ai"); +#[cfg(feature = "stm32h563zi")] +teleprobe_meta::target!(b"nucleo-stm32h563zi"); +#[cfg(feature = "stm32c031c6")] +teleprobe_meta::target!(b"nucleo-stm32c031c6"); + +pub fn config() -> Config { + #[allow(unused_mut)] + let mut config = Config::default(); + + #[cfg(feature = "stm32h755zi")] + { + config.rcc.sys_ck = Some(Hertz(400_000_000)); + config.rcc.pll1.q_ck = Some(Hertz(100_000_000)); + } + + #[cfg(feature = "stm32u585ai")] + { + config.rcc.mux = embassy_stm32::rcc::ClockSrc::MSI(embassy_stm32::rcc::MSIRange::Range48mhz); + } + + config +} diff --git a/tests/stm32/src/example_common.rs b/tests/stm32/src/example_common.rs deleted file mode 100644 index c47ed75c4..000000000 --- a/tests/stm32/src/example_common.rs +++ /dev/null @@ -1,31 +0,0 @@ -#![macro_use] - -use core::sync::atomic::{AtomicUsize, Ordering}; - -pub use defmt::*; -#[allow(unused)] -use embassy_stm32::time::Hertz; -use embassy_stm32::Config; -use {defmt_rtt as _, panic_probe as _}; - -defmt::timestamp! {"{=u64}", { - static COUNT: AtomicUsize = AtomicUsize::new(0); - // NOTE(no-CAS) `timestamps` runs with interrupts disabled - let n = COUNT.load(Ordering::Relaxed); - COUNT.store(n + 1, Ordering::Relaxed); - n as u64 - } -} - -pub fn config() -> Config { - #[allow(unused_mut)] - let mut config = Config::default(); - - #[cfg(feature = "stm32h755zi")] - { - config.rcc.sys_ck = Some(Hertz(400_000_000)); - config.rcc.pll1.q_ck = Some(Hertz(100_000_000)); - } - - config -} diff --git a/stm32-gen-features/Cargo.toml b/tests/utils/Cargo.toml similarity index 73% rename from stm32-gen-features/Cargo.toml rename to tests/utils/Cargo.toml index f92d127ea..7d66fd586 100644 --- a/stm32-gen-features/Cargo.toml +++ b/tests/utils/Cargo.toml @@ -1,10 +1,10 @@ [package] -name = "gen_features" +name = "test-utils" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -glob = "0.3.0" -yaml-rust = "0.4.5" +rand = "0.8" +serial = "0.4" diff --git a/tests/utils/src/bin/saturate_serial.rs b/tests/utils/src/bin/saturate_serial.rs new file mode 100644 index 000000000..18ca12fb7 --- /dev/null +++ b/tests/utils/src/bin/saturate_serial.rs @@ -0,0 +1,53 @@ +use std::path::Path; +use std::time::Duration; +use std::{env, io, process, thread}; + +use rand::random; +use serial::SerialPort; + +pub fn main() { + if let Some(port_name) = env::args().nth(1) { + let idles = env::args().position(|x| x == "--idles").is_some(); + + println!("Saturating port {:?} with 115200 8N1", port_name); + println!("Idles: {}", idles); + println!("Process ID: {}", process::id()); + let mut port = serial::open(&port_name).unwrap(); + if saturate(&mut port, idles).is_err() { + eprintln!("Unable to saturate port"); + } + } else { + let path = env::args().next().unwrap(); + let basepath = Path::new(&path).with_extension(""); + let basename = basepath.file_name().unwrap(); + eprintln!("USAGE: {} ", basename.to_string_lossy()); + } +} + +fn saturate(port: &mut T, idles: bool) -> io::Result<()> { + port.reconfigure(&|settings| { + settings.set_baud_rate(serial::Baud115200)?; + settings.set_char_size(serial::Bits8); + settings.set_parity(serial::ParityNone); + settings.set_stop_bits(serial::Stop1); + Ok(()) + })?; + + let mut written = 0; + loop { + let len = random::() % 0x1000; + let buf: Vec = (written..written + len).map(|x| x as u8).collect(); + + port.write_all(&buf)?; + + if idles { + let micros = (random::() % 1000) as u64; + println!("Sleeping {}us", micros); + port.flush().unwrap(); + thread::sleep(Duration::from_micros(micros)); + } + + written += len; + println!("Written: {}", written); + } +} diff --git a/xtask/.cargo/config.toml b/xtask/.cargo/config.toml deleted file mode 100644 index 919ec05a4..000000000 --- a/xtask/.cargo/config.toml +++ /dev/null @@ -1,6 +0,0 @@ -[alias] -xtask = "run --package xtask --" -ci = "run --package xtask -- ci" -core = "run --package xtask -- core" -examples = "run --package xtask -- examples" -fmt = "run --package xtask -- fmt" diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml deleted file mode 100644 index d9d6c9b26..000000000 --- a/xtask/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -edition = "2021" -name = "xtask" -version = "0.1.0" - -[dependencies] -anyhow = "1.0.43" -xshell = "0.1.17" -yaml-rust = "0.4.5" -walkdir = "2.3.2" diff --git a/xtask/src/main.rs b/xtask/src/main.rs deleted file mode 100644 index b8b453fd0..000000000 --- a/xtask/src/main.rs +++ /dev/null @@ -1,221 +0,0 @@ -#![allow(dead_code)] -#![deny(unused_must_use)] - -use std::path::{Path, PathBuf}; -use std::{env, format, fs}; - -use walkdir::WalkDir; -use xshell::{cmd, Cmd}; -use yaml_rust::YamlLoader; - -extern crate yaml_rust; - -fn main() -> Result<(), anyhow::Error> { - let args = env::args().skip(1).collect::>(); - let args = args.iter().map(|s| &**s).collect::>(); - - match &args[..] { - ["ci"] => task_ci()?, - ["core"] => task_check(Realm::Core)?, - ["metapac"] => task_metapac_gen()?, - ["examples"] => task_check(Realm::Examples)?, - ["fmt-check"] => task_cargo_fmt_check()?, - ["fmt"] => task_cargo_fmt()?, - _ => { - println!(""); - println!("USAGE: cargo xtask [command]"); - println!(""); - println!("Commands:"); - println!(" ci :: Runs entire CI"); - println!(" core :: Builds the core"); - println!(" metapac :: Builds the metapac"); - println!(" examples :: Builds the examples"); - println!(" fmt-check :: Checks rustfmt"); - println!(" fmt :: Performs rustfmt"); - println!(""); - } - } - Ok(()) -} - -fn task_ci() -> Result<(), anyhow::Error> { - task_check(Realm::Core)?; - task_check(Realm::Examples)?; - task_metapac_gen()?; - task_cargo_fmt_check()?; - Ok(()) -} - -#[derive(Copy, Clone)] -enum Realm { - All, - Core, - Examples, -} - -impl Realm { - fn accepts(&self, package: &str) -> bool { - match self { - Realm::All => true, - Realm::Core => !package.contains("examples"), - Realm::Examples => package.contains("examples"), - } - } -} - -fn task_check(realm: Realm) -> Result<(), anyhow::Error> { - let _e = xshell::pushenv("CI", "true"); - - let matrix_yaml = root_dir().join(".github").join("workflows").join("rust.yml"); - - let matrix = YamlLoader::load_from_str(&*fs::read_to_string(matrix_yaml).unwrap()).unwrap(); - - let matrix = &matrix.get(0).unwrap()["jobs"]["ci"]["strategy"]["matrix"]["include"]; - - let entries = matrix.as_vec().unwrap(); - - for entry in entries { - let package = entry["package"].as_str().unwrap(); - if !realm.accepts(package) { - continue; - } - let target = entry["target"].as_str().unwrap(); - let features = entry["features"].as_str(); - let package_dir = root_dir().join(entry["package"].as_str().unwrap()); - let _p = xshell::pushd(package_dir)?; - banner(&*format!( - "Building {} [target={}] [features={}]", - package, - target, - features.unwrap_or("default-features") - )); - - let root_cargo_dir = root_dir().join(".cargo"); - fs::create_dir_all(root_cargo_dir.clone()).unwrap(); - fs::write( - root_cargo_dir.join("config"), - "[target.\"cfg(all())\"]\nrustflags = [\"-D\", \"warnings\"]", - ) - .unwrap(); - - let mut args = Vec::new(); - args.push("check"); - args.push("--target"); - args.push(target); - - if let Some(features) = features { - args.push("--features"); - args.push(features); - } - - let command = Cmd::new(PathBuf::from("cargo")); - let command = command.args(args); - let result = command.run(); - - fs::remove_file(root_cargo_dir.join("config")).unwrap(); - - result?; - } - - Ok(()) -} - -fn task_metapac_gen() -> Result<(), anyhow::Error> { - banner("Building metapac"); - let _p = xshell::pushd(root_dir().join("stm32-metapac-gen")); - cmd!("cargo run").run()?; - Ok(()) -} - -fn task_cargo_fmt() -> Result<(), anyhow::Error> { - for entry in WalkDir::new(root_dir()) - .follow_links(false) - .into_iter() - .filter_map(|e| e.ok()) - { - let f_name = entry.file_name().to_string_lossy(); - - if f_name.ends_with(".rs") { - if !is_primary_source(entry.path()) { - continue; - } - let mut args = Vec::new(); - args.push("--skip-children"); - args.push("--unstable-features"); - args.push("--edition=2018"); - args.push(&*entry.path().to_str().unwrap()); - let command = Cmd::new("rustfmt"); - command.args(args).run()?; - } - } - - Ok(()) -} - -fn task_cargo_fmt_check() -> Result<(), anyhow::Error> { - let mut actual_result = Ok(()); - for entry in WalkDir::new(root_dir()) - .follow_links(false) - .into_iter() - .filter_map(|e| e.ok()) - { - let f_name = entry.file_name().to_string_lossy(); - - if f_name.ends_with(".rs") { - if !is_primary_source(entry.path()) { - continue; - } - let mut args = Vec::new(); - args.push("--check"); - args.push("--skip-children"); - args.push("--unstable-features"); - args.push("--edition=2018"); - args.push(&*entry.path().to_str().unwrap()); - let command = Cmd::new("rustfmt"); - if let Err(result) = command.args(args).run() { - actual_result = Err(result.into()); - } - } - } - - actual_result -} - -fn root_dir() -> PathBuf { - let mut xtask_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - xtask_dir.pop(); - xtask_dir -} - -fn examples_dir() -> PathBuf { - root_dir().join("examples") -} - -fn is_primary_source(path: &Path) -> bool { - let mut current = path; - - loop { - let current_file_name = current.file_name().unwrap().to_str().unwrap(); - if current_file_name == "target" - || current_file_name == "stm32-metapac-gen" - || current_file_name == "stm32-data" - { - return false; - } - - if let Some(path) = current.parent() { - current = path.into(); - if current == root_dir() { - return true; - } - } else { - return false; - } - } -} - -fn banner(text: &str) { - println!("------------------------------------------------------------------------------------------------------------------------------------------------"); - println!("== {}", text); - println!("------------------------------------------------------------------------------------------------------------------------------------------------"); -}