diff --git a/.gitattributes b/.gitattributes index 4db9edae7..a51376f0d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -15,6 +15,8 @@ *.x text *.yml text +.vscode/*.json linguist-language=JSON-with-Comments + *.raw binary *.bin binary *.png binary @@ -38,4 +40,4 @@ *.pdf binary *.ez binary *.bz2 binary -*.swp binary \ No newline at end of file +*.swp binary diff --git a/.github/ci/doc.sh b/.github/ci/doc.sh index 70833f934..7112d8aaa 100755 --- a/.github/ci/doc.sh +++ b/.github/ci/doc.sh @@ -1,7 +1,7 @@ #!/bin/bash ## on push branch=main -set -euo pipefail +set -euxo pipefail export RUSTUP_HOME=/ci/cache/rustup export CARGO_HOME=/ci/cache/cargo @@ -35,6 +35,7 @@ docserver-builder -i ./embassy-time-driver -o webroot/crates/embassy-time-driver docserver-builder -i ./embassy-time-queue-driver -o webroot/crates/embassy-time-queue-driver/git.zup docserver-builder -i ./embassy-usb -o webroot/crates/embassy-usb/git.zup +docserver-builder -i ./embassy-usb-dfu -o webroot/crates/embassy-usb-dfu/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 diff --git a/README.md b/README.md index 24347a43f..b6f667f75 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. -## <a href="https://embassy.dev/dev/index.html">Documentation</a> - <a href="https://docs.embassy.dev/">API reference</a> - <a href="https://embassy.dev/">Website</a> - <a href="https://matrix.to/#/#embassy-rs:matrix.org">Chat</a> +## <a href="https://embassy.dev/book/dev/index.html">Documentation</a> - <a href="https://docs.embassy.dev/">API reference</a> - <a href="https://embassy.dev/">Website</a> - <a href="https://matrix.to/#/#embassy-rs:matrix.org">Chat</a> ## 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. diff --git a/ci-nightly.sh b/ci-nightly.sh index 1fc9692b5..46b19c5b7 100755 --- a/ci-nightly.sh +++ b/ci-nightly.sh @@ -28,3 +28,6 @@ cargo batch \ --- build --release --manifest-path embassy-executor/Cargo.toml --target riscv32imac-unknown-none-elf --features nightly,arch-riscv32,executor-thread,integrated-timers \ --- build --release --manifest-path examples/nrf52840-rtic/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/nrf52840-rtic \ +cargo build --release --manifest-path embassy-executor/Cargo.toml --target avr-unknown-gnu-atmega328 -Z build-std=core,alloc --features nightly,arch-avr,avr-device/atmega328p +cargo build --release --manifest-path embassy-executor/Cargo.toml --target avr-unknown-gnu-atmega328 -Z build-std=core,alloc --features nightly,arch-avr,integrated-timers,avr-device/atmega328p + diff --git a/ci.sh b/ci.sh index a1d0c8c26..cd82af2f1 100755 --- a/ci.sh +++ b/ci.sh @@ -15,7 +15,8 @@ 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" fi -cargo batch \ +# CI intentionally does not use -eabihf on thumbv7em to minimize dep compile time. +cargo batch \ --- 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 \ @@ -23,6 +24,8 @@ cargo batch \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv6m-none-eabi --features defmt,arch-cortex-m,executor-thread,executor-interrupt,integrated-timers \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,integrated-timers \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,rtos-trace \ + --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,integrated-timers,rtos-trace \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,executor-thread \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,executor-thread,integrated-timers \ --- build --release --manifest-path embassy-executor/Cargo.toml --target thumbv7em-none-eabi --features arch-cortex-m,executor-interrupt \ @@ -47,6 +50,7 @@ cargo batch \ --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip \ --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip,medium-ethernet \ --- build --release --manifest-path embassy-net/Cargo.toml --target thumbv7em-none-eabi --features defmt,tcp,udp,dns,proto-ipv4,proto-ipv6,medium-ip,medium-ethernet,medium-ieee802154 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv6m-none-eabi --features nrf51,gpiote,time,time-driver-rtc1 \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52805,gpiote,time,time-driver-rtc1 \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52810,gpiote,time,time-driver-rtc1 \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52811,gpiote,time,time-driver-rtc1 \ @@ -67,6 +71,9 @@ cargo batch \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840,gpiote,defmt,time \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840,gpiote,defmt,time-driver-rtc1 \ --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv7em-none-eabi --features nrf52840,gpiote,defmt,time,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv6m-none-eabi --features nrf51,defmt,time,time-driver-rtc1 \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv6m-none-eabi --features nrf51,defmt,time \ + --- build --release --manifest-path embassy-nrf/Cargo.toml --target thumbv6m-none-eabi --features nrf51,time \ --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features time-driver,defmt \ --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features time-driver,log \ --- build --release --manifest-path embassy-rp/Cargo.toml --target thumbv6m-none-eabi --features time-driver,intrinsics \ @@ -81,6 +88,16 @@ cargo batch \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,time \ --- 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 \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32f038f6,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32f030c6,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32f058t8,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32f030r8,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32f031k6,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32f030rc,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32f070f6,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32f078vb,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32f042g4,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32f072c8,defmt,exti,time-driver-any,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f401ve,defmt,exti,time-driver-any \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f405zg,defmt,exti,time-driver-any \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f407zg,defmt,exti,time-driver-any \ @@ -107,6 +124,7 @@ cargo batch \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h755zi-cm7,defmt,exti,time-driver-any,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h725re,defmt,exti,time-driver-any,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h7b3ai,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l431cb,defmt,exti,time-driver-any,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l476vg,defmt,exti,time-driver-any,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l422cb,defmt,exti,time-driver-any,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wb15cc,defmt,exti,time-driver-any,time \ @@ -115,20 +133,21 @@ cargo batch \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32l051k8,defmt,exti,time-driver-any,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32l073cz,defmt,exti,time-driver-any,low-power,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32l151cb-a,defmt,exti,time-driver-any,time \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f303c8,defmt,exti,time-driver-any,time \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f398ve,defmt,exti,time-driver-any,time \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f378cc,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f303c8,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f398ve,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f378cc,defmt,exti,time-driver-any,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32g0c1ve,defmt,exti,time-driver-any,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f217zg,defmt,exti,time-driver-any,time \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32l552ze,defmt,exti,time-driver-any,low-power,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32wl54jc-cm0p,defmt,exti,time-driver-any,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wle5jb,defmt,exti,time-driver-any,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32g474pe,defmt,exti,time-driver-any,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f107vc,defmt,exti,time-driver-any,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f103re,defmt,exti,time-driver-any,time \ --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f100c4,defmt,exti,time-driver-any,time \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32h503rb,defmt,exti,time-driver-any,time \ - --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32h562ag,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32h503rb,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32h562ag,defmt,exti,time-driver-any,time \ + --- build --release --manifest-path embassy-stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wb35ce,defmt,exti,time-driver-any,time \ --- 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' \ @@ -148,28 +167,29 @@ cargo batch \ --- build --release --manifest-path examples/nrf52840/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/nrf52840 \ --- build --release --manifest-path examples/nrf5340/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/nrf5340 \ --- build --release --manifest-path examples/nrf9160/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/nrf9160 \ + --- build --release --manifest-path examples/nrf51/Cargo.toml --target thumbv6m-none-eabi --out-dir out/examples/nrf51 \ --- 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 \ --- build --release --manifest-path examples/stm32f2/Cargo.toml --target thumbv7m-none-eabi --out-dir out/examples/stm32f2 \ - --- build --release --manifest-path examples/stm32f3/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/stm32f3 \ - --- build --release --manifest-path examples/stm32f334/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/stm32f334 \ + --- build --release --manifest-path examples/stm32f3/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32f3 \ + --- build --release --manifest-path examples/stm32f334/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32f334 \ --- 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/stm32f7/Cargo.toml --target thumbv7em-none-eabi --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/stm32h5/Cargo.toml --target thumbv8m.main-none-eabihf --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/stm32wb/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32wb \ --- build --release --manifest-path examples/stm32wba/Cargo.toml --target thumbv8m.main-none-eabihf --out-dir out/examples/stm32wba \ - --- 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 --features embassy-nrf/nrf52840,skip-include --out-dir out/examples/boot/nrf52840 \ + --- build --release --manifest-path examples/stm32wl/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/stm32wl \ + --- 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/nrf52840 \ --- 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/nrf9160 \ --- 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 \ @@ -178,13 +198,14 @@ cargo batch \ --- 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/application/stm32wb-dfu/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/boot/stm32wb-dfu \ + --- build --release --manifest-path examples/boot/application/stm32wl/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32wl \ + --- build --release --manifest-path examples/boot/application/stm32wb-dfu/Cargo.toml --target thumbv7em-none-eabi --out-dir out/examples/boot/stm32wb-dfu \ --- 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/boot/bootloader/stm32wb-dfu/Cargo.toml --target thumbv7em-none-eabihf \ + --- build --release --manifest-path examples/boot/bootloader/stm32wb-dfu/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wb55rg \ + --- build --release --manifest-path examples/boot/bootloader/stm32-dual-bank/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32h747xi-cm7 \ --- 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/stm32f103c8 \ --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi --out-dir out/tests/stm32f429zi \ @@ -196,10 +217,10 @@ cargo batch \ --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h753zi --out-dir out/tests/stm32h753zi \ --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32h7a3zi --out-dir out/tests/stm32h7a3zi \ --- 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/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32u5a5zj --out-dir out/tests/stm32u5a5zj \ - --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wba52cg --out-dir out/tests/stm32wba52cg \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32h563zi --out-dir out/tests/stm32h563zi \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32u585ai --out-dir out/tests/stm32u585ai \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32u5a5zj --out-dir out/tests/stm32u5a5zj \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32wba52cg --out-dir out/tests/stm32wba52cg \ --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32l073rz --out-dir out/tests/stm32l073rz \ --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32l152re --out-dir out/tests/stm32l152re \ --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l4a6zg --out-dir out/tests/stm32l4a6zg \ @@ -210,21 +231,18 @@ cargo batch \ --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f303ze --out-dir out/tests/stm32f303ze \ --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32l496zg --out-dir out/tests/stm32l496zg \ --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32wl55jc --out-dir out/tests/stm32wl55jc \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv6m-none-eabi --features stm32f091rc --out-dir out/tests/stm32f091rc \ + --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv8m.main-none-eabihf --features stm32h503rb --out-dir out/tests/stm32h503rb \ --- 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/nrf52840/Cargo.toml --target thumbv7em-none-eabi --out-dir out/tests/nrf52840-dk \ + --- build --release --manifest-path tests/nrf51422/Cargo.toml --target thumbv6m-none-eabi --out-dir out/tests/nrf51-dk \ --- build --release --manifest-path tests/riscv32/Cargo.toml --target riscv32imac-unknown-none-elf \ $BUILD_EXTRA -# failed, a wire must've come loose, will check asap. -rm out/tests/rpi-pico/i2c - rm out/tests/stm32wb55rg/wpan_mac rm out/tests/stm32wb55rg/wpan_ble -# not in CI yet. -rm -rf out/tests/stm32f446re - # unstable, I think it's running out of RAM? rm out/tests/stm32f207zg/eth diff --git a/cyw43-pio/src/lib.rs b/cyw43-pio/src/lib.rs index 5efab10e4..8c217b995 100644 --- a/cyw43-pio/src/lib.rs +++ b/cyw43-pio/src/lib.rs @@ -7,25 +7,24 @@ use core::slice; use cyw43::SpiBusCyw43; use embassy_rp::dma::Channel; -use embassy_rp::gpio::{Drive, Level, Output, Pin, Pull, SlewRate}; +use embassy_rp::gpio::{Drive, Level, Output, Pull, SlewRate}; use embassy_rp::pio::{instr, Common, Config, Direction, Instance, Irq, PioPin, ShiftDirection, StateMachine}; use embassy_rp::{Peripheral, PeripheralRef}; use fixed::FixedU32; use pio_proc::pio_asm; /// SPI comms driven by PIO. -pub struct PioSpi<'d, CS: Pin, PIO: Instance, const SM: usize, DMA> { - cs: Output<'d, CS>, +pub struct PioSpi<'d, PIO: Instance, const SM: usize, DMA> { + cs: Output<'d>, 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> +impl<'d, PIO, const SM: usize, DMA> PioSpi<'d, PIO, SM, DMA> where DMA: Channel, - CS: Pin, PIO: Instance, { /// Create a new instance of PioSpi. @@ -33,7 +32,7 @@ where common: &mut Common<'d, PIO>, mut sm: StateMachine<'d, PIO, SM>, irq: Irq<'d, PIO, 0>, - cs: Output<'d, CS>, + cs: Output<'d>, dio: DIO, clk: CLK, dma: impl Peripheral<P = DMA> + 'd, @@ -206,9 +205,8 @@ where } } -impl<'d, CS, PIO, const SM: usize, DMA> SpiBusCyw43 for PioSpi<'d, CS, PIO, SM, DMA> +impl<'d, PIO, const SM: usize, DMA> SpiBusCyw43 for PioSpi<'d, PIO, SM, DMA> where - CS: Pin, PIO: Instance, DMA: Channel, { diff --git a/cyw43/Cargo.toml b/cyw43/Cargo.toml index c857f7378..f279739e4 100644 --- a/cyw43/Cargo.toml +++ b/cyw43/Cargo.toml @@ -10,7 +10,7 @@ repository = "https://github.com/embassy-rs/embassy" documentation = "https://docs.embassy.dev/cyw43" [features] -defmt = ["dep:defmt"] +defmt = ["dep:defmt", "heapless/defmt-03", "embassy-time/defmt"] log = ["dep:log"] # Fetch console logs from the WiFi firmware and forward them to `log` or `defmt`. @@ -32,6 +32,8 @@ futures = { version = "0.3.17", default-features = false, features = ["async-awa embedded-hal-1 = { package = "embedded-hal", version = "1.0" } num_enum = { version = "0.5.7", default-features = false } +heapless = "0.8.0" + [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/" diff --git a/cyw43/src/control.rs b/cyw43/src/control.rs index 311fcb08c..135b8c245 100644 --- a/cyw43/src/control.rs +++ b/cyw43/src/control.rs @@ -3,7 +3,7 @@ use core::iter::zip; use embassy_net_driver_channel as ch; use embassy_net_driver_channel::driver::{HardwareAddress, LinkState}; -use embassy_time::Timer; +use embassy_time::{Duration, Timer}; use crate::consts::*; use crate::events::{Event, EventSubscriber, Events}; @@ -35,6 +35,43 @@ pub struct Control<'a> { ioctl_state: &'a IoctlState, } +#[derive(Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ScanType { + Active, + Passive, +} + +#[derive(Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ScanOptions { + pub ssid: Option<heapless::String<32>>, + /// If set to `None`, all APs will be returned. If set to `Some`, only APs + /// with the specified BSSID will be returned. + pub bssid: Option<[u8; 6]>, + /// Number of probes to send on each channel. + pub nprobes: Option<u16>, + /// Time to spend waiting on the home channel. + pub home_time: Option<Duration>, + /// Scan type: active or passive. + pub scan_type: ScanType, + /// Period of time to wait on each channel when passive scanning. + pub dwell_time: Option<Duration>, +} + +impl Default for ScanOptions { + fn default() -> Self { + Self { + ssid: None, + bssid: None, + nprobes: None, + home_time: None, + scan_type: ScanType::Passive, + dwell_time: None, + } + } +} + impl<'a> Control<'a> { pub(crate) fn new(state_ch: ch::StateRunner<'a>, event_sub: &'a Events, ioctl_state: &'a IoctlState) -> Self { Self { @@ -471,22 +508,54 @@ impl<'a> Control<'a> { /// # 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<'_> { + pub async fn scan(&mut self, scan_opts: ScanOptions) -> Scanner<'_> { + const SCANTYPE_ACTIVE: u8 = 0; const SCANTYPE_PASSIVE: u8 = 1; + let dwell_time = match scan_opts.dwell_time { + None => !0, + Some(t) => { + let mut t = t.as_millis() as u32; + if t == !0 { + t = !0 - 1; + } + t + } + }; + + let mut active_time = !0; + let mut passive_time = !0; + let scan_type = match scan_opts.scan_type { + ScanType::Active => { + active_time = dwell_time; + SCANTYPE_ACTIVE + } + ScanType::Passive => { + passive_time = dwell_time; + SCANTYPE_PASSIVE + } + }; + let scan_params = ScanParams { version: 1, action: 1, sync_id: 1, - ssid_len: 0, - ssid: [0; 32], - bssid: [0xff; 6], + ssid_len: scan_opts.ssid.as_ref().map(|e| e.as_bytes().len() as u32).unwrap_or(0), + ssid: scan_opts + .ssid + .map(|e| { + let mut ssid = [0; 32]; + ssid[..e.as_bytes().len()].copy_from_slice(e.as_bytes()); + ssid + }) + .unwrap_or([0; 32]), + bssid: scan_opts.bssid.unwrap_or([0xff; 6]), bss_type: 2, - scan_type: SCANTYPE_PASSIVE, - nprobes: !0, - active_time: !0, - passive_time: !0, - home_time: !0, + scan_type, + nprobes: scan_opts.nprobes.unwrap_or(!0).into(), + active_time, + passive_time, + home_time: scan_opts.home_time.map(|e| e.as_millis() as u32).unwrap_or(!0), channel_num: 0, channel_list: [0; 1], }; diff --git a/cyw43/src/events.rs b/cyw43/src/events.rs index a94c49a0c..44bfa98e9 100644 --- a/cyw43/src/events.rs +++ b/cyw43/src/events.rs @@ -311,13 +311,13 @@ pub struct Status { pub status: u32, } -#[derive(Clone, Copy)] +#[derive(Copy, Clone)] pub enum Payload { None, BssInfo(BssInfo), } -#[derive(Clone, Copy)] +#[derive(Copy, Clone)] pub struct Message { pub header: Status, diff --git a/cyw43/src/runner.rs b/cyw43/src/runner.rs index b2a9e3e80..c72cf0def 100644 --- a/cyw43/src/runner.rs +++ b/cyw43/src/runner.rs @@ -242,13 +242,12 @@ where cmd, iface, }) => { - self.send_ioctl(kind, cmd, iface, unsafe { &*iobuf }).await; + self.send_ioctl(kind, cmd, iface, unsafe { &*iobuf }, &mut buf).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. @@ -480,9 +479,8 @@ where 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); + async fn send_ioctl(&mut self, kind: IoctlType, cmd: u32, iface: u32, data: &[u8], buf: &mut [u32; 512]) { + let buf8 = slice8_mut(buf); let total_len = SdpcmHeader::SIZE + CdcHeader::SIZE + data.len(); diff --git a/cyw43/src/structs.rs b/cyw43/src/structs.rs index 5ea62d95b..ae7ef6038 100644 --- a/cyw43/src/structs.rs +++ b/cyw43/src/structs.rs @@ -491,6 +491,44 @@ pub struct BssInfo { pub ssid_len: u8, /// SSID. pub ssid: [u8; 32], + reserved1: [u8; 1], + /// Number of rates in the rates field. + pub rateset_count: u32, + /// Rates in 500kpbs units. + pub rates: [u8; 16], + /// Channel specification. + pub chanspec: u16, + /// Announcement traffic indication message. + pub atim_window: u16, + /// Delivery traffic indication message. + pub dtim_period: u8, + reserved2: [u8; 1], + /// Receive signal strength (in dbM). + pub rssi: i16, + /// Received noise (in dbM). + pub phy_noise: i8, + /// 802.11n capability. + pub n_cap: u8, + reserved3: [u8; 2], + /// 802.11n BSS capabilities. + pub nbss_cap: u32, + /// 802.11n control channel number. + pub ctl_ch: u8, + reserved4: [u8; 3], + reserved32: [u32; 1], + /// Flags. + pub flags: u8, + /// VHT capability. + pub vht_cap: u8, + reserved5: [u8; 2], + /// 802.11n BSS required MCS. + pub basic_mcs: [u8; 16], + /// Information Elements (IE) offset. + pub ie_offset: u16, + /// Length of Information Elements (IE) in bytes. + pub ie_length: u32, + /// Average signal-to-noise (SNR) ratio during frame reception. + pub snr: i16, // there will be more stuff here } impl_bytes!(BssInfo); diff --git a/docs/modules/ROOT/examples/basic/src/main.rs b/docs/modules/ROOT/examples/basic/src/main.rs index 2a4ee5968..4412712c8 100644 --- a/docs/modules/ROOT/examples/basic/src/main.rs +++ b/docs/modules/ROOT/examples/basic/src/main.rs @@ -4,12 +4,11 @@ use defmt::*; use embassy_executor::Spawner; use embassy_nrf::gpio::{Level, Output, OutputDrive}; -use embassy_nrf::peripherals::P0_13; use embassy_time::{Duration, Timer}; use {defmt_rtt as _, panic_probe as _}; // global logger #[embassy_executor::task] -async fn blinker(mut led: Output<'static, P0_13>, interval: Duration) { +async fn blinker(mut led: Output<'static>, interval: Duration) { loop { led.set_high(); Timer::after(interval).await; diff --git a/docs/modules/ROOT/examples/layer-by-layer/blinky-async/src/main.rs b/docs/modules/ROOT/examples/layer-by-layer/blinky-async/src/main.rs index e6753be28..004602816 100644 --- a/docs/modules/ROOT/examples/layer-by-layer/blinky-async/src/main.rs +++ b/docs/modules/ROOT/examples/layer-by-layer/blinky-async/src/main.rs @@ -3,14 +3,14 @@ use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; -use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; +use embassy_stm32::gpio::{Level, Output, Pull, Speed}; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); let mut led = Output::new(p.PB14, Level::Low, Speed::VeryHigh); - let mut button = ExtiInput::new(Input::new(p.PC13, Pull::Up), p.EXTI13); + let mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::Up); loop { button.wait_for_any_edge().await; 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 aecba0755..743c9d99b 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 @@ -6,13 +6,12 @@ use core::cell::RefCell; use cortex_m::interrupt::Mutex; use cortex_m::peripheral::NVIC; use cortex_m_rt::entry; -use embassy_stm32::gpio::{Input, Level, Output, Pin, Pull, Speed}; -use embassy_stm32::peripherals::{PB14, PC13}; +use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; use embassy_stm32::{interrupt, pac}; use {defmt_rtt as _, panic_probe as _}; -static BUTTON: Mutex<RefCell<Option<Input<'static, PC13>>>> = Mutex::new(RefCell::new(None)); -static LED: Mutex<RefCell<Option<Output<'static, PB14>>>> = Mutex::new(RefCell::new(None)); +static BUTTON: Mutex<RefCell<Option<Input<'static>>>> = Mutex::new(RefCell::new(None)); +static LED: Mutex<RefCell<Option<Output<'static>>>> = Mutex::new(RefCell::new(None)); #[entry] fn main() -> ! { @@ -62,14 +61,14 @@ fn EXTI15_10() { const PORT: u8 = 2; const PIN: usize = 13; -fn check_interrupt<P: Pin>(_pin: &mut Input<'static, P>) -> bool { +fn check_interrupt(_pin: &mut Input<'static>) -> bool { let exti = pac::EXTI; let pin = PIN; let lines = exti.pr(0).read(); lines.line(pin) } -fn clear_interrupt<P: Pin>(_pin: &mut Input<'static, P>) { +fn clear_interrupt(_pin: &mut Input<'static>) { let exti = pac::EXTI; let pin = PIN; let mut lines = exti.pr(0).read(); @@ -77,7 +76,7 @@ fn clear_interrupt<P: Pin>(_pin: &mut Input<'static, P>) { exti.pr(0).write_value(lines); } -fn enable_interrupt<P: Pin>(_pin: &mut Input<'static, P>) { +fn enable_interrupt(_pin: &mut Input<'static>) { cortex_m::interrupt::free(|_| { let rcc = pac::RCC; rcc.apb2enr().modify(|w| w.set_syscfgen(true)); diff --git a/docs/modules/ROOT/pages/basic_application.adoc b/docs/modules/ROOT/pages/basic_application.adoc index 95792d5a0..d5aad806d 100644 --- a/docs/modules/ROOT/pages/basic_application.adoc +++ b/docs/modules/ROOT/pages/basic_application.adoc @@ -17,22 +17,13 @@ The first thing you’ll notice are two attributes at the top of the file. These include::example$basic/src/main.rs[lines="1..2"] ---- -=== Rust Nightly - -The next declaration is a Rust Unstable feature, which means that Embassy requires Rust Nightly: - -[source,rust] ----- -include::example$basic/src/main.rs[lines="3"] ----- - === Dealing with errors Then, what follows are some declarations on how to deal with panics and faults. During development, a good practice is to rely on `defmt-rtt` and `panic-probe` to print diagnostics to the terminal: [source,rust] ---- -include::example$basic/src/main.rs[lines="10"] +include::example$basic/src/main.rs[lines="8"] ---- === Task declaration @@ -41,7 +32,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="12..20"] +include::example$basic/src/main.rs[lines="10..18"] ---- 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. @@ -56,7 +47,7 @@ We then initialize the HAL with a default config, which gives us a `Peripherals` [source,rust] ---- -include::example$basic/src/main.rs[lines="22..-1"] +include::example$basic/src/main.rs[lines="20..-1"] ---- 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_executor::main]` macro. The macro does the following: diff --git a/docs/modules/ROOT/pages/best_practices.adoc b/docs/modules/ROOT/pages/best_practices.adoc index 1e02f9ba9..bfcedec06 100644 --- a/docs/modules/ROOT/pages/best_practices.adoc +++ b/docs/modules/ROOT/pages/best_practices.adoc @@ -3,8 +3,10 @@ Over time, a couple of best practices have emerged. The following list should serve as a guideline for developers writing embedded software in _Rust_, especially in the context of the _Embassy_ framework. == Passing Buffers by Reference -It may be tempting to pass arrays or wrappers, like link:https://docs.rs/heapless/latest/heapless/[`heapless::Vec`], to a function or return one just like you would with a `std::Vec`. However, in most embedded applications you don't want to spend ressources on an allocator and end up placing buffers on the stack. -This, however, can easily blow up your stack if you are not careful. +It may be tempting to pass arrays or wrappers, like link:https://docs.rs/heapless/latest/heapless/[`heapless::Vec`], +to a function or return one just like you would with a `std::Vec`. However, in most embedded applications you don't +want to spend resources on an allocator and end up placing buffers on the stack. This, however, can easily blow up +your stack if you are not careful. Consider the following example: [,rust] diff --git a/docs/modules/ROOT/pages/embassy_in_the_wild.adoc b/docs/modules/ROOT/pages/embassy_in_the_wild.adoc index a1c31bfc7..85ad7f4a2 100644 --- a/docs/modules/ROOT/pages/embassy_in_the_wild.adoc +++ b/docs/modules/ROOT/pages/embassy_in_the_wild.adoc @@ -7,3 +7,5 @@ Here are known examples of real-world projects which make use of Embassy. Feel f * link:https://github.com/card-io-ecg/card-io-fw[Card/IO firmware] - firmware for an open source ECG device ** Targets the ESP32-S3 or ESP32-C6 MCU * The link:https://github.com/lora-rs/lora-rs[lora-rs] project includes link:https://github.com/lora-rs/lora-rs/tree/main/examples/stm32l0/src/bin[various standalone examples] for NRF52840, RP2040, STM32L0 and STM32WL +** link:https://github.com/matoushybl/air-force-one[Air force one: A simple air quality monitoring system] +*** Targets nRF52 and uses nrf-softdevice diff --git a/docs/modules/ROOT/pages/faq.adoc b/docs/modules/ROOT/pages/faq.adoc index 147f119b0..6b5e6d009 100644 --- a/docs/modules/ROOT/pages/faq.adoc +++ b/docs/modules/ROOT/pages/faq.adoc @@ -29,11 +29,10 @@ If you see an error like this: You are likely missing some features of the `embassy-executor` crate. -For Cortex-M targets, consider making sure that ALL of the following features are active in your `Cargo.toml` for the `embassy-executor` crate: +For Cortex-M targets, check whether ALL of the following features are enabled in your `Cargo.toml` for the `embassy-executor` crate: * `arch-cortex-m` * `executor-thread` -* `nightly` For ESP32, consider using the executors and `#[main]` macro provided by your appropriate link:https://crates.io/crates/esp-hal-common[HAL crate]. @@ -44,11 +43,12 @@ The first step to managing your binary size is to set up your link:https://doc.r [source,toml] ---- [profile.release] -debug = false lto = true opt-level = "s" incremental = false codegen-units = 1 +# note: debug = true is okay - debuginfo isn't flashed to the device! +debug = true ---- All of these flags are elaborated on in the Rust Book page linked above. @@ -118,21 +118,31 @@ features = [ ] ---- +If you are in the early project setup phase and not using anything from the HAL, make sure the HAL is explicitly used to prevent the linker removing it as dead code by adding this line to your source: + +[source,rust] +---- +use embassy_stm32 as _; +---- + == Error: `Only one package in the dependency graph may specify the same links value.` You have multiple versions of the same crate in your dependency tree. This means that some of your embassy crates are coming from crates.io, and some from git, each of them pulling in a different set of dependencies. -To resolve this issue, make sure to only use a single source for all your embassy crates! To do this, -you should patch your dependencies to use git sources using `[patch.crates.io]` and maybe `[patch.'https://github.com/embassy-rs/embassy.git']`. +To resolve this issue, make sure to only use a single source for all your embassy crates! +To do this, you should patch your dependencies to use git sources using `[patch.crates.io]` +and maybe `[patch.'https://github.com/embassy-rs/embassy.git']`. Example: [source,toml] ---- [patch.crates-io] -embassy-time = { git = "https://github.com/embassy-rs/embassy.git", rev = "e5fdd35" } +embassy-time-queue-driver = { git = "https://github.com/embassy-rs/embassy.git", rev = "e5fdd35" } +embassy-time-driver = { git = "https://github.com/embassy-rs/embassy.git", rev = "e5fdd35" } +# embassy-time = { git = "https://github.com/embassy-rs/embassy.git", rev = "e5fdd35" } ---- Note that the git revision should match any other embassy patches or git dependencies that you are using! @@ -180,3 +190,21 @@ Check out link:https://docs.embassy.dev/embassy-executor/git/cortex-m/index.html == Can I use manual ISRs alongside Embassy? Yes! This can be useful if you need to respond to an event as fast as possible, and the latency caused by the usual “ISR, wake, return from ISR, context switch to woken task” flow is too much for your application. Simply define a `#[interrupt] fn INTERRUPT_NAME() {}` handler as you would link:https://docs.rust-embedded.org/book/start/interrupts.html[in any other embedded rust project]. + +== How can I measure resource usage (CPU, RAM, etc.)? + +=== For CPU Usage: + +There are a couple techniques that have been documented, generally you want to measure how long you are spending in the idle or low priority loop. + +We need to document specifically how to do this in embassy, but link:https://blog.japaric.io/cpu-monitor/[this older post] describes the general process. + +If you end up doing this, please update this section with more specific examples! + +=== For Static Memory Usage + +Tools like `cargo size` and `cargo nm` can tell you the size of any globals or other static usage. Specifically you will want to see the size of the `.data` and `.bss` sections, which together make up the total global/static memory usage. + +=== For Max Stack Usage + +Check out link:https://github.com/Dirbaio/cargo-call-stack/[`cargo-call-stack`] for statically calculating worst-case stack usage. There are some caveats and inaccuracies possible with this, but this is a good way to get the general idea. See link:https://github.com/dirbaio/cargo-call-stack#known-limitations[the README] for more details. diff --git a/docs/modules/ROOT/pages/getting_started.adoc b/docs/modules/ROOT/pages/getting_started.adoc index ab819ac2a..73cb5530d 100644 --- a/docs/modules/ROOT/pages/getting_started.adoc +++ b/docs/modules/ROOT/pages/getting_started.adoc @@ -3,7 +3,7 @@ So you want to try Embassy, great! To get started, there are a few tools you need to install: * link:https://rustup.rs/[rustup] - the Rust toolchain is needed to compile Rust code. -* link:https://crates.io/crates/probe-rs[probe-rs] - to flash the firmware on your device. If you already have other tools like `OpenOCD` setup, you can use that as well. +* link:https://probe.rs/[probe-rs] - to flash the firmware on your device. If you already have other tools like `OpenOCD` setup, you can use that as well. If you don't have any supported board, don't worry: you can also run embassy on your PC using the `std` examples. @@ -82,19 +82,19 @@ If everything worked correctly, you should see a blinking LED on your board, and └─ blinky::__embassy_main::task::{generator#0} @ src/bin/blinky.rs:27 ---- -NOTE: How does the `cargo run` command know how to connect to our board and program it? In each `examples` folder, there’s a `.cargo/config.toml` file which tells cargo to use link:https://probe.rs/[probe-rs] as the runner for ARM binaries in that folder. probe-rs handles communication with the debug probe and MCU. In order for this to work, probe-rs needs to know which chip it’s programming, so you’ll have to edit this file if you want to run examples on other chips. +NOTE: How does the `+cargo run+` command know how to connect to our board and program it? In each `examples` folder, there’s a `.cargo/config.toml` file which tells cargo to use link:https://probe.rs/[probe-rs] as the runner for ARM binaries in that folder. probe-rs handles communication with the debug probe and MCU. In order for this to work, probe-rs needs to know which chip it’s programming, so you’ll have to edit this file if you want to run examples on other chips. === It didn’t work! -If you hare having issues when running `cargo run --release`, please check the following: +If you hare having issues when running `+cargo run --release+`, please check the following: -* You are specifying the correct `--chip on the command line``, OR -* You have set `.cargo/config.toml`'s run line to the correct chip, AND -* You have changed `examples/Cargo.toml`'s HAL (e.g. embassy-stm32) dependency's feature to use the correct chip (replace the existing stm32xxxx feature) +* You are specifying the correct `+--chip+` on the command line, OR +* You have set `+.cargo/config.toml+`’s run line to the correct chip, AND +* You have changed `+examples/Cargo.toml+`’s HAL (e.g. embassy-stm32) dependency's feature to use the correct chip (replace the existing stm32xxxx feature) At this point the project should run. If you do not see a blinky LED for blinky, for example, be sure to check the code is toggling your board's LED pin. -If you are trying to run an example with `cargo run --release` and you see the following output: +If you are trying to run an example with `+cargo run --release+` and you see the following output: [source] ---- 0.000000 INFO Hello World! @@ -115,6 +115,22 @@ To get rid of the frame-index error add the following to your `Cargo.toml`: debug = 2 ---- +If you’re getting an extremely long error message containing something like the following: + +[source] +---- +error[E0463]: can't find crate for `std` + | + = note: the `thumbv6m-none-eabi` target may not support the standard library + = note: `std` is required by `stable_deref_trait` because it does not declare `#![no_std]` +---- + +Make sure that you didn’t accidentally run `+cargo add probe-rs+` (which adds it as a dependency) instead of link:https://probe.rs/docs/getting-started/installation/[correctly installing probe-rs]. + +If you’re using a raspberry pi pico-w, make sure you’re running `+cargo run --bin wifi_blinky --release+` rather than the regular blinky. The pico-w’s on-board LED is connected to the WiFi chip, which needs to be initialized before the LED can be blinked. + +If you’re using an rp2040 debug probe (e.g. the pico probe) and are having issues after running `probe-rs info`, unplug and reconnect the probe, letting it power cycle. Running `probe-rs info` is link:https://github.com/probe-rs/probe-rs/issues/1849[known to put the pico probe into an unusable state]. + If you’re still having problems, check the link:https://embassy.dev/book/dev/faq.html[FAQ], or ask for help in the link:https://matrix.to/#/#embassy-rs:matrix.org[Embassy Chat Room]. == What's next? @@ -124,3 +140,4 @@ Congratulations, you have your first Embassy application running! Here are some * Read more about the xref:runtime.adoc[executor]. * Read more about the xref:hal.adoc[HAL]. * Start xref:basic_application.adoc[writing your application]. +* Learn how to xref:new_project.adoc[start a new embassy project by adapting an example]. diff --git a/docs/modules/ROOT/pages/new_project.adoc b/docs/modules/ROOT/pages/new_project.adoc index ce139ed8d..320966bb6 100644 --- a/docs/modules/ROOT/pages/new_project.adoc +++ b/docs/modules/ROOT/pages/new_project.adoc @@ -1,6 +1,17 @@ = Starting a new Embassy project -Once you’ve successfully xref:getting_started.adoc[run some example projects], the next step is to make a standalone Embassy project. The easiest way to do this is to adapt an example for a similar chip to the one you’re targeting. +Once you’ve successfully xref:getting_started.adoc[run some example projects], the next step is to make a standalone Embassy project. + +There are some tools for generating Embassy projects: (WIP) + +==== CLI +- link:https://github.com/adinack/cargo-embassy[cargo-embassy] (STM32 and NRF) + +==== cargo-generate +- link:https://github.com/lulf/embassy-template[embassy-template] (STM32, NRF, and RP) +- link:https://github.com/bentwire/embassy-rp2040-template[embassy-rp2040-template] (RP) + +But if you want to start from scratch: As an example, let’s create a new embassy project from scratch for a STM32G474. The same instructions are applicable for any supported chip with some minor changes. @@ -166,13 +177,13 @@ should result in a blinking LED (if there’s one attached to the pin in `src/ma Erasing sectors ✔ [00:00:00] [#########################################################] 18.00 KiB/18.00 KiB @ 54.09 KiB/s (eta 0s ) Programming pages ✔ [00:00:00] [#########################################################] 17.00 KiB/17.00 KiB @ 35.91 KiB/s (eta 0s ) Finished in 0.817s 0.000000 TRACE BDCR configured: 00008200 -└─ embassy_stm32::rcc::bd::{impl#3}::init::{closure#4} @ /home/you/.cargo/git/checkouts/embassy-9312dcb0ed774b29/7703f47/embassy-stm32/src/fmt.rs:117 +└─ embassy_stm32::rcc::bd::{impl#3}::init::{closure#4} @ /home/you/.cargo/git/checkouts/embassy-9312dcb0ed774b29/7703f47/embassy-stm32/src/fmt.rs:117 0.000000 DEBUG rcc: Clocks { sys: Hertz(16000000), pclk1: Hertz(16000000), pclk1_tim: Hertz(16000000), pclk2: Hertz(16000000), pclk2_tim: Hertz(16000000), hclk1: Hertz(16000000), hclk2: Hertz(16000000), pll1_p: None, adc: None, adc34: None, rtc: Some(Hertz(32000)) } -└─ embassy_stm32::rcc::set_freqs @ /home/you/.cargo/git/checkouts/embassy-9312dcb0ed774b29/7703f47/embassy-stm32/src/fmt.rs:130 +└─ embassy_stm32::rcc::set_freqs @ /home/you/.cargo/git/checkouts/embassy-9312dcb0ed774b29/7703f47/embassy-stm32/src/fmt.rs:130 0.000000 INFO Hello World! -└─ embassy_stm32g474::____embassy_main_task::{async_fn#0} @ src/main.rs:14 +└─ embassy_stm32g474::____embassy_main_task::{async_fn#0} @ src/main.rs:14 0.000091 INFO high -└─ embassy_stm32g474::____embassy_main_task::{async_fn#0} @ src/main.rs:19 +└─ embassy_stm32g474::____embassy_main_task::{async_fn#0} @ src/main.rs:19 0.300201 INFO low -└─ embassy_stm32g474::____embassy_main_task::{async_fn#0} @ src/main.rs:23 ----- \ No newline at end of file +└─ embassy_stm32g474::____embassy_main_task::{async_fn#0} @ src/main.rs:23 +---- diff --git a/docs/modules/ROOT/pages/project_structure.adoc b/docs/modules/ROOT/pages/project_structure.adoc index 61ffd05a6..2adfcc1df 100644 --- a/docs/modules/ROOT/pages/project_structure.adoc +++ b/docs/modules/ROOT/pages/project_structure.adoc @@ -38,13 +38,18 @@ DEFMT_LOG = "trace" # <- can change to info, warn, or error == build.rs -This is the build script for your project. It links defmt (what is defmt?) and the `memory.x` file if needed. This file is pretty specific for each chipset, just copy and paste from the corresponding link:https://github.com/embassy-rs/embassy/tree/main/examples[example]. +This is the build script for your project. It links defmt (what is link:https://defmt.ferrous-systems.com[defmt]?) and the `memory.x` file if needed. This file is pretty specific for each chipset, just copy and paste from the corresponding link:https://github.com/embassy-rs/embassy/tree/main/examples[example]. == Cargo.toml This is your manifest file, where you can configure all of the embassy components to use the features you need. -TODO: someone should exhaustively describe every feature for every component! +==== Features +===== Time +- tick-hz-x: Configures the tick rate of `embassy-time`. Higher tick rate means higher precision, and higher CPU wakes. +- defmt-timestamp-uptime: defmt log entries will display the uptime in seconds. + +...more to come == memory.x diff --git a/embassy-boot-nrf/src/lib.rs b/embassy-boot-nrf/src/lib.rs index 5b20a93c6..d53e78895 100644 --- a/embassy-boot-nrf/src/lib.rs +++ b/embassy-boot-nrf/src/lib.rs @@ -4,8 +4,8 @@ mod fmt; pub use embassy_boot::{ - AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareState, FirmwareUpdater, - FirmwareUpdaterConfig, + AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootError, BootLoaderConfig, FirmwareState, + FirmwareUpdater, FirmwareUpdaterConfig, }; use embassy_nrf::nvmc::PAGE_SIZE; use embassy_nrf::peripherals::WDT; @@ -16,14 +16,21 @@ use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; pub struct BootLoader<const BUFFER_SIZE: usize = PAGE_SIZE>; impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> { - /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware. + /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware pub fn prepare<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash>( config: BootLoaderConfig<ACTIVE, DFU, STATE>, ) -> Self { + Self::try_prepare::<ACTIVE, DFU, STATE>(config).expect("Boot prepare error") + } + + /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware + pub fn try_prepare<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash>( + config: BootLoaderConfig<ACTIVE, DFU, STATE>, + ) -> Result<Self, BootError> { let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]); let mut boot = embassy_boot::BootLoader::new(config); - boot.prepare_boot(&mut aligned_buf.0).expect("Boot prepare error"); - Self + let _state = boot.prepare_boot(aligned_buf.as_mut())?; + Ok(Self) } /// Boots the application without softdevice mechanisms. diff --git a/embassy-boot-rp/src/lib.rs b/embassy-boot-rp/src/lib.rs index 07a5b3f4d..d0a393bed 100644 --- a/embassy-boot-rp/src/lib.rs +++ b/embassy-boot-rp/src/lib.rs @@ -4,8 +4,8 @@ mod fmt; pub use embassy_boot::{ - AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareState, FirmwareUpdater, - FirmwareUpdaterConfig, State, + AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootError, BootLoaderConfig, FirmwareState, + FirmwareUpdater, FirmwareUpdaterConfig, State, }; use embassy_rp::flash::{Blocking, Flash, ERASE_SIZE}; use embassy_rp::peripherals::{FLASH, WATCHDOG}; @@ -21,10 +21,17 @@ impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> { pub fn prepare<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash>( config: BootLoaderConfig<ACTIVE, DFU, STATE>, ) -> Self { + Self::try_prepare::<ACTIVE, DFU, STATE>(config).expect("Boot prepare error") + } + + /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware + pub fn try_prepare<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash>( + config: BootLoaderConfig<ACTIVE, DFU, STATE>, + ) -> Result<Self, BootError> { let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]); let mut boot = embassy_boot::BootLoader::new(config); - boot.prepare_boot(aligned_buf.as_mut()).expect("Boot prepare error"); - Self + let _state = boot.prepare_boot(aligned_buf.as_mut())?; + Ok(Self) } /// Boots the application. diff --git a/embassy-boot-stm32/src/lib.rs b/embassy-boot-stm32/src/lib.rs index 4b4091ac9..708441835 100644 --- a/embassy-boot-stm32/src/lib.rs +++ b/embassy-boot-stm32/src/lib.rs @@ -4,8 +4,8 @@ mod fmt; pub use embassy_boot::{ - AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareState, FirmwareUpdater, - FirmwareUpdaterConfig, State, + AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootError, BootLoaderConfig, FirmwareState, + FirmwareUpdater, FirmwareUpdaterConfig, State, }; use embedded_storage::nor_flash::NorFlash; @@ -20,10 +20,17 @@ impl BootLoader { pub fn prepare<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize>( config: BootLoaderConfig<ACTIVE, DFU, STATE>, ) -> Self { + Self::try_prepare::<ACTIVE, DFU, STATE, BUFFER_SIZE>(config).expect("Boot prepare error") + } + + /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware + pub fn try_prepare<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize>( + config: BootLoaderConfig<ACTIVE, DFU, STATE>, + ) -> Result<Self, BootError> { let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]); let mut boot = embassy_boot::BootLoader::new(config); - let state = boot.prepare_boot(aligned_buf.as_mut()).expect("Boot prepare error"); - Self { state } + let state = boot.prepare_boot(aligned_buf.as_mut())?; + Ok(Self { state }) } /// Boots the application. diff --git a/embassy-boot/src/boot_loader.rs b/embassy-boot/src/boot_loader.rs index e568001bc..ca1a1b10c 100644 --- a/embassy-boot/src/boot_loader.rs +++ b/embassy-boot/src/boot_loader.rs @@ -49,16 +49,51 @@ pub struct BootLoaderConfig<ACTIVE, DFU, STATE> { pub state: STATE, } -impl<'a, FLASH: NorFlash> +impl<'a, ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> BootLoaderConfig< - BlockingPartition<'a, NoopRawMutex, FLASH>, - BlockingPartition<'a, NoopRawMutex, FLASH>, - BlockingPartition<'a, NoopRawMutex, FLASH>, + BlockingPartition<'a, NoopRawMutex, ACTIVE>, + BlockingPartition<'a, NoopRawMutex, DFU>, + BlockingPartition<'a, NoopRawMutex, STATE>, > { - /// Create a bootloader config from the flash and address symbols defined in the linkerfile + /// Constructs a `BootLoaderConfig` instance from flash memory and address symbols defined in the linker file. + /// + /// This method initializes `BlockingPartition` instances for the active, DFU (Device Firmware Update), + /// and state partitions, leveraging start and end addresses specified by the linker. These partitions + /// are critical for managing firmware updates, application state, and boot operations within the bootloader. + /// + /// # Parameters + /// - `active_flash`: A reference to a mutex-protected `RefCell` for the active partition's flash interface. + /// - `dfu_flash`: A reference to a mutex-protected `RefCell` for the DFU partition's flash interface. + /// - `state_flash`: A reference to a mutex-protected `RefCell` for the state partition's flash interface. + /// + /// # Safety + /// The method contains `unsafe` blocks for dereferencing raw pointers that represent the start and end addresses + /// of the bootloader's partitions in flash memory. It is crucial that these addresses are accurately defined + /// in the memory.x file to prevent undefined behavior. + /// + /// The caller must ensure that the memory regions defined by these symbols are valid and that the flash memory + /// interfaces provided are compatible with these regions. + /// + /// # Returns + /// A `BootLoaderConfig` instance with `BlockingPartition` instances for the active, DFU, and state partitions. + /// + /// # Example + /// ```ignore + /// // Assume `active_flash`, `dfu_flash`, and `state_flash` all share the same flash memory interface. + /// 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, &flash, &flash); + /// // `config` can now be used to create a `BootLoader` instance for managing boot operations. + /// ``` + /// Working examples can be found in the bootloader examples folder. // #[cfg(target_os = "none")] - pub fn from_linkerfile_blocking(flash: &'a Mutex<NoopRawMutex, RefCell<FLASH>>) -> Self { + pub fn from_linkerfile_blocking( + active_flash: &'a Mutex<NoopRawMutex, RefCell<ACTIVE>>, + dfu_flash: &'a Mutex<NoopRawMutex, RefCell<DFU>>, + state_flash: &'a Mutex<NoopRawMutex, RefCell<STATE>>, + ) -> Self { extern "C" { static __bootloader_state_start: u32; static __bootloader_state_end: u32; @@ -73,21 +108,21 @@ impl<'a, FLASH: NorFlash> let end = &__bootloader_active_end as *const u32 as u32; trace!("ACTIVE: 0x{:x} - 0x{:x}", start, end); - BlockingPartition::new(flash, start, end - start) + BlockingPartition::new(active_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) + BlockingPartition::new(dfu_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) + BlockingPartition::new(state_flash, start, end - start) }; Self { active, dfu, state } diff --git a/embassy-boot/src/firmware_updater/asynch.rs b/embassy-boot/src/firmware_updater/asynch.rs index 2e43e1cc1..26f65f295 100644 --- a/embassy-boot/src/firmware_updater/asynch.rs +++ b/embassy-boot/src/firmware_updater/asynch.rs @@ -13,14 +13,18 @@ use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERA pub struct FirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> { dfu: DFU, state: FirmwareState<'d, STATE>, + last_erased_dfu_sector_index: Option<usize>, } #[cfg(target_os = "none")] -impl<'a, FLASH: NorFlash> - FirmwareUpdaterConfig<Partition<'a, NoopRawMutex, FLASH>, Partition<'a, NoopRawMutex, FLASH>> +impl<'a, DFU: NorFlash, STATE: NorFlash> + FirmwareUpdaterConfig<Partition<'a, NoopRawMutex, DFU>, Partition<'a, NoopRawMutex, STATE>> { /// 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<NoopRawMutex, FLASH>) -> Self { + pub fn from_linkerfile( + dfu_flash: &'a embassy_sync::mutex::Mutex<NoopRawMutex, DFU>, + state_flash: &'a embassy_sync::mutex::Mutex<NoopRawMutex, STATE>, + ) -> Self { extern "C" { static __bootloader_state_start: u32; static __bootloader_state_end: u32; @@ -33,14 +37,14 @@ impl<'a, FLASH: NorFlash> let end = &__bootloader_dfu_end as *const u32 as u32; trace!("DFU: 0x{:x} - 0x{:x}", start, end); - Partition::new(flash, start, end - start) + Partition::new(dfu_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) + Partition::new(state_flash, start, end - start) }; Self { dfu, state } @@ -53,6 +57,7 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> { Self { dfu: config.dfu, state: FirmwareState::new(config.state, aligned), + last_erased_dfu_sector_index: None, } } @@ -69,7 +74,7 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> { /// 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. + /// Mark to trigger firmware swap on next boot if verify succeeds. /// /// 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. @@ -169,21 +174,68 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> { self.state.mark_booted().await } - /// Write data to a flash page. + /// Writes firmware data to the device. /// - /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. + /// This function writes the given data to the firmware area starting at the specified offset. + /// It handles sector erasures and data writes while verifying the device is in a proper state + /// for firmware updates. The function ensures that only unerased sectors are erased before + /// writing and efficiently handles the writing process across sector boundaries and in + /// various configurations (data size, sector size, etc.). /// - /// # Safety + /// # Arguments /// - /// Failing to meet alignment and size requirements may result in a panic. + /// * `offset` - The starting offset within the firmware area where data writing should begin. + /// * `data` - A slice of bytes representing the firmware data to be written. It must be a + /// multiple of NorFlash WRITE_SIZE. + /// + /// # Returns + /// + /// A `Result<(), FirmwareUpdaterError>` indicating the success or failure of the write operation. + /// + /// # Errors + /// + /// This function will return an error if: + /// + /// - The device is not in a proper state to receive firmware updates (e.g., not booted). + /// - There is a failure erasing a sector before writing. + /// - There is a failure writing data to the device. pub async fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> { - assert!(data.len() >= DFU::ERASE_SIZE); - + // Make sure we are running a booted firmware to avoid reverting to a bad state. self.state.verify_booted().await?; - self.dfu.erase(offset as u32, (offset + data.len()) as u32).await?; + // Initialize variables to keep track of the remaining data and the current offset. + let mut remaining_data = data; + let mut offset = offset; - self.dfu.write(offset as u32, data).await?; + // Continue writing as long as there is data left to write. + while !remaining_data.is_empty() { + // Compute the current sector and its boundaries. + let current_sector = offset / DFU::ERASE_SIZE; + let sector_start = current_sector * DFU::ERASE_SIZE; + let sector_end = sector_start + DFU::ERASE_SIZE; + // Determine if the current sector needs to be erased before writing. + let need_erase = self + .last_erased_dfu_sector_index + .map_or(true, |last_erased_sector| current_sector != last_erased_sector); + + // If the sector needs to be erased, erase it and update the last erased sector index. + if need_erase { + self.dfu.erase(sector_start as u32, sector_end as u32).await?; + self.last_erased_dfu_sector_index = Some(current_sector); + } + + // Calculate the size of the data chunk that can be written in the current iteration. + let write_size = core::cmp::min(remaining_data.len(), sector_end - offset); + // Split the data to get the current chunk to be written and the remaining data. + let (data_chunk, rest) = remaining_data.split_at(write_size); + + // Write the current data chunk. + self.dfu.write(offset as u32, data_chunk).await?; + + // Update the offset and remaining data for the next iteration. + remaining_data = rest; + offset += write_size; + } Ok(()) } @@ -273,16 +325,25 @@ impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> { async fn set_magic(&mut self, magic: u8) -> Result<(), FirmwareUpdaterError> { self.state.read(0, &mut self.aligned).await?; - if self.aligned.iter().any(|&b| b != magic) { + if self.aligned[..STATE::WRITE_SIZE].iter().any(|&b| b != magic) { // Read progress validity - self.state.read(STATE::WRITE_SIZE as u32, &mut self.aligned).await?; + if STATE::READ_SIZE <= 2 * STATE::WRITE_SIZE { + self.state.read(STATE::WRITE_SIZE as u32, &mut self.aligned).await?; + } else { + self.aligned.rotate_left(STATE::WRITE_SIZE); + } - if self.aligned.iter().any(|&b| b != STATE_ERASE_VALUE) { + if self.aligned[..STATE::WRITE_SIZE] + .iter() + .any(|&b| b != STATE_ERASE_VALUE) + { // The current progress validity marker is invalid } else { // Invalidate progress self.aligned.fill(!STATE_ERASE_VALUE); - self.state.write(STATE::WRITE_SIZE as u32, &self.aligned).await?; + self.state + .write(STATE::WRITE_SIZE as u32, &self.aligned[..STATE::WRITE_SIZE]) + .await?; } // Clear magic and progress @@ -290,7 +351,7 @@ impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> { // Set magic self.aligned.fill(magic); - self.state.write(0, &self.aligned).await?; + self.state.write(0, &self.aligned[..STATE::WRITE_SIZE]).await?; } Ok(()) } @@ -326,4 +387,76 @@ mod tests { assert_eq!(Sha1::digest(update).as_slice(), hash); } + + #[test] + fn can_verify_sha1_sector_bigger_than_chunk() { + let flash = Mutex::<NoopRawMutex, _>::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 }, &mut aligned); + let mut offset = 0; + for chunk in to_write.chunks(1024) { + block_on(updater.write_firmware(offset, chunk)).unwrap(); + offset += chunk.len(); + } + let mut chunk_buf = [0; 2]; + let mut hash = [0; 20]; + block_on(updater.hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap(); + + assert_eq!(Sha1::digest(update).as_slice(), hash); + } + + #[test] + fn can_verify_sha1_sector_smaller_than_chunk() { + let flash = Mutex::<NoopRawMutex, _>::new(MemFlash::<131072, 1024, 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 }, &mut aligned); + let mut offset = 0; + for chunk in to_write.chunks(2048) { + block_on(updater.write_firmware(offset, chunk)).unwrap(); + offset += chunk.len(); + } + let mut chunk_buf = [0; 2]; + let mut hash = [0; 20]; + block_on(updater.hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap(); + + assert_eq!(Sha1::digest(update).as_slice(), hash); + } + + #[test] + fn can_verify_sha1_cross_sector_boundary() { + let flash = Mutex::<NoopRawMutex, _>::new(MemFlash::<131072, 1024, 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 }, &mut aligned); + let mut offset = 0; + for chunk in to_write.chunks(896) { + block_on(updater.write_firmware(offset, chunk)).unwrap(); + offset += chunk.len(); + } + let mut chunk_buf = [0; 2]; + let mut hash = [0; 20]; + block_on(updater.hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap(); + + assert_eq!(Sha1::digest(update).as_slice(), hash); + } } diff --git a/embassy-boot/src/firmware_updater/blocking.rs b/embassy-boot/src/firmware_updater/blocking.rs index f1368540d..35772a856 100644 --- a/embassy-boot/src/firmware_updater/blocking.rs +++ b/embassy-boot/src/firmware_updater/blocking.rs @@ -13,15 +13,47 @@ use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERA pub struct BlockingFirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> { dfu: DFU, state: BlockingFirmwareState<'d, STATE>, + last_erased_dfu_sector_index: Option<usize>, } #[cfg(target_os = "none")] -impl<'a, FLASH: NorFlash> - FirmwareUpdaterConfig<BlockingPartition<'a, NoopRawMutex, FLASH>, BlockingPartition<'a, NoopRawMutex, FLASH>> +impl<'a, DFU: NorFlash, STATE: NorFlash> + FirmwareUpdaterConfig<BlockingPartition<'a, NoopRawMutex, DFU>, BlockingPartition<'a, NoopRawMutex, STATE>> { - /// Create a firmware updater config from the flash and address symbols defined in the linkerfile + /// Constructs a `FirmwareUpdaterConfig` instance from flash memory and address symbols defined in the linker file. + /// + /// This method initializes `BlockingPartition` instances for the DFU (Device Firmware Update), and state + /// partitions, leveraging start and end addresses specified by the linker. These partitions are critical + /// for managing firmware updates, application state, and boot operations within the bootloader. + /// + /// # Parameters + /// - `dfu_flash`: A reference to a mutex-protected `RefCell` for the DFU partition's flash interface. + /// - `state_flash`: A reference to a mutex-protected `RefCell` for the state partition's flash interface. + /// + /// # Safety + /// The method contains `unsafe` blocks for dereferencing raw pointers that represent the start and end addresses + /// of the bootloader's partitions in flash memory. It is crucial that these addresses are accurately defined + /// in the memory.x file to prevent undefined behavior. + /// + /// The caller must ensure that the memory regions defined by these symbols are valid and that the flash memory + /// interfaces provided are compatible with these regions. + /// + /// # Returns + /// A `FirmwareUpdaterConfig` instance with `BlockingPartition` instances for the DFU, and state partitions. + /// + /// # Example + /// ```ignore + /// // Assume `dfu_flash`, and `state_flash` share the same flash memory interface. + /// let layout = Flash::new_blocking(p.FLASH).into_blocking_regions(); + /// let flash = Mutex::new(RefCell::new(layout.bank1_region)); + /// + /// let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash, &flash); + /// // `config` can now be used to create a `FirmwareUpdater` instance for managing boot operations. + /// ``` + /// Working examples can be found in the bootloader examples folder. pub fn from_linkerfile_blocking( - flash: &'a embassy_sync::blocking_mutex::Mutex<NoopRawMutex, core::cell::RefCell<FLASH>>, + dfu_flash: &'a embassy_sync::blocking_mutex::Mutex<NoopRawMutex, core::cell::RefCell<DFU>>, + state_flash: &'a embassy_sync::blocking_mutex::Mutex<NoopRawMutex, core::cell::RefCell<STATE>>, ) -> Self { extern "C" { static __bootloader_state_start: u32; @@ -35,14 +67,14 @@ impl<'a, FLASH: NorFlash> let end = &__bootloader_dfu_end as *const u32 as u32; trace!("DFU: 0x{:x} - 0x{:x}", start, end); - BlockingPartition::new(flash, start, end - start) + BlockingPartition::new(dfu_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) + BlockingPartition::new(state_flash, start, end - start) }; Self { dfu, state } @@ -60,6 +92,7 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE> Self { dfu: config.dfu, state: BlockingFirmwareState::new(config.state, aligned), + last_erased_dfu_sector_index: None, } } @@ -76,7 +109,7 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE> /// 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. + /// Mark to trigger firmware swap on next boot if verify succeeds. /// /// 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. @@ -176,20 +209,68 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE> self.state.mark_booted() } - /// Write data to a flash page. + /// Writes firmware data to the device. /// - /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. + /// This function writes the given data to the firmware area starting at the specified offset. + /// It handles sector erasures and data writes while verifying the device is in a proper state + /// for firmware updates. The function ensures that only unerased sectors are erased before + /// writing and efficiently handles the writing process across sector boundaries and in + /// various configurations (data size, sector size, etc.). /// - /// # Safety + /// # Arguments /// - /// Failing to meet alignment and size requirements may result in a panic. + /// * `offset` - The starting offset within the firmware area where data writing should begin. + /// * `data` - A slice of bytes representing the firmware data to be written. It must be a + /// multiple of NorFlash WRITE_SIZE. + /// + /// # Returns + /// + /// A `Result<(), FirmwareUpdaterError>` indicating the success or failure of the write operation. + /// + /// # Errors + /// + /// This function will return an error if: + /// + /// - The device is not in a proper state to receive firmware updates (e.g., not booted). + /// - There is a failure erasing a sector before writing. + /// - There is a failure writing data to the device. pub fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> { - assert!(data.len() >= DFU::ERASE_SIZE); + // Make sure we are running a booted firmware to avoid reverting to a bad state. self.state.verify_booted()?; - self.dfu.erase(offset as u32, (offset + data.len()) as u32)?; + // Initialize variables to keep track of the remaining data and the current offset. + let mut remaining_data = data; + let mut offset = offset; - self.dfu.write(offset as u32, data)?; + // Continue writing as long as there is data left to write. + while !remaining_data.is_empty() { + // Compute the current sector and its boundaries. + let current_sector = offset / DFU::ERASE_SIZE; + let sector_start = current_sector * DFU::ERASE_SIZE; + let sector_end = sector_start + DFU::ERASE_SIZE; + // Determine if the current sector needs to be erased before writing. + let need_erase = self + .last_erased_dfu_sector_index + .map_or(true, |last_erased_sector| current_sector != last_erased_sector); + + // If the sector needs to be erased, erase it and update the last erased sector index. + if need_erase { + self.dfu.erase(sector_start as u32, sector_end as u32)?; + self.last_erased_dfu_sector_index = Some(current_sector); + } + + // Calculate the size of the data chunk that can be written in the current iteration. + let write_size = core::cmp::min(remaining_data.len(), sector_end - offset); + // Split the data to get the current chunk to be written and the remaining data. + let (data_chunk, rest) = remaining_data.split_at(write_size); + + // Write the current data chunk. + self.dfu.write(offset as u32, data_chunk)?; + + // Update the offset and remaining data for the next iteration. + remaining_data = rest; + offset += write_size; + } Ok(()) } @@ -337,4 +418,82 @@ mod tests { assert_eq!(Sha1::digest(update).as_slice(), hash); } + + #[test] + fn can_verify_sha1_sector_bigger_than_chunk() { + let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(MemFlash::<131072, 4096, 8>::default())); + let state = BlockingPartition::new(&flash, 0, 4096); + let dfu = BlockingPartition::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 = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned); + let mut offset = 0; + for chunk in to_write.chunks(1024) { + updater.write_firmware(offset, chunk).unwrap(); + offset += chunk.len(); + } + let mut chunk_buf = [0; 2]; + let mut hash = [0; 20]; + updater + .hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash) + .unwrap(); + + assert_eq!(Sha1::digest(update).as_slice(), hash); + } + + #[test] + fn can_verify_sha1_sector_smaller_than_chunk() { + let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(MemFlash::<131072, 1024, 8>::default())); + let state = BlockingPartition::new(&flash, 0, 4096); + let dfu = BlockingPartition::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 = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned); + let mut offset = 0; + for chunk in to_write.chunks(2048) { + updater.write_firmware(offset, chunk).unwrap(); + offset += chunk.len(); + } + let mut chunk_buf = [0; 2]; + let mut hash = [0; 20]; + updater + .hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash) + .unwrap(); + + assert_eq!(Sha1::digest(update).as_slice(), hash); + } + + #[test] + fn can_verify_sha1_cross_sector_boundary() { + let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(MemFlash::<131072, 1024, 8>::default())); + let state = BlockingPartition::new(&flash, 0, 4096); + let dfu = BlockingPartition::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 = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned); + let mut offset = 0; + for chunk in to_write.chunks(896) { + updater.write_firmware(offset, chunk).unwrap(); + offset += chunk.len(); + } + let mut chunk_buf = [0; 2]; + let mut hash = [0; 20]; + updater + .hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash) + .unwrap(); + + assert_eq!(Sha1::digest(update).as_slice(), hash); + } } diff --git a/embassy-boot/src/firmware_updater/mod.rs b/embassy-boot/src/firmware_updater/mod.rs index 4814786bf..4c4f4f10b 100644 --- a/embassy-boot/src/firmware_updater/mod.rs +++ b/embassy-boot/src/firmware_updater/mod.rs @@ -8,7 +8,7 @@ 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 easiest way to do this is to use [`FirmwareUpdaterConfig::from_linkerfile_blocking`] or [`FirmwareUpdaterConfig::from_linkerfile_blocking`] which will partition /// the provided flash according to symbols defined in the linkerfile. pub struct FirmwareUpdaterConfig<DFU, STATE> { /// The dfu flash partition diff --git a/embassy-executor-macros/src/lib.rs b/embassy-executor-macros/src/lib.rs index c9d58746a..5461fe04c 100644 --- a/embassy-executor-macros/src/lib.rs +++ b/embassy-executor-macros/src/lib.rs @@ -62,6 +62,13 @@ pub fn task(args: TokenStream, item: TokenStream) -> TokenStream { task::run(&args.meta, f).unwrap_or_else(|x| x).into() } +#[proc_macro_attribute] +pub fn main_avr(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::avr()).unwrap_or_else(|x| x).into() +} + /// Creates a new `executor` instance and declares an application entry point for Cortex-M spawning the corresponding function body as an async task. /// /// The following restrictions apply: diff --git a/embassy-executor-macros/src/macros/main.rs b/embassy-executor-macros/src/macros/main.rs index 3c0d58567..088e64d1c 100644 --- a/embassy-executor-macros/src/macros/main.rs +++ b/embassy-executor-macros/src/macros/main.rs @@ -12,6 +12,20 @@ struct Args { entry: Option<String>, } +pub fn avr() -> TokenStream { + quote! { + #[avr_device::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)); + }) + } + } +} + pub fn riscv(args: &[NestedMeta]) -> TokenStream { let maybe_entry = match Args::from_list(args) { Ok(args) => args.entry, diff --git a/embassy-executor/Cargo.toml b/embassy-executor/Cargo.toml index b6b156c9f..431165cee 100644 --- a/embassy-executor/Cargo.toml +++ b/embassy-executor/Cargo.toml @@ -40,8 +40,7 @@ critical-section = "1.1" document-features = "0.2.7" -# needed for riscv -# remove when https://github.com/rust-lang/rust/pull/114499 is merged +# needed for AVR portable-atomic = { version = "1.5", optional = true } # arch-cortex-m dependencies @@ -51,6 +50,9 @@ cortex-m = { version = "0.7.6", optional = true } wasm-bindgen = { version = "0.2.82", optional = true } js-sys = { version = "0.3", optional = true } +# arch-avr dependencies +avr-device = { version = "0.5.3", optional = true } + [dev-dependencies] critical-section = { version = "1.1", features = ["std"] } @@ -75,9 +77,11 @@ arch-std = ["_arch", "critical-section/std"] ## Cortex-M arch-cortex-m = ["_arch", "dep:cortex-m"] ## RISC-V 32 -arch-riscv32 = ["_arch", "dep:portable-atomic"] +arch-riscv32 = ["_arch"] ## WASM arch-wasm = ["_arch", "dep:wasm-bindgen", "dep:js-sys"] +## AVR +arch-avr = ["_arch", "dep:portable-atomic", "dep:avr-device"] #! ### Executor @@ -88,11 +92,11 @@ executor-interrupt = [] #! ### Task Arena Size #! Sets the [task arena](#task-arena) size. Necessary if you’re not using `nightly`. -#! +#! #! <details> #! <summary>Preconfigured Task Arena Sizes:</summary> #! <!-- rustdoc requires the following blank line for the feature list to render correctly! --> -#! +#! # BEGIN AUTOGENERATED CONFIG FEATURES # Generated by gen_config.py. DO NOT EDIT. diff --git a/embassy-executor/src/arch/avr.rs b/embassy-executor/src/arch/avr.rs new file mode 100644 index 000000000..70085d04d --- /dev/null +++ b/embassy-executor/src/arch/avr.rs @@ -0,0 +1,72 @@ +#[cfg(feature = "executor-interrupt")] +compile_error!("`executor-interrupt` is not supported with `arch-avr`."); + +#[cfg(feature = "executor-thread")] +pub use thread::*; +#[cfg(feature = "executor-thread")] +mod thread { + use core::marker::PhantomData; + + pub use embassy_executor_macros::main_avr as main; + use portable_atomic::{AtomicBool, Ordering}; + + use crate::{raw, Spawner}; + + static SIGNAL_WORK_THREAD_MODE: AtomicBool = AtomicBool::new(false); + + #[export_name = "__pender"] + fn __pender(_context: *mut ()) { + SIGNAL_WORK_THREAD_MODE.store(true, Ordering::SeqCst); + } + + /// avr 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(core::ptr::null_mut()), + 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 { + avr_device::interrupt::disable(); + if !SIGNAL_WORK_THREAD_MODE.swap(false, Ordering::SeqCst) { + avr_device::interrupt::enable(); + avr_device::asm::sleep(); + } else { + avr_device::interrupt::enable(); + self.inner.poll(); + } + } + } + } + } +} diff --git a/embassy-executor/src/arch/riscv32.rs b/embassy-executor/src/arch/riscv32.rs index c56f502d3..01e63a9fd 100644 --- a/embassy-executor/src/arch/riscv32.rs +++ b/embassy-executor/src/arch/riscv32.rs @@ -6,9 +6,9 @@ pub use thread::*; #[cfg(feature = "executor-thread")] mod thread { use core::marker::PhantomData; + use core::sync::atomic::{AtomicBool, Ordering}; pub use embassy_executor_macros::main_riscv as main; - use portable_atomic::{AtomicBool, Ordering}; use crate::{raw, Spawner}; diff --git a/embassy-executor/src/lib.rs b/embassy-executor/src/lib.rs index eea118ade..6a2e493a2 100644 --- a/embassy-executor/src/lib.rs +++ b/embassy-executor/src/lib.rs @@ -23,9 +23,10 @@ macro_rules! check_at_most_one { check_at_most_one!(@amo [$($f)*] [$($f)*] []); }; } -check_at_most_one!("arch-cortex-m", "arch-riscv32", "arch-std", "arch-wasm",); +check_at_most_one!("arch-avr", "arch-cortex-m", "arch-riscv32", "arch-std", "arch-wasm",); #[cfg(feature = "_arch")] +#[cfg_attr(feature = "arch-avr", path = "arch/avr.rs")] #[cfg_attr(feature = "arch-cortex-m", path = "arch/cortex_m.rs")] #[cfg_attr(feature = "arch-riscv32", path = "arch/riscv32.rs")] #[cfg_attr(feature = "arch-std", path = "arch/std.rs")] diff --git a/embassy-executor/src/raw/mod.rs b/embassy-executor/src/raw/mod.rs index 3f00be4a8..3d5e3ab9f 100644 --- a/embassy-executor/src/raw/mod.rs +++ b/embassy-executor/src/raw/mod.rs @@ -581,6 +581,15 @@ impl embassy_time_queue_driver::TimerQueue for TimerQueue { #[cfg(feature = "integrated-timers")] embassy_time_queue_driver::timer_queue_impl!(static TIMER_QUEUE: TimerQueue = TimerQueue); +#[cfg(all(feature = "rtos-trace", feature = "integrated-timers"))] +const fn gcd(a: u64, b: u64) -> u64 { + if b == 0 { + a + } else { + gcd(b, a % b) + } +} + #[cfg(feature = "rtos-trace")] impl rtos_trace::RtosTraceOSCallbacks for Executor { fn task_list() { @@ -588,7 +597,8 @@ impl rtos_trace::RtosTraceOSCallbacks for Executor { } #[cfg(feature = "integrated-timers")] fn time() -> u64 { - Instant::now().as_micros() + const GCD_1M: u64 = gcd(embassy_time_driver::TICK_HZ, 1_000_000); + embassy_time_driver::now() * (1_000_000 / GCD_1M) / (embassy_time_driver::TICK_HZ / GCD_1M) } #[cfg(not(feature = "integrated-timers"))] fn time() -> u64 { diff --git a/embassy-hal-internal/src/atomic_ring_buffer.rs b/embassy-hal-internal/src/atomic_ring_buffer.rs index b4f2cec28..34ceac852 100644 --- a/embassy-hal-internal/src/atomic_ring_buffer.rs +++ b/embassy-hal-internal/src/atomic_ring_buffer.rs @@ -1,6 +1,6 @@ //! Atomic reusable ringbuffer. -use core::slice; use core::sync::atomic::{AtomicPtr, AtomicUsize, Ordering}; +use core::{ptr, slice}; /// Atomic reusable ringbuffer /// @@ -73,6 +73,7 @@ impl RingBuffer { pub unsafe fn deinit(&self) { // Ordering: it's OK to use `Relaxed` because this is not called // concurrently with other methods. + self.buf.store(ptr::null_mut(), Ordering::Relaxed); self.len.store(0, Ordering::Relaxed); self.start.store(0, Ordering::Relaxed); self.end.store(0, Ordering::Relaxed); @@ -82,20 +83,46 @@ impl RingBuffer { /// /// # Safety /// - /// Only one reader can exist at a time. + /// - Only one reader can exist at a time. + /// - Ringbuffer must be initialized. pub unsafe fn reader(&self) -> Reader<'_> { Reader(self) } + /// Try creating a reader, fails if not initialized. + /// + /// # Safety + /// + /// Only one reader can exist at a time. + pub unsafe fn try_reader(&self) -> Option<Reader<'_>> { + if self.buf.load(Ordering::Relaxed).is_null() { + return None; + } + Some(Reader(self)) + } + /// Create a writer. /// /// # Safety /// - /// Only one writer can exist at a time. + /// - Only one writer can exist at a time. + /// - Ringbuffer must be initialized. pub unsafe fn writer(&self) -> Writer<'_> { Writer(self) } + /// Try creating a writer, fails if not initialized. + /// + /// # Safety + /// + /// Only one writer can exist at a time. + pub unsafe fn try_writer(&self) -> Option<Writer<'_>> { + if self.buf.load(Ordering::Relaxed).is_null() { + return None; + } + Some(Writer(self)) + } + /// Return length of buffer. pub fn len(&self) -> usize { self.len.load(Ordering::Relaxed) diff --git a/embassy-hal-internal/src/peripheral.rs b/embassy-hal-internal/src/peripheral.rs index 16d49edfb..f03f41507 100644 --- a/embassy-hal-internal/src/peripheral.rs +++ b/embassy-hal-internal/src/peripheral.rs @@ -1,5 +1,5 @@ use core::marker::PhantomData; -use core::ops::{Deref, DerefMut}; +use core::ops::Deref; /// An exclusive reference to a peripheral. /// @@ -86,13 +86,6 @@ impl<'a, T> Deref for PeripheralRef<'a, T> { } } -impl<'a, T> DerefMut for PeripheralRef<'a, T> { - #[inline] - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } -} - /// Trait for any type that can be used as a peripheral of type `P`. /// /// This is used in driver constructors, to allow passing either owned peripherals (e.g. `TWISPI0`), @@ -162,7 +155,7 @@ pub trait Peripheral: Sized { } } -impl<'b, T: DerefMut> Peripheral for T +impl<'b, T: Deref> Peripheral for T where T::Target: Peripheral, { diff --git a/embassy-net/Cargo.toml b/embassy-net/Cargo.toml index 44bd2e8f3..be9f1d784 100644 --- a/embassy-net/Cargo.toml +++ b/embassy-net/Cargo.toml @@ -16,11 +16,11 @@ categories = [ [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 = ["defmt", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "medium-ieee802154", "igmp"] +features = ["defmt", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "medium-ieee802154", "igmp", "dhcpv4-hostname"] target = "thumbv7em-none-eabi" [package.metadata.docs.rs] -features = ["defmt", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "medium-ieee802154", "igmp"] +features = ["defmt", "tcp", "udp", "dns", "dhcpv4", "proto-ipv6", "medium-ethernet", "medium-ip", "medium-ieee802154", "igmp", "dhcpv4-hostname"] [features] default = [] diff --git a/embassy-net/src/tcp.rs b/embassy-net/src/tcp.rs index 72b0677b4..57c9b7a04 100644 --- a/embassy-net/src/tcp.rs +++ b/embassy-net/src/tcp.rs @@ -342,7 +342,7 @@ impl<'a> TcpSocket<'a> { self.io.with(|s, _| s.may_send()) } - /// return whether the recieve half of the full-duplex connection is open. + /// return whether the receive 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. @@ -471,7 +471,7 @@ impl<'d> TcpIo<'d> { s.register_recv_waker(cx.waker()); Poll::Pending } else { - // if we can't receive because the recieve half of the duplex connection is closed then return an error + // if we can't receive because the receive half of the duplex connection is closed then return an error Poll::Ready(Err(Error::ConnectionReset)) } } else { @@ -491,10 +491,16 @@ impl<'d> TcpIo<'d> { async fn flush(&mut self) -> Result<(), Error> { poll_fn(move |cx| { self.with_mut(|s, _| { - let waiting_close = s.state() == tcp::State::Closed && s.remote_endpoint().is_some(); + let data_pending = s.send_queue() > 0; + let fin_pending = matches!( + s.state(), + tcp::State::FinWait1 | tcp::State::Closing | tcp::State::LastAck + ); + let rst_pending = 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 { + if data_pending || fin_pending || rst_pending { s.register_send_waker(cx.waker()); Poll::Pending // No outstanding sends, socket is flushed diff --git a/embassy-nrf/Cargo.toml b/embassy-nrf/Cargo.toml index 10f268b51..0045d9f97 100644 --- a/embassy-nrf/Cargo.toml +++ b/embassy-nrf/Cargo.toml @@ -15,6 +15,7 @@ src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-nrf/s features = ["time", "defmt", "unstable-pac", "gpiote", "time-driver-rtc1"] flavors = [ + { regex_feature = "nrf51", target = "thumbv6m-none-eabi" }, { regex_feature = "nrf52.*", target = "thumbv7em-none-eabihf" }, { regex_feature = "nrf53.*", target = "thumbv8m.main-none-eabihf" }, { regex_feature = "nrf91.*", target = "thumbv8m.main-none-eabihf" }, @@ -28,6 +29,7 @@ rustdoc-args = ["--cfg", "docsrs"] default = ["rt"] ## Cortex-M runtime (enabled by default) rt = [ + "nrf51-pac?/rt", "nrf52805-pac?/rt", "nrf52810-pac?/rt", "nrf52811-pac?/rt", @@ -71,6 +73,8 @@ reset-pin-as-gpio = [] qspi-multiwrite-flash = [] #! ### Chip selection features +## nRF51 +nrf51 = ["nrf51-pac", "_nrf51"] ## nRF52805 nrf52805 = ["nrf52805-pac", "_nrf52"] ## nRF52810 @@ -104,6 +108,7 @@ _nrf5340-net = ["_nrf5340", "nrf5340-net-pac"] _nrf5340 = ["_gpio-p1", "_dppi"] _nrf9160 = ["nrf9160-pac", "_dppi"] _nrf52 = ["_ppi"] +_nrf51 = ["_ppi"] _time-driver = ["dep:embassy-time-driver", "embassy-time-driver?/tick-hz-32_768"] @@ -133,6 +138,7 @@ embedded-io = { version = "0.6.0" } embedded-io-async = { version = "0.6.1" } defmt = { version = "0.3", optional = true } +bitflags = "2.4.2" log = { version = "0.4.14", optional = true } cortex-m-rt = ">=0.6.15,<0.8" cortex-m = "0.7.6" @@ -140,10 +146,11 @@ critical-section = "1.1" rand_core = "0.6.3" fixed = "1.10.0" embedded-storage = "0.3.1" -embedded-storage-async = "0.4.0" +embedded-storage-async = "0.4.1" cfg-if = "1.0.0" document-features = "0.2.7" +nrf51-pac = { version = "0.12.0", optional = true } nrf52805-pac = { version = "0.12.0", optional = true } nrf52810-pac = { version = "0.12.0", optional = true } nrf52811-pac = { version = "0.12.0", optional = true } diff --git a/embassy-nrf/README.md b/embassy-nrf/README.md index 50662749d..3df5f1fa5 100644 --- a/embassy-nrf/README.md +++ b/embassy-nrf/README.md @@ -14,11 +14,12 @@ For a complete list of available peripherals and features, see the [embassy-nrf The `embassy-nrf` HAL supports most variants of the nRF family: +* nRF51 ([examples](https://github.com/embassy-rs/embassy/tree/main/examples/nrf51)) * nRF52 ([examples](https://github.com/embassy-rs/embassy/tree/main/examples/nrf52840)) * nRF53 ([examples](https://github.com/embassy-rs/embassy/tree/main/examples/nrf5340)) * nRF91 ([examples](https://github.com/embassy-rs/embassy/tree/main/examples/nrf9160)) -Most peripherals are supported. To check what's available, make sure to pick the MCU you're targeting in the top menu in the [documentation](https://docs.embassy.dev/embassy-nrf). +Most peripherals are supported, but can vary between chip families. To check what's available, make sure to pick the MCU you're targeting in the top menu in the [documentation](https://docs.embassy.dev/embassy-nrf). For MCUs with TrustZone support, both Secure (S) and Non-Secure (NS) modes are supported. Running in Secure mode allows running Rust code without a SPM or TF-M binary, saving flash space and simplifying development. diff --git a/embassy-nrf/src/buffered_uarte.rs b/embassy-nrf/src/buffered_uarte.rs index 2c620798d..b04c96e09 100644 --- a/embassy-nrf/src/buffered_uarte.rs +++ b/embassy-nrf/src/buffered_uarte.rs @@ -17,29 +17,26 @@ use core::task::Poll; use embassy_hal_internal::atomic_ring_buffer::RingBuffer; use embassy_hal_internal::{into_ref, PeripheralRef}; -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::sealed::Pin; -use crate::gpio::{self, AnyPin, Pin as GpioPin, PselBits}; +use crate::gpio::{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::uarte::{configure, drop_tx_rx, Config, Instance as UarteInstance}; use crate::{interrupt, pac, Peripheral}; mod sealed { use super::*; pub struct State { - pub tx_waker: AtomicWaker, pub tx_buf: RingBuffer, pub tx_count: AtomicUsize, - pub rx_waker: AtomicWaker, pub rx_buf: RingBuffer, pub rx_started: AtomicBool, pub rx_started_count: AtomicU8, @@ -61,11 +58,9 @@ 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_started: AtomicBool::new(false), rx_started_count: AtomicU8::new(0), @@ -84,128 +79,131 @@ impl<U: UarteInstance> interrupt::typelevel::Handler<U::Interrupt> for Interrupt unsafe fn on_interrupt() { //trace!("irq: start"); let r = U::regs(); + let ss = U::state(); 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 let Some(mut rx) = unsafe { s.rx_buf.try_writer() } { + let buf_len = s.rx_buf.len(); + let half_len = buf_len / 2; - 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 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"); + 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(); - } + // 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(); + ss.rx_waker.wake(); + } - if r.events_endrx.read().bits() != 0 { - //trace!(" irq_rx: endrx"); - r.events_endrx.reset(); + if r.events_endrx.read().bits() != 0 { + //trace!(" irq_rx: endrx"); + r.events_endrx.reset(); - let val = s.rx_ended_count.load(Ordering::Relaxed); - s.rx_ended_count.store(val.wrapping_add(1), Ordering::Relaxed); - } + let val = s.rx_ended_count.load(Ordering::Relaxed); + s.rx_ended_count.store(val.wrapping_add(1), Ordering::Relaxed); + } - if r.events_rxstarted.read().bits() != 0 || !s.rx_started.load(Ordering::Relaxed) { - //trace!(" irq_rx: rxstarted"); - let (ptr, len) = rx.push_buf(); - if len >= half_len { - r.events_rxstarted.reset(); + if r.events_rxstarted.read().bits() != 0 || !s.rx_started.load(Ordering::Relaxed) { + //trace!(" irq_rx: rxstarted"); + let (ptr, len) = rx.push_buf(); + if len >= half_len { + r.events_rxstarted.reset(); - //trace!(" irq_rx: starting second {:?}", 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 _) }); + // 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); + let chn = s.rx_ppi_ch.load(Ordering::Relaxed); - // Enable endrx -> startrx PPI channel. - // From this point on, if endrx happens, startrx is automatically fired. - ppi::regs().chenset.write(|w| unsafe { w.bits(1 << chn) }); + // Enable endrx -> startrx PPI channel. + // From this point on, if endrx happens, startrx is automatically fired. + ppi::regs().chenset.write(|w| unsafe { w.bits(1 << chn) }); - // It is possible that endrx happened BEFORE enabling the PPI. In this case - // the PPI channel doesn't trigger, and we'd hang. We have to detect this - // and manually start. + // It is possible that endrx happened BEFORE enabling the PPI. In this case + // the PPI channel doesn't trigger, and we'd hang. We have to detect this + // and manually start. - // check again in case endrx has happened between the last check and now. - if r.events_endrx.read().bits() != 0 { - //trace!(" irq_rx: endrx"); - r.events_endrx.reset(); + // check again in case endrx has happened between the last check and now. + if r.events_endrx.read().bits() != 0 { + //trace!(" irq_rx: endrx"); + r.events_endrx.reset(); - let val = s.rx_ended_count.load(Ordering::Relaxed); - s.rx_ended_count.store(val.wrapping_add(1), Ordering::Relaxed); + let val = s.rx_ended_count.load(Ordering::Relaxed); + s.rx_ended_count.store(val.wrapping_add(1), Ordering::Relaxed); + } + + let rx_ended = s.rx_ended_count.load(Ordering::Relaxed); + let rx_started = s.rx_started_count.load(Ordering::Relaxed); + + // If we started the same amount of transfers as ended, the last rxend has + // already occured. + let rxend_happened = rx_started == rx_ended; + + // Check if the PPI channel is still enabled. The PPI channel disables itself + // when it fires, so if it's still enabled it hasn't fired. + let ppi_ch_enabled = ppi::regs().chen.read().bits() & (1 << chn) != 0; + + // if rxend happened, and the ppi channel hasn't fired yet, the rxend got missed. + // this condition also naturally matches if `!started`, needed to kickstart the DMA. + if rxend_happened && ppi_ch_enabled { + //trace!("manually starting."); + + // disable the ppi ch, it's of no use anymore. + ppi::regs().chenclr.write(|w| unsafe { w.bits(1 << chn) }); + + // manually start + r.tasks_startrx.write(|w| unsafe { w.bits(1) }); + } + + rx.push_done(half_len); + + s.rx_started_count.store(rx_started.wrapping_add(1), Ordering::Relaxed); + s.rx_started.store(true, Ordering::Relaxed); + } else { + //trace!(" irq_rx: rxstarted no buf"); + r.intenclr.write(|w| w.rxstarted().clear()); } - - let rx_ended = s.rx_ended_count.load(Ordering::Relaxed); - let rx_started = s.rx_started_count.load(Ordering::Relaxed); - - // If we started the same amount of transfers as ended, the last rxend has - // already occured. - let rxend_happened = rx_started == rx_ended; - - // Check if the PPI channel is still enabled. The PPI channel disables itself - // when it fires, so if it's still enabled it hasn't fired. - let ppi_ch_enabled = ppi::regs().chen.read().bits() & (1 << chn) != 0; - - // if rxend happened, and the ppi channel hasn't fired yet, the rxend got missed. - // this condition also naturally matches if `!started`, needed to kickstart the DMA. - if rxend_happened && ppi_ch_enabled { - //trace!("manually starting."); - - // disable the ppi ch, it's of no use anymore. - ppi::regs().chenclr.write(|w| unsafe { w.bits(1 << chn) }); - - // manually start - r.tasks_startrx.write(|w| unsafe { w.bits(1) }); - } - - rx.push_done(half_len); - - s.rx_started_count.store(rx_started.wrapping_add(1), Ordering::Relaxed); - s.rx_started.store(true, Ordering::Relaxed); - } 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(); + if let Some(mut tx) = unsafe { s.tx_buf.try_reader() } { + // 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); - } + let n = s.tx_count.load(Ordering::Relaxed); + //trace!(" irq_tx: endtx {:?}", n); + tx.pop_done(n); + ss.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); + // 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 _) }); + // 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) }); + // Start UARTE Transmit transaction + r.tasks_starttx.write(|w| unsafe { w.bits(1) }); + } } } @@ -215,11 +213,8 @@ impl<U: UarteInstance> interrupt::typelevel::Handler<U::Interrupt> for Interrupt /// Buffered UARTE driver. pub struct BufferedUarte<'d, U: UarteInstance, T: TimerInstance> { - _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>, + tx: BufferedUarteTx<'d, U>, + rx: BufferedUarteRx<'d, U, T>, } impl<'d, U: UarteInstance, T: TimerInstance> Unpin for BufferedUarte<'d, U, T> {} @@ -243,7 +238,7 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { rx_buffer: &'d mut [u8], tx_buffer: &'d mut [u8], ) -> Self { - into_ref!(rxd, txd, ppi_ch1, ppi_ch2, ppi_group); + into_ref!(uarte, timer, rxd, txd, ppi_ch1, ppi_ch2, ppi_group); Self::new_inner( uarte, timer, @@ -280,7 +275,7 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { rx_buffer: &'d mut [u8], tx_buffer: &'d mut [u8], ) -> Self { - into_ref!(rxd, txd, cts, rts, ppi_ch1, ppi_ch2, ppi_group); + into_ref!(uarte, timer, rxd, txd, cts, rts, ppi_ch1, ppi_ch2, ppi_group); Self::new_inner( uarte, timer, @@ -298,8 +293,8 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { } fn new_inner( - peri: impl Peripheral<P = U> + 'd, - timer: impl Peripheral<P = T> + 'd, + peri: PeripheralRef<'d, U>, + timer: PeripheralRef<'d, T>, ppi_ch1: PeripheralRef<'d, AnyConfigurableChannel>, ppi_ch2: PeripheralRef<'d, AnyConfigurableChannel>, ppi_group: PeripheralRef<'d, AnyGroup>, @@ -311,16 +306,127 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { rx_buffer: &'d mut [u8], tx_buffer: &'d mut [u8], ) -> Self { - into_ref!(peri, timer); + configure(U::regs(), config, cts.is_some()); - assert!(rx_buffer.len() % 2 == 0); + let tx = BufferedUarteTx::new_innerer(unsafe { peri.clone_unchecked() }, txd, cts, tx_buffer); + let rx = BufferedUarteRx::new_innerer(peri, timer, ppi_ch1, ppi_ch2, ppi_group, rxd, rts, rx_buffer); + U::Interrupt::pend(); + unsafe { U::Interrupt::enable() }; + + U::state().tx_rx_refcount.store(2, Ordering::Relaxed); + + Self { tx, rx } + } + + /// Adjust the baud rate to the provided value. + pub fn set_baudrate(&mut self, baudrate: Baudrate) { let r = U::regs(); + r.baudrate.write(|w| w.baudrate().variant(baudrate)); + } - let hwfc = cts.is_some(); + /// Split the UART in reader and writer parts. + /// + /// This allows reading and writing concurrently from independent tasks. + pub fn split(self) -> (BufferedUarteRx<'d, U, T>, BufferedUarteTx<'d, U>) { + (self.rx, self.tx) + } - rxd.conf().write(|w| w.input().connect().drive().h0h1()); - r.psel.rxd.write(|w| unsafe { w.bits(rxd.psel_bits()) }); + /// Split the UART in reader and writer parts, by reference. + /// + /// The returned halves borrow from `self`, so you can drop them and go back to using + /// the "un-split" `self`. This allows temporarily splitting the UART. + pub fn split_by_ref(&mut self) -> (&mut BufferedUarteRx<'d, U, T>, &mut BufferedUarteTx<'d, U>) { + (&mut self.rx, &mut self.tx) + } + + /// 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<usize, Error> { + self.rx.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.rx.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.rx.consume(amt) + } + + /// Write a buffer into this writer, returning how many bytes were written. + pub async fn write(&mut self, buf: &[u8]) -> Result<usize, Error> { + self.tx.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.tx.flush().await + } +} + +/// Reader part of the buffered UARTE driver. +pub struct BufferedUarteTx<'d, U: UarteInstance> { + _peri: PeripheralRef<'d, U>, +} + +impl<'d, U: UarteInstance> BufferedUarteTx<'d, U> { + /// Create a new BufferedUarteTx without hardware flow control. + pub fn new( + uarte: impl Peripheral<P = U> + 'd, + _irq: impl interrupt::typelevel::Binding<U::Interrupt, InterruptHandler<U>> + 'd, + txd: impl Peripheral<P = impl GpioPin> + 'd, + config: Config, + tx_buffer: &'d mut [u8], + ) -> Self { + into_ref!(uarte, txd); + Self::new_inner(uarte, txd.map_into(), None, config, tx_buffer) + } + + /// Create a new BufferedUarte with hardware flow control (RTS/CTS) + /// + /// # Panics + /// + /// Panics if `rx_buffer.len()` is odd. + pub fn new_with_cts( + uarte: impl Peripheral<P = U> + 'd, + _irq: impl interrupt::typelevel::Binding<U::Interrupt, InterruptHandler<U>> + 'd, + txd: impl Peripheral<P = impl GpioPin> + 'd, + cts: impl Peripheral<P = impl GpioPin> + 'd, + config: Config, + tx_buffer: &'d mut [u8], + ) -> Self { + into_ref!(uarte, txd, cts); + Self::new_inner(uarte, txd.map_into(), Some(cts.map_into()), config, tx_buffer) + } + + fn new_inner( + peri: PeripheralRef<'d, U>, + txd: PeripheralRef<'d, AnyPin>, + cts: Option<PeripheralRef<'d, AnyPin>>, + config: Config, + tx_buffer: &'d mut [u8], + ) -> Self { + configure(U::regs(), config, cts.is_some()); + + let this = Self::new_innerer(peri, txd, cts, tx_buffer); + + U::Interrupt::pend(); + unsafe { U::Interrupt::enable() }; + + U::state().tx_rx_refcount.store(1, Ordering::Relaxed); + + this + } + + fn new_innerer( + peri: PeripheralRef<'d, U>, + txd: PeripheralRef<'d, AnyPin>, + cts: Option<PeripheralRef<'d, AnyPin>>, + tx_buffer: &'d mut [u8], + ) -> Self { + let r = U::regs(); txd.set_high(); txd.conf().write(|w| w.dir().output().drive().h0h1()); @@ -331,6 +437,203 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { } r.psel.cts.write(|w| unsafe { w.bits(cts.psel_bits()) }); + // Initialize state + let s = U::buffered_state(); + s.tx_count.store(0, Ordering::Relaxed); + let len = tx_buffer.len(); + unsafe { s.tx_buf.init(tx_buffer.as_mut_ptr(), len) }; + + r.events_txstarted.reset(); + + // Enable interrupts + r.intenset.write(|w| { + w.endtx().set(); + w + }); + + Self { _peri: peri } + } + + /// Write a buffer into this writer, returning how many bytes were written. + pub async fn write(&mut self, buf: &[u8]) -> Result<usize, Error> { + poll_fn(move |cx| { + //trace!("poll_write: {:?}", buf.len()); + let ss = U::state(); + let s = U::buffered_state(); + let mut tx = unsafe { s.tx_buf.writer() }; + + let tx_buf = tx.push_slice(); + if tx_buf.is_empty() { + //trace!("poll_write: pending"); + ss.tx_waker.register(cx.waker()); + return Poll::Pending; + } + + 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); + U::Interrupt::pend(); + + Poll::Ready(Ok(n)) + }) + .await + } + + /// Flush this output stream, ensuring that all intermediately buffered contents reach their destination. + pub async fn flush(&mut self) -> Result<(), Error> { + poll_fn(move |cx| { + //trace!("poll_flush"); + let ss = U::state(); + let s = U::buffered_state(); + if !s.tx_buf.is_empty() { + //trace!("poll_flush: pending"); + ss.tx_waker.register(cx.waker()); + return Poll::Pending; + } + + Poll::Ready(Ok(())) + }) + .await + } +} + +impl<'a, U: UarteInstance> Drop for BufferedUarteTx<'a, U> { + fn drop(&mut self) { + let r = U::regs(); + + r.intenclr.write(|w| { + w.txdrdy().set_bit(); + w.txstarted().set_bit(); + w.txstopped().set_bit(); + w + }); + r.events_txstopped.reset(); + r.tasks_stoptx.write(|w| unsafe { w.bits(1) }); + while r.events_txstopped.read().bits() == 0 {} + + let s = U::buffered_state(); + unsafe { s.tx_buf.deinit() } + + let s = U::state(); + drop_tx_rx(r, s); + } +} + +/// Reader part of the buffered UARTE driver. +pub struct BufferedUarteRx<'d, U: UarteInstance, T: TimerInstance> { + _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> BufferedUarteRx<'d, U, T> { + /// Create a new BufferedUarte without hardware flow control. + /// + /// # Panics + /// + /// Panics if `rx_buffer.len()` is odd. + pub fn new( + uarte: impl Peripheral<P = U> + 'd, + timer: impl Peripheral<P = T> + 'd, + ppi_ch1: impl Peripheral<P = impl ConfigurableChannel> + 'd, + ppi_ch2: impl Peripheral<P = impl ConfigurableChannel> + 'd, + ppi_group: impl Peripheral<P = impl Group> + 'd, + _irq: impl interrupt::typelevel::Binding<U::Interrupt, InterruptHandler<U>> + 'd, + rxd: impl Peripheral<P = impl GpioPin> + 'd, + config: Config, + rx_buffer: &'d mut [u8], + ) -> Self { + into_ref!(uarte, timer, rxd, 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(), + None, + config, + rx_buffer, + ) + } + + /// Create a new BufferedUarte with hardware flow control (RTS/CTS) + /// + /// # Panics + /// + /// Panics if `rx_buffer.len()` is odd. + pub fn new_with_rts( + uarte: impl Peripheral<P = U> + 'd, + timer: impl Peripheral<P = T> + 'd, + ppi_ch1: impl Peripheral<P = impl ConfigurableChannel> + 'd, + ppi_ch2: impl Peripheral<P = impl ConfigurableChannel> + 'd, + ppi_group: impl Peripheral<P = impl Group> + 'd, + _irq: impl interrupt::typelevel::Binding<U::Interrupt, InterruptHandler<U>> + 'd, + rxd: impl Peripheral<P = impl GpioPin> + 'd, + rts: impl Peripheral<P = impl GpioPin> + 'd, + config: Config, + rx_buffer: &'d mut [u8], + ) -> Self { + into_ref!(uarte, timer, rxd, 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(), + Some(rts.map_into()), + config, + rx_buffer, + ) + } + + fn new_inner( + peri: PeripheralRef<'d, U>, + timer: PeripheralRef<'d, T>, + ppi_ch1: PeripheralRef<'d, AnyConfigurableChannel>, + ppi_ch2: PeripheralRef<'d, AnyConfigurableChannel>, + ppi_group: PeripheralRef<'d, AnyGroup>, + rxd: PeripheralRef<'d, AnyPin>, + rts: Option<PeripheralRef<'d, AnyPin>>, + config: Config, + rx_buffer: &'d mut [u8], + ) -> Self { + configure(U::regs(), config, rts.is_some()); + + let this = Self::new_innerer(peri, timer, ppi_ch1, ppi_ch2, ppi_group, rxd, rts, rx_buffer); + + U::Interrupt::pend(); + unsafe { U::Interrupt::enable() }; + + U::state().tx_rx_refcount.store(1, Ordering::Relaxed); + + this + } + + fn new_innerer( + peri: PeripheralRef<'d, U>, + timer: PeripheralRef<'d, T>, + ppi_ch1: PeripheralRef<'d, AnyConfigurableChannel>, + ppi_ch2: PeripheralRef<'d, AnyConfigurableChannel>, + ppi_group: PeripheralRef<'d, AnyGroup>, + rxd: PeripheralRef<'d, AnyPin>, + rts: Option<PeripheralRef<'d, AnyPin>>, + rx_buffer: &'d mut [u8], + ) -> Self { + assert!(rx_buffer.len() % 2 == 0); + + let r = U::regs(); + + rxd.conf().write(|w| w.input().connect().drive().h0h1()); + r.psel.rxd.write(|w| unsafe { w.bits(rxd.psel_bits()) }); + if let Some(pin) = &rts { pin.set_high(); pin.conf().write(|w| w.dir().output().drive().h0h1()); @@ -339,35 +642,21 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { // Initialize state let s = U::buffered_state(); - s.tx_count.store(0, Ordering::Relaxed); s.rx_started_count.store(0, Ordering::Relaxed); s.rx_ended_count.store(0, Ordering::Relaxed); s.rx_started.store(false, 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(hwfc); - w.parity().variant(config.parity); - w - }); - r.baudrate.write(|w| w.baudrate().variant(config.baudrate)); - // clear errors let errors = r.errorsrc.read().bits(); r.errorsrc.write(|w| unsafe { w.bits(errors) }); 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(); @@ -376,10 +665,6 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { w }); - // Enable UARTE instance - apply_workaround_for_enable_anomaly(&r); - r.enable.write(|w| w.enable().enabled()); - // Configure byte counter. let timer = Timer::new_counter(timer); timer.cc(1).write(rx_buffer.len() as u32 * 2); @@ -401,9 +686,6 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { ppi_ch2.disable(); ppi_group.add_channel(&ppi_ch2); - U::Interrupt::pend(); - unsafe { U::Interrupt::enable() }; - Self { _peri: peri, timer, @@ -413,80 +695,24 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { } } - fn pend_irq() { - U::Interrupt::pend() - } - - /// Adjust the baud rate to the provided value. - pub fn set_baudrate(&mut self, baudrate: Baudrate) { - let r = U::regs(); - r.baudrate.write(|w| w.baudrate().variant(baudrate)); - } - - /// 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 }) - } - - async fn inner_read(&self, buf: &mut [u8]) -> Result<usize, Error> { - let data = self.inner_fill_buf().await?; + /// 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<usize, Error> { + let data = self.fill_buf().await?; let n = data.len().min(buf.len()); buf[..n].copy_from_slice(&data[..n]); - self.inner_consume(n); + self.consume(n); Ok(n) } - async fn inner_write<'a>(&'a self, buf: &'a [u8]) -> Result<usize, Error> { - poll_fn(move |cx| { - //trace!("poll_write: {:?}", buf.len()); - let s = U::buffered_state(); - let mut tx = unsafe { s.tx_buf.writer() }; - - let tx_buf = tx.push_slice(); - if tx_buf.is_empty() { - //trace!("poll_write: pending"); - s.tx_waker.register(cx.waker()); - return Poll::Pending; - } - - 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> { + /// 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> { poll_fn(move |cx| { compiler_fence(Ordering::SeqCst); //trace!("poll_read"); let r = U::regs(); let s = U::buffered_state(); + let ss = U::state(); // Read the RXDRDY counter. T::regs().tasks_capture[0].write(|w| unsafe { w.bits(1) }); @@ -510,7 +736,7 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { let len = s.rx_buf.len(); if start == end { //trace!(" empty"); - s.rx_waker.register(cx.waker()); + ss.rx_waker.register(cx.waker()); r.intenset.write(|w| w.rxdrdy().set_bit()); return Poll::Pending; } @@ -532,7 +758,8 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { .await } - fn inner_consume(&self, amt: usize) { + /// 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) { if amt == 0 { return; } @@ -542,69 +769,31 @@ impl<'d, U: UarteInstance, T: TimerInstance> BufferedUarte<'d, U, T> { 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<usize, Error> { - 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<usize, Error> { - 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 - } } -/// Reader part of the buffered UARTE driver. -pub struct BufferedUarteTx<'u, 'd, U: UarteInstance, T: TimerInstance> { - inner: &'u BufferedUarte<'d, U, T>, -} +impl<'a, U: UarteInstance, T: TimerInstance> Drop for BufferedUarteRx<'a, U, T> { + fn drop(&mut self) { + self._ppi_group.disable_all(); -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<usize, Error> { - self.inner.inner_write(buf).await - } + let r = U::regs(); - /// 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 - } -} + self.timer.stop(); -/// Writer part of the buffered UARTE driver. -pub struct BufferedUarteRx<'u, 'd, U: UarteInstance, T: TimerInstance> { - inner: &'u BufferedUarte<'d, U, T>, -} + r.intenclr.write(|w| { + w.rxdrdy().set_bit(); + w.rxstarted().set_bit(); + w.rxto().set_bit(); + w + }); + r.events_rxto.reset(); + r.tasks_stoprx.write(|w| unsafe { w.bits(1) }); + while r.events_rxto.read().bits() == 0 {} -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<usize, Error> { - self.inner.inner_read(buf).await - } + let s = U::buffered_state(); + unsafe { s.rx_buf.deinit() } - /// 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) + let s = U::state(); + drop_tx_rx(r, s); } } @@ -621,95 +810,63 @@ mod _embedded_io { type Error = Error; } - impl<'u, 'd, U: UarteInstance, T: TimerInstance> embedded_io_async::ErrorType for BufferedUarteRx<'u, 'd, U, T> { + impl<'d, U: UarteInstance, T: TimerInstance> embedded_io_async::ErrorType for BufferedUarteRx<'d, U, T> { type Error = Error; } - impl<'u, 'd, U: UarteInstance, T: TimerInstance> embedded_io_async::ErrorType for BufferedUarteTx<'u, 'd, U, T> { + impl<'d, U: UarteInstance> embedded_io_async::ErrorType for BufferedUarteTx<'d, U> { type Error = Error; } impl<'d, U: UarteInstance, T: TimerInstance> embedded_io_async::Read for BufferedUarte<'d, U, T> { async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> { - self.inner_read(buf).await + self.read(buf).await } } - impl<'u, 'd: 'u, U: UarteInstance, T: TimerInstance> embedded_io_async::Read for BufferedUarteRx<'u, 'd, U, T> { + impl<'d: 'd, U: UarteInstance, T: TimerInstance> embedded_io_async::Read for BufferedUarteRx<'d, U, T> { async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> { - self.inner.inner_read(buf).await + self.read(buf).await } } impl<'d, U: UarteInstance, T: TimerInstance> embedded_io_async::BufRead for BufferedUarte<'d, U, T> { async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { - self.inner_fill_buf().await + self.fill_buf().await } fn consume(&mut self, amt: usize) { - self.inner_consume(amt) + self.consume(amt) } } - impl<'u, 'd: 'u, U: UarteInstance, T: TimerInstance> embedded_io_async::BufRead for BufferedUarteRx<'u, 'd, U, T> { + impl<'d: 'd, U: UarteInstance, T: TimerInstance> embedded_io_async::BufRead for BufferedUarteRx<'d, U, T> { async fn fill_buf(&mut self) -> Result<&[u8], Self::Error> { - self.inner.inner_fill_buf().await + self.fill_buf().await } fn consume(&mut self, amt: usize) { - self.inner.inner_consume(amt) + self.consume(amt) } } impl<'d, U: UarteInstance, T: TimerInstance> embedded_io_async::Write for BufferedUarte<'d, U, T> { async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> { - self.inner_write(buf).await + self.write(buf).await } async fn flush(&mut self) -> Result<(), Self::Error> { - self.inner_flush().await + self.flush().await } } - impl<'u, 'd: 'u, U: UarteInstance, T: TimerInstance> embedded_io_async::Write for BufferedUarteTx<'u, 'd, U, T> { + impl<'d: 'd, U: UarteInstance> embedded_io_async::Write for BufferedUarteTx<'d, U> { async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> { - self.inner.inner_write(buf).await + self.write(buf).await } async fn flush(&mut self) -> Result<(), Self::Error> { - self.inner.inner_flush().await - } - } -} - -impl<'a, U: UarteInstance, T: TimerInstance> Drop for BufferedUarte<'a, U, T> { - fn drop(&mut self) { - self._ppi_group.disable_all(); - - let r = U::regs(); - - self.timer.stop(); - - 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(); + self.flush().await } } } diff --git a/embassy-nrf/src/chips/nrf51.rs b/embassy-nrf/src/chips/nrf51.rs new file mode 100644 index 000000000..cc1cbc8a0 --- /dev/null +++ b/embassy-nrf/src/chips/nrf51.rs @@ -0,0 +1,174 @@ +pub use nrf51_pac as pac; + +/// The maximum buffer size that the EasyDMA can send/recv in one operation. +pub const EASY_DMA_SIZE: usize = (1 << 14) - 1; + +pub const FLASH_SIZE: usize = 128 * 1024; + +embassy_hal_internal::peripherals! { + // RTC + RTC0, + RTC1, + + // WDT + WDT, + + // NVMC + NVMC, + + // RNG + RNG, + + // UARTE + UART0, + + // SPI/TWI + TWI0, + SPI0, + + // ADC + ADC, + + // TIMER + TIMER0, + TIMER1, + TIMER2, + + // GPIOTE + GPIOTE_CH0, + GPIOTE_CH1, + GPIOTE_CH2, + GPIOTE_CH3, + + // PPI + PPI_CH0, + PPI_CH1, + PPI_CH2, + PPI_CH3, + PPI_CH4, + PPI_CH5, + PPI_CH6, + PPI_CH7, + PPI_CH8, + PPI_CH9, + PPI_CH10, + PPI_CH11, + PPI_CH12, + PPI_CH13, + PPI_CH14, + PPI_CH15, + + PPI_GROUP0, + PPI_GROUP1, + PPI_GROUP2, + PPI_GROUP3, + + // GPIO port 0 + P0_00, + P0_01, + P0_02, + P0_03, + P0_04, + P0_05, + P0_06, + P0_07, + P0_08, + P0_09, + P0_10, + P0_11, + P0_12, + P0_13, + P0_14, + P0_15, + P0_16, + P0_17, + P0_18, + P0_19, + P0_20, + P0_21, + P0_22, + P0_23, + P0_24, + P0_25, + P0_26, + P0_27, + P0_28, + P0_29, + P0_30, + P0_31, + + // TEMP + TEMP, + + // Radio + RADIO, +} + +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); +impl_pin!(P0_03, 0, 3); +impl_pin!(P0_04, 0, 4); +impl_pin!(P0_05, 0, 5); +impl_pin!(P0_06, 0, 6); +impl_pin!(P0_07, 0, 7); +impl_pin!(P0_08, 0, 8); +impl_pin!(P0_09, 0, 9); +impl_pin!(P0_10, 0, 10); +impl_pin!(P0_11, 0, 11); +impl_pin!(P0_12, 0, 12); +impl_pin!(P0_13, 0, 13); +impl_pin!(P0_14, 0, 14); +impl_pin!(P0_15, 0, 15); +impl_pin!(P0_16, 0, 16); +impl_pin!(P0_17, 0, 17); +impl_pin!(P0_18, 0, 18); +impl_pin!(P0_19, 0, 19); +impl_pin!(P0_20, 0, 20); +impl_pin!(P0_21, 0, 21); +impl_pin!(P0_22, 0, 22); +impl_pin!(P0_23, 0, 23); +impl_pin!(P0_24, 0, 24); +impl_pin!(P0_25, 0, 25); +impl_pin!(P0_26, 0, 26); +impl_pin!(P0_27, 0, 27); +impl_pin!(P0_28, 0, 28); +impl_pin!(P0_29, 0, 29); +impl_pin!(P0_30, 0, 30); +impl_pin!(P0_31, 0, 31); + +impl_radio!(RADIO, RADIO, RADIO); + +embassy_hal_internal::interrupt_mod!( + POWER_CLOCK, + RADIO, + UART0, + SPI0_TWI0, + SPI1_TWI1, + GPIOTE, + ADC, + TIMER0, + TIMER1, + TIMER2, + RTC0, + TEMP, + RNG, + ECB, + CCM_AAR, + WDT, + RTC1, + QDEC, + LPCOMP, + SWI0, + SWI1, + SWI2, + SWI3, + SWI4, + SWI5, +); diff --git a/embassy-nrf/src/chips/nrf52805.rs b/embassy-nrf/src/chips/nrf52805.rs index 624d6613d..14c3f9b1a 100644 --- a/embassy-nrf/src/chips/nrf52805.rs +++ b/embassy-nrf/src/chips/nrf52805.rs @@ -129,6 +129,9 @@ embassy_hal_internal::peripherals! { // QDEC QDEC, + + // Radio + RADIO, } impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); @@ -209,6 +212,8 @@ impl_ppi_channel!(PPI_CH31, 31 => static); impl_saadc_input!(P0_04, ANALOG_INPUT2); impl_saadc_input!(P0_05, ANALOG_INPUT3); +impl_radio!(RADIO, RADIO, RADIO); + embassy_hal_internal::interrupt_mod!( POWER_CLOCK, RADIO, diff --git a/embassy-nrf/src/chips/nrf52810.rs b/embassy-nrf/src/chips/nrf52810.rs index 002feab3b..c607586db 100644 --- a/embassy-nrf/src/chips/nrf52810.rs +++ b/embassy-nrf/src/chips/nrf52810.rs @@ -135,6 +135,9 @@ embassy_hal_internal::peripherals! { // PDM PDM, + + // Radio + RADIO, } impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); @@ -235,6 +238,8 @@ impl_saadc_input!(P0_29, ANALOG_INPUT5); impl_saadc_input!(P0_30, ANALOG_INPUT6); impl_saadc_input!(P0_31, ANALOG_INPUT7); +impl_radio!(RADIO, RADIO, RADIO); + embassy_hal_internal::interrupt_mod!( POWER_CLOCK, RADIO, diff --git a/embassy-nrf/src/chips/nrf52811.rs b/embassy-nrf/src/chips/nrf52811.rs index 5952907f8..5f70365b4 100644 --- a/embassy-nrf/src/chips/nrf52811.rs +++ b/embassy-nrf/src/chips/nrf52811.rs @@ -135,6 +135,9 @@ embassy_hal_internal::peripherals! { // PDM PDM, + + // Radio + RADIO, } impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); @@ -237,6 +240,8 @@ impl_saadc_input!(P0_29, ANALOG_INPUT5); impl_saadc_input!(P0_30, ANALOG_INPUT6); impl_saadc_input!(P0_31, ANALOG_INPUT7); +impl_radio!(RADIO, RADIO, RADIO); + embassy_hal_internal::interrupt_mod!( POWER_CLOCK, RADIO, diff --git a/embassy-nrf/src/chips/nrf52820.rs b/embassy-nrf/src/chips/nrf52820.rs index c2f792cb9..82d097407 100644 --- a/embassy-nrf/src/chips/nrf52820.rs +++ b/embassy-nrf/src/chips/nrf52820.rs @@ -130,6 +130,9 @@ embassy_hal_internal::peripherals! { // QDEC QDEC, + + // Radio + RADIO, } impl_usb!(USBD, USBD, USBD); @@ -224,6 +227,8 @@ impl_ppi_channel!(PPI_CH29, 29 => static); impl_ppi_channel!(PPI_CH30, 30 => static); impl_ppi_channel!(PPI_CH31, 31 => static); +impl_radio!(RADIO, RADIO, RADIO); + embassy_hal_internal::interrupt_mod!( POWER_CLOCK, RADIO, diff --git a/embassy-nrf/src/chips/nrf52832.rs b/embassy-nrf/src/chips/nrf52832.rs index 65d52364d..67b32fe5f 100644 --- a/embassy-nrf/src/chips/nrf52832.rs +++ b/embassy-nrf/src/chips/nrf52832.rs @@ -150,6 +150,9 @@ embassy_hal_internal::peripherals! { // PDM PDM, + + // Radio + RADIO, } impl_uarte!(UARTE0, UARTE0, UARTE0_UART0); @@ -264,6 +267,8 @@ impl_saadc_input!(P0_31, ANALOG_INPUT7); impl_i2s!(I2S, I2S, I2S); +impl_radio!(RADIO, RADIO, RADIO); + embassy_hal_internal::interrupt_mod!( POWER_CLOCK, RADIO, diff --git a/embassy-nrf/src/chips/nrf52833.rs b/embassy-nrf/src/chips/nrf52833.rs index 7c9b66d69..20f14e2d6 100644 --- a/embassy-nrf/src/chips/nrf52833.rs +++ b/embassy-nrf/src/chips/nrf52833.rs @@ -170,6 +170,9 @@ embassy_hal_internal::peripherals! { // I2S I2S, + + // Radio + RADIO, } impl_usb!(USBD, USBD, USBD); @@ -306,6 +309,8 @@ impl_saadc_input!(P0_31, ANALOG_INPUT7); impl_i2s!(I2S, I2S, I2S); +impl_radio!(RADIO, RADIO, RADIO); + embassy_hal_internal::interrupt_mod!( POWER_CLOCK, RADIO, diff --git a/embassy-nrf/src/chips/nrf52840.rs b/embassy-nrf/src/chips/nrf52840.rs index 51c55cd4d..d3272b2e8 100644 --- a/embassy-nrf/src/chips/nrf52840.rs +++ b/embassy-nrf/src/chips/nrf52840.rs @@ -173,6 +173,9 @@ embassy_hal_internal::peripherals! { // I2S I2S, + + // Radio + RADIO, } impl_usb!(USBD, USBD, USBD); @@ -311,6 +314,8 @@ impl_saadc_input!(P0_31, ANALOG_INPUT7); impl_i2s!(I2S, I2S, I2S); +impl_radio!(RADIO, RADIO, RADIO); + embassy_hal_internal::interrupt_mod!( POWER_CLOCK, RADIO, diff --git a/embassy-nrf/src/chips/nrf5340_net.rs b/embassy-nrf/src/chips/nrf5340_net.rs index a7cf82872..65e8f9653 100644 --- a/embassy-nrf/src/chips/nrf5340_net.rs +++ b/embassy-nrf/src/chips/nrf5340_net.rs @@ -248,6 +248,9 @@ embassy_hal_internal::peripherals! { P1_13, P1_14, P1_15, + + // Radio + RADIO, } impl_uarte!(SERIAL0, UARTE0, SERIAL0); @@ -345,6 +348,8 @@ impl_ppi_channel!(PPI_CH29, 29 => configurable); impl_ppi_channel!(PPI_CH30, 30 => configurable); impl_ppi_channel!(PPI_CH31, 31 => configurable); +impl_radio!(RADIO, RADIO, RADIO); + embassy_hal_internal::interrupt_mod!( CLOCK_POWER, RADIO, diff --git a/embassy-nrf/src/gpio.rs b/embassy-nrf/src/gpio.rs index eabf409dd..3649ea61a 100644 --- a/embassy-nrf/src/gpio.rs +++ b/embassy-nrf/src/gpio.rs @@ -8,7 +8,13 @@ use cfg_if::cfg_if; use embassy_hal_internal::{impl_peripheral, into_ref, PeripheralRef}; use self::sealed::Pin as _; +#[cfg(feature = "nrf51")] +use crate::pac::gpio; +#[cfg(feature = "nrf51")] +use crate::pac::gpio::pin_cnf::{DRIVE_A, PULL_A}; +#[cfg(not(feature = "nrf51"))] use crate::pac::p0 as gpio; +#[cfg(not(feature = "nrf51"))] use crate::pac::p0::pin_cnf::{DRIVE_A, PULL_A}; use crate::{pac, Peripheral}; @@ -36,14 +42,14 @@ pub enum Pull { } /// GPIO input driver. -pub struct Input<'d, T: Pin> { - pub(crate) pin: Flex<'d, T>, +pub struct Input<'d> { + pub(crate) pin: Flex<'d>, } -impl<'d, T: Pin> Input<'d, T> { +impl<'d> Input<'d> { /// Create GPIO input driver for a [Pin] with the provided [Pull] configuration. #[inline] - pub fn new(pin: impl Peripheral<P = T> + 'd, pull: Pull) -> Self { + pub fn new(pin: impl Peripheral<P = impl Pin> + 'd, pull: Pull) -> Self { let mut pin = Flex::new(pin); pin.set_as_input(pull); @@ -122,14 +128,14 @@ pub enum OutputDrive { } /// GPIO output driver. -pub struct Output<'d, T: Pin> { - pub(crate) pin: Flex<'d, T>, +pub struct Output<'d> { + pub(crate) pin: Flex<'d>, } -impl<'d, T: Pin> Output<'d, T> { +impl<'d> Output<'d> { /// Create GPIO output driver for a [Pin] with the provided [Level] and [OutputDriver] configuration. #[inline] - pub fn new(pin: impl Peripheral<P = T> + 'd, initial_output: Level, drive: OutputDrive) -> Self { + pub fn new(pin: impl Peripheral<P = impl Pin> + 'd, initial_output: Level, drive: OutputDrive) -> Self { let mut pin = Flex::new(pin); match initial_output { Level::High => pin.set_high(), @@ -183,7 +189,7 @@ impl<'d, T: Pin> Output<'d, T> { } } -fn convert_drive(drive: OutputDrive) -> DRIVE_A { +pub(crate) fn convert_drive(drive: OutputDrive) -> DRIVE_A { match drive { OutputDrive::Standard => DRIVE_A::S0S1, OutputDrive::HighDrive0Standard1 => DRIVE_A::H0S1, @@ -209,20 +215,20 @@ fn convert_pull(pull: Pull) -> PULL_A { /// This pin can either be a disconnected, input, or output pin, or both. The level register bit will remain /// set while not in output mode, so the pin's level will be 'remembered' when it is not in output /// mode. -pub struct Flex<'d, T: Pin> { - pub(crate) pin: PeripheralRef<'d, T>, +pub struct Flex<'d> { + pub(crate) pin: PeripheralRef<'d, AnyPin>, } -impl<'d, T: Pin> Flex<'d, T> { +impl<'d> Flex<'d> { /// Wrap the pin in a `Flex`. /// /// The pin remains disconnected. The initial output level is unspecified, but can be changed /// before the pin is put into output mode. #[inline] - pub fn new(pin: impl Peripheral<P = T> + 'd) -> Self { + pub fn new(pin: impl Peripheral<P = impl Pin> + 'd) -> Self { into_ref!(pin); // Pin will be in disconnected state. - Self { pin } + Self { pin: pin.map_into() } } /// Put the pin into input mode. @@ -349,7 +355,7 @@ impl<'d, T: Pin> Flex<'d, T> { } } -impl<'d, T: Pin> Drop for Flex<'d, T> { +impl<'d> Drop for Flex<'d> { fn drop(&mut self) { self.pin.conf().reset(); } @@ -376,6 +382,9 @@ pub(crate) mod sealed { fn block(&self) -> &gpio::RegisterBlock { unsafe { match self.pin_port() / 32 { + #[cfg(feature = "nrf51")] + 0 => &*pac::GPIO::ptr(), + #[cfg(not(feature = "nrf51"))] 0 => &*pac::P0::ptr(), #[cfg(feature = "_gpio-p1")] 1 => &*pac::P1::ptr(), @@ -478,6 +487,7 @@ impl<'a, P: Pin> PselBits for Option<PeripheralRef<'a, P>> { } } +#[allow(dead_code)] pub(crate) fn deconfigure_pin(psel_bits: u32) { if psel_bits & 0x8000_0000 != 0 { return; @@ -510,7 +520,7 @@ macro_rules! impl_pin { mod eh02 { use super::*; - impl<'d, T: Pin> embedded_hal_02::digital::v2::InputPin for Input<'d, T> { + impl<'d> embedded_hal_02::digital::v2::InputPin for Input<'d> { type Error = Infallible; fn is_high(&self) -> Result<bool, Self::Error> { @@ -522,7 +532,7 @@ mod eh02 { } } - impl<'d, T: Pin> embedded_hal_02::digital::v2::OutputPin for Output<'d, T> { + impl<'d> embedded_hal_02::digital::v2::OutputPin for Output<'d> { type Error = Infallible; fn set_high(&mut self) -> Result<(), Self::Error> { @@ -534,7 +544,7 @@ mod eh02 { } } - impl<'d, T: Pin> embedded_hal_02::digital::v2::StatefulOutputPin for Output<'d, T> { + impl<'d> embedded_hal_02::digital::v2::StatefulOutputPin for Output<'d> { fn is_set_high(&self) -> Result<bool, Self::Error> { Ok(self.is_set_high()) } @@ -544,7 +554,7 @@ mod eh02 { } } - impl<'d, T: Pin> embedded_hal_02::digital::v2::ToggleableOutputPin for Output<'d, T> { + impl<'d> embedded_hal_02::digital::v2::ToggleableOutputPin for Output<'d> { type Error = Infallible; #[inline] fn toggle(&mut self) -> Result<(), Self::Error> { @@ -556,7 +566,7 @@ mod eh02 { /// Implement [`embedded_hal_02::digital::v2::InputPin`] for [`Flex`]; /// /// If the pin is not in input mode the result is unspecified. - impl<'d, T: Pin> embedded_hal_02::digital::v2::InputPin for Flex<'d, T> { + impl<'d> embedded_hal_02::digital::v2::InputPin for Flex<'d> { type Error = Infallible; fn is_high(&self) -> Result<bool, Self::Error> { @@ -568,7 +578,7 @@ mod eh02 { } } - impl<'d, T: Pin> embedded_hal_02::digital::v2::OutputPin for Flex<'d, T> { + impl<'d> embedded_hal_02::digital::v2::OutputPin for Flex<'d> { type Error = Infallible; fn set_high(&mut self) -> Result<(), Self::Error> { @@ -580,7 +590,7 @@ mod eh02 { } } - impl<'d, T: Pin> embedded_hal_02::digital::v2::StatefulOutputPin for Flex<'d, T> { + impl<'d> embedded_hal_02::digital::v2::StatefulOutputPin for Flex<'d> { fn is_set_high(&self) -> Result<bool, Self::Error> { Ok(self.is_set_high()) } @@ -590,7 +600,7 @@ mod eh02 { } } - impl<'d, T: Pin> embedded_hal_02::digital::v2::ToggleableOutputPin for Flex<'d, T> { + impl<'d> embedded_hal_02::digital::v2::ToggleableOutputPin for Flex<'d> { type Error = Infallible; #[inline] fn toggle(&mut self) -> Result<(), Self::Error> { @@ -600,11 +610,11 @@ mod eh02 { } } -impl<'d, T: Pin> embedded_hal_1::digital::ErrorType for Input<'d, T> { +impl<'d> embedded_hal_1::digital::ErrorType for Input<'d> { type Error = Infallible; } -impl<'d, T: Pin> embedded_hal_1::digital::InputPin for Input<'d, T> { +impl<'d> embedded_hal_1::digital::InputPin for Input<'d> { fn is_high(&mut self) -> Result<bool, Self::Error> { Ok((*self).is_high()) } @@ -614,11 +624,11 @@ impl<'d, T: Pin> embedded_hal_1::digital::InputPin for Input<'d, T> { } } -impl<'d, T: Pin> embedded_hal_1::digital::ErrorType for Output<'d, T> { +impl<'d> embedded_hal_1::digital::ErrorType for Output<'d> { type Error = Infallible; } -impl<'d, T: Pin> embedded_hal_1::digital::OutputPin for Output<'d, T> { +impl<'d> embedded_hal_1::digital::OutputPin for Output<'d> { fn set_high(&mut self) -> Result<(), Self::Error> { Ok(self.set_high()) } @@ -628,7 +638,7 @@ impl<'d, T: Pin> embedded_hal_1::digital::OutputPin for Output<'d, T> { } } -impl<'d, T: Pin> embedded_hal_1::digital::StatefulOutputPin for Output<'d, T> { +impl<'d> embedded_hal_1::digital::StatefulOutputPin for Output<'d> { fn is_set_high(&mut self) -> Result<bool, Self::Error> { Ok((*self).is_set_high()) } @@ -638,14 +648,14 @@ impl<'d, T: Pin> embedded_hal_1::digital::StatefulOutputPin for Output<'d, T> { } } -impl<'d, T: Pin> embedded_hal_1::digital::ErrorType for Flex<'d, T> { +impl<'d> embedded_hal_1::digital::ErrorType for Flex<'d> { type Error = Infallible; } /// Implement [`InputPin`] for [`Flex`]; /// /// If the pin is not in input mode the result is unspecified. -impl<'d, T: Pin> embedded_hal_1::digital::InputPin for Flex<'d, T> { +impl<'d> embedded_hal_1::digital::InputPin for Flex<'d> { fn is_high(&mut self) -> Result<bool, Self::Error> { Ok((*self).is_high()) } @@ -655,7 +665,7 @@ impl<'d, T: Pin> embedded_hal_1::digital::InputPin for Flex<'d, T> { } } -impl<'d, T: Pin> embedded_hal_1::digital::OutputPin for Flex<'d, T> { +impl<'d> embedded_hal_1::digital::OutputPin for Flex<'d> { fn set_high(&mut self) -> Result<(), Self::Error> { Ok(self.set_high()) } @@ -665,7 +675,7 @@ impl<'d, T: Pin> embedded_hal_1::digital::OutputPin for Flex<'d, T> { } } -impl<'d, T: Pin> embedded_hal_1::digital::StatefulOutputPin for Flex<'d, T> { +impl<'d> embedded_hal_1::digital::StatefulOutputPin for Flex<'d> { fn is_set_high(&mut self) -> Result<bool, Self::Error> { Ok((*self).is_set_high()) } diff --git a/embassy-nrf/src/gpiote.rs b/embassy-nrf/src/gpiote.rs index 8020b8dc2..12f4ed0a0 100644 --- a/embassy-nrf/src/gpiote.rs +++ b/embassy-nrf/src/gpiote.rs @@ -13,6 +13,10 @@ use crate::interrupt::InterruptExt; use crate::ppi::{Event, Task}; use crate::{interrupt, pac, peripherals}; +#[cfg(feature = "nrf51")] +/// Amount of GPIOTE channels in the chip. +const CHANNEL_COUNT: usize = 4; +#[cfg(not(feature = "_nrf51"))] /// Amount of GPIOTE channels in the chip. const CHANNEL_COUNT: usize = 8; @@ -61,16 +65,20 @@ fn regs() -> &'static pac::gpiote::RegisterBlock { } pub(crate) fn init(irq_prio: crate::interrupt::Priority) { - #[cfg(any(feature = "nrf52833", feature = "nrf52840"))] - let ports = unsafe { &[&*pac::P0::ptr(), &*pac::P1::ptr()] }; - #[cfg(not(any(feature = "nrf52833", feature = "nrf52840")))] - let ports = unsafe { &[&*pac::P0::ptr()] }; + // no latched GPIO detect in nrf51. + #[cfg(not(feature = "_nrf51"))] + { + #[cfg(any(feature = "nrf52833", feature = "nrf52840"))] + let ports = unsafe { &[&*pac::P0::ptr(), &*pac::P1::ptr()] }; + #[cfg(not(any(feature = "_nrf51", feature = "nrf52833", feature = "nrf52840")))] + let ports = unsafe { &[&*pac::P0::ptr()] }; - for &p in ports { - // Enable latched detection - p.detectmode.write(|w| w.detectmode().ldetect()); - // Clear latch - p.latch.write(|w| unsafe { w.bits(0xFFFFFFFF) }) + for &p in ports { + // Enable latched detection + p.detectmode.write(|w| w.detectmode().ldetect()); + // Clear latch + p.latch.write(|w| unsafe { w.bits(0xFFFFFFFF) }) + } } // Enable interrupts @@ -78,7 +86,7 @@ pub(crate) fn init(irq_prio: crate::interrupt::Priority) { let irq = interrupt::GPIOTE0; #[cfg(any(feature = "nrf5340-app-ns", feature = "nrf9160-ns"))] let irq = interrupt::GPIOTE1; - #[cfg(any(feature = "_nrf52", feature = "nrf5340-net"))] + #[cfg(any(feature = "_nrf51", feature = "_nrf52", feature = "nrf5340-net"))] let irq = interrupt::GPIOTE; irq.unpend(); @@ -103,7 +111,7 @@ fn GPIOTE1() { unsafe { handle_gpiote_interrupt() }; } -#[cfg(any(feature = "_nrf52", feature = "nrf5340-net"))] +#[cfg(any(feature = "_nrf51", feature = "_nrf52", feature = "nrf5340-net"))] #[cfg(feature = "rt")] #[interrupt] fn GPIOTE() { @@ -125,9 +133,29 @@ unsafe fn handle_gpiote_interrupt() { #[cfg(any(feature = "nrf52833", feature = "nrf52840"))] let ports = &[&*pac::P0::ptr(), &*pac::P1::ptr()]; - #[cfg(not(any(feature = "nrf52833", feature = "nrf52840")))] + #[cfg(not(any(feature = "_nrf51", feature = "nrf52833", feature = "nrf52840")))] let ports = &[&*pac::P0::ptr()]; + #[cfg(feature = "_nrf51")] + let ports = unsafe { &[&*pac::GPIO::ptr()] }; + #[cfg(feature = "_nrf51")] + for (port, &p) in ports.iter().enumerate() { + let inp = p.in_.read().bits(); + for pin in 0..32 { + let fired = match p.pin_cnf[pin as usize].read().sense().variant() { + Some(pac::gpio::pin_cnf::SENSE_A::HIGH) => inp & (1 << pin) != 0, + Some(pac::gpio::pin_cnf::SENSE_A::LOW) => inp & (1 << pin) == 0, + _ => false, + }; + + if fired { + PORT_WAKERS[port * 32 + pin as usize].wake(); + p.pin_cnf[pin as usize].modify(|_, w| w.sense().disabled()); + } + } + } + + #[cfg(not(feature = "_nrf51"))] for (port, &p) in ports.iter().enumerate() { let bits = p.latch.read().bits(); for pin in BitIter(bits) { @@ -156,12 +184,12 @@ impl Iterator for BitIter { } /// GPIOTE channel driver in input mode -pub struct InputChannel<'d, C: Channel, T: GpioPin> { - ch: PeripheralRef<'d, C>, - pin: Input<'d, T>, +pub struct InputChannel<'d> { + ch: PeripheralRef<'d, AnyChannel>, + pin: Input<'d>, } -impl<'d, C: Channel, T: GpioPin> Drop for InputChannel<'d, C, T> { +impl<'d> Drop for InputChannel<'d> { fn drop(&mut self) { let g = regs(); let num = self.ch.number(); @@ -170,9 +198,9 @@ impl<'d, C: Channel, T: GpioPin> Drop for InputChannel<'d, C, T> { } } -impl<'d, C: Channel, T: GpioPin> InputChannel<'d, C, T> { +impl<'d> InputChannel<'d> { /// Create a new GPIOTE input channel driver. - pub fn new(ch: impl Peripheral<P = C> + 'd, pin: Input<'d, T>, polarity: InputChannelPolarity) -> Self { + pub fn new(ch: impl Peripheral<P = impl Channel> + 'd, pin: Input<'d>, polarity: InputChannelPolarity) -> Self { into_ref!(ch); let g = regs(); @@ -195,7 +223,7 @@ impl<'d, C: Channel, T: GpioPin> InputChannel<'d, C, T> { g.events_in[num].reset(); - InputChannel { ch, pin } + InputChannel { ch: ch.map_into(), pin } } /// Asynchronously wait for an event in this channel. @@ -227,12 +255,12 @@ 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: PeripheralRef<'d, C>, - _pin: Output<'d, T>, +pub struct OutputChannel<'d> { + ch: PeripheralRef<'d, AnyChannel>, + _pin: Output<'d>, } -impl<'d, C: Channel, T: GpioPin> Drop for OutputChannel<'d, C, T> { +impl<'d> Drop for OutputChannel<'d> { fn drop(&mut self) { let g = regs(); let num = self.ch.number(); @@ -241,9 +269,9 @@ impl<'d, C: Channel, T: GpioPin> Drop for OutputChannel<'d, C, T> { } } -impl<'d, C: Channel, T: GpioPin> OutputChannel<'d, C, T> { +impl<'d> OutputChannel<'d> { /// Create a new GPIOTE output channel driver. - pub fn new(ch: impl Peripheral<P = C> + 'd, pin: Output<'d, T>, polarity: OutputChannelPolarity) -> Self { + pub fn new(ch: impl Peripheral<P = impl Channel> + 'd, pin: Output<'d>, polarity: OutputChannelPolarity) -> Self { into_ref!(ch); let g = regs(); let num = ch.number(); @@ -267,7 +295,10 @@ impl<'d, C: Channel, T: GpioPin> OutputChannel<'d, C, T> { unsafe { w.psel().bits(pin.pin.pin.pin()) } }); - OutputChannel { ch, _pin: pin } + OutputChannel { + ch: ch.map_into(), + _pin: pin, + } } /// Triggers the OUT task (does the action as configured with task_out_polarity, defaults to Toggle). @@ -348,7 +379,7 @@ impl<'a> Future for PortInputFuture<'a> { } } -impl<'d, T: GpioPin> Input<'d, T> { +impl<'d> Input<'d> { /// 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 @@ -375,7 +406,7 @@ impl<'d, T: GpioPin> Input<'d, T> { } } -impl<'d, T: GpioPin> Flex<'d, T> { +impl<'d> Flex<'d> { /// 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()); @@ -420,7 +451,7 @@ mod sealed { /// GPIOTE channel trait. /// /// Implemented by all GPIOTE channels. -pub trait Channel: sealed::Channel + Sized { +pub trait Channel: sealed::Channel + Into<AnyChannel> + Sized + 'static { /// Get the channel number. fn number(&self) -> usize; @@ -460,6 +491,12 @@ macro_rules! impl_channel { $number as usize } } + + impl From<peripherals::$type> for AnyChannel { + fn from(val: peripherals::$type) -> Self { + Channel::degrade(val) + } + } }; } @@ -467,9 +504,13 @@ impl_channel!(GPIOTE_CH0, 0); impl_channel!(GPIOTE_CH1, 1); impl_channel!(GPIOTE_CH2, 2); impl_channel!(GPIOTE_CH3, 3); +#[cfg(not(feature = "nrf51"))] impl_channel!(GPIOTE_CH4, 4); +#[cfg(not(feature = "nrf51"))] impl_channel!(GPIOTE_CH5, 5); +#[cfg(not(feature = "nrf51"))] impl_channel!(GPIOTE_CH6, 6); +#[cfg(not(feature = "nrf51"))] impl_channel!(GPIOTE_CH7, 7); // ==================== @@ -477,7 +518,7 @@ impl_channel!(GPIOTE_CH7, 7); mod eh02 { use super::*; - impl<'d, C: Channel, T: GpioPin> embedded_hal_02::digital::v2::InputPin for InputChannel<'d, C, T> { + impl<'d> embedded_hal_02::digital::v2::InputPin for InputChannel<'d> { type Error = Infallible; fn is_high(&self) -> Result<bool, Self::Error> { @@ -490,11 +531,11 @@ mod eh02 { } } -impl<'d, C: Channel, T: GpioPin> embedded_hal_1::digital::ErrorType for InputChannel<'d, C, T> { +impl<'d> embedded_hal_1::digital::ErrorType for InputChannel<'d> { type Error = Infallible; } -impl<'d, C: Channel, T: GpioPin> embedded_hal_1::digital::InputPin for InputChannel<'d, C, T> { +impl<'d> embedded_hal_1::digital::InputPin for InputChannel<'d> { fn is_high(&mut self) -> Result<bool, Self::Error> { Ok(self.pin.is_high()) } @@ -504,7 +545,7 @@ impl<'d, C: Channel, T: GpioPin> embedded_hal_1::digital::InputPin for InputChan } } -impl<'d, T: GpioPin> embedded_hal_async::digital::Wait for Input<'d, T> { +impl<'d> embedded_hal_async::digital::Wait for Input<'d> { async fn wait_for_high(&mut self) -> Result<(), Self::Error> { Ok(self.wait_for_high().await) } @@ -526,7 +567,7 @@ impl<'d, T: GpioPin> embedded_hal_async::digital::Wait for Input<'d, T> { } } -impl<'d, T: GpioPin> embedded_hal_async::digital::Wait for Flex<'d, T> { +impl<'d> embedded_hal_async::digital::Wait for Flex<'d> { async fn wait_for_high(&mut self) -> Result<(), Self::Error> { Ok(self.wait_for_high().await) } diff --git a/embassy-nrf/src/lib.rs b/embassy-nrf/src/lib.rs index d9c92a76d..718f229a3 100644 --- a/embassy-nrf/src/lib.rs +++ b/embassy-nrf/src/lib.rs @@ -40,10 +40,16 @@ pub(crate) mod util; #[cfg(feature = "_time-driver")] mod time_driver; +#[cfg(not(feature = "nrf51"))] pub mod buffered_uarte; pub mod gpio; #[cfg(feature = "gpiote")] pub mod gpiote; + +// TODO: tested on other chips +#[cfg(not(any(feature = "_nrf9160", feature = "_nrf5340-app")))] +pub mod radio; + #[cfg(any(feature = "nrf52832", feature = "nrf52833", feature = "nrf52840"))] pub mod i2s; pub mod nvmc; @@ -58,7 +64,12 @@ pub mod nvmc; ))] pub mod pdm; pub mod ppi; -#[cfg(not(any(feature = "nrf52805", feature = "nrf52820", feature = "_nrf5340-net")))] +#[cfg(not(any( + feature = "nrf51", + feature = "nrf52805", + feature = "nrf52820", + feature = "_nrf5340-net" +)))] pub mod pwm; #[cfg(not(any(feature = "nrf51", feature = "_nrf9160", feature = "_nrf5340-net")))] pub mod qdec; @@ -66,15 +77,20 @@ pub mod qdec; pub mod qspi; #[cfg(not(any(feature = "_nrf5340-app", feature = "_nrf9160")))] pub mod rng; -#[cfg(not(any(feature = "nrf52820", feature = "_nrf5340-net")))] +#[cfg(not(any(feature = "nrf51", feature = "nrf52820", feature = "_nrf5340-net")))] pub mod saadc; +#[cfg(not(feature = "nrf51"))] pub mod spim; +#[cfg(not(feature = "nrf51"))] pub mod spis; #[cfg(not(any(feature = "_nrf5340", feature = "_nrf9160")))] pub mod temp; pub mod timer; +#[cfg(not(feature = "nrf51"))] pub mod twim; +#[cfg(not(feature = "nrf51"))] pub mod twis; +#[cfg(not(feature = "nrf51"))] pub mod uarte; #[cfg(any( feature = "_nrf5340-app", @@ -87,6 +103,7 @@ pub mod usb; pub mod wdt; // This mod MUST go last, so that it sees all the `impl_foo!` macros +#[cfg_attr(feature = "nrf51", path = "chips/nrf51.rs")] #[cfg_attr(feature = "nrf52805", path = "chips/nrf52805.rs")] #[cfg_attr(feature = "nrf52810", path = "chips/nrf52810.rs")] #[cfg_attr(feature = "nrf52811", path = "chips/nrf52811.rs")] @@ -324,6 +341,7 @@ mod consts { pub const APPROTECT_DISABLED: u32 = 0x0000_005a; } +#[cfg(not(feature = "nrf51"))] #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] enum WriteResult { @@ -335,10 +353,12 @@ enum WriteResult { Failed, } +#[cfg(not(feature = "nrf51"))] unsafe fn uicr_write(address: *mut u32, value: u32) -> WriteResult { uicr_write_masked(address, value, 0xFFFF_FFFF) } +#[cfg(not(feature = "nrf51"))] 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 { @@ -371,9 +391,11 @@ pub fn init(config: config::Config) -> Peripherals { // before doing anything important. let peripherals = Peripherals::take(); + #[allow(unused_mut)] let mut needs_reset = false; // Setup debug protection. + #[cfg(not(feature = "nrf51"))] match config.debug { config::Debug::Allowed => { #[cfg(feature = "_nrf52")] @@ -489,7 +511,7 @@ pub fn init(config: config::Config) -> Peripherals { } // Configure LFCLK. - #[cfg(not(any(feature = "_nrf5340", feature = "_nrf9160")))] + #[cfg(not(any(feature = "nrf51", feature = "_nrf5340", feature = "_nrf9160")))] match config.lfclk_source { config::LfclkSource::InternalRC => r.lfclksrc.write(|w| w.src().rc()), config::LfclkSource::Synthesized => r.lfclksrc.write(|w| w.src().synth()), diff --git a/embassy-nrf/src/nvmc.rs b/embassy-nrf/src/nvmc.rs index de840b886..4f9eda167 100644 --- a/embassy-nrf/src/nvmc.rs +++ b/embassy-nrf/src/nvmc.rs @@ -160,7 +160,7 @@ impl<'d> NorFlash for Nvmc<'d> { if offset as usize + bytes.len() > FLASH_SIZE { return Err(Error::OutOfBounds); } - if offset as usize % 4 != 0 || bytes.len() as usize % 4 != 0 { + if offset as usize % 4 != 0 || bytes.len() % 4 != 0 { return Err(Error::Unaligned); } diff --git a/embassy-nrf/src/pdm.rs b/embassy-nrf/src/pdm.rs index 24fa29a4a..754d38310 100644 --- a/embassy-nrf/src/pdm.rs +++ b/embassy-nrf/src/pdm.rs @@ -1,4 +1,4 @@ -//! Pulse Density Modulation (PDM) mirophone driver. +//! Pulse Density Modulation (PDM) mirophone driver #![macro_use] @@ -26,7 +26,7 @@ pub use crate::pac::pdm::pdmclkctrl::FREQ_A as Frequency; pub use crate::pac::pdm::ratio::RATIO_A as Ratio; use crate::{interrupt, Peripheral}; -/// Interrupt handler. +/// Interrupt handler pub struct InterruptHandler<T: Instance> { _phantom: PhantomData<T>, } @@ -56,12 +56,12 @@ pub struct Pdm<'d, T: Instance> { _peri: PeripheralRef<'d, T>, } -/// PDM error. +/// PDM error #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum Error { - /// Buffer is too long. + /// Buffer is too long BufferTooLong, /// Buffer is empty BufferZeroLength, @@ -75,13 +75,13 @@ 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 -/// next. For example, if the sampler has stopped then the Pdm implementation -/// can then tear down its infrastructure. +/// next. For example, if the sampler has stopped then the PDM implementation +/// can then tear down its infrastructure #[derive(PartialEq)] pub enum SamplerState { - /// The sampler processed the samples and is ready for more. + /// The sampler processed the samples and is ready for more Sampled, - /// The sampler is done processing samples. + /// The sampler is done processing samples Stopped, } @@ -145,15 +145,12 @@ impl<'d, T: Instance> Pdm<'d, T> { } 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::<u8>() - .clamp(0, 0x50); - let gain_right = gain_right - .saturating_add(I7F1::from_bits(40)) - .saturating_to_num::<u8>() - .clamp(0, 0x50); - + let gain_to_bits = |gain: I7F1| -> u8 { + let gain = gain.saturating_add(I7F1::from_bits(0x28)).to_bits().clamp(0, 0x50); + unsafe { core::mem::transmute(gain) } + }; + let gain_left = gain_to_bits(gain_left); + let gain_right = gain_to_bits(gain_right); r.gainl.write(|w| unsafe { w.gainl().bits(gain_left) }); r.gainr.write(|w| unsafe { w.gainr().bits(gain_right) }); } @@ -163,12 +160,12 @@ impl<'d, T: Instance> Pdm<'d, T> { Self::_set_gain(T::regs(), gain_left, gain_right) } - /// Start sampling microphon data into a dummy buffer - /// Usefull to start the microphon and keep it active between recording samples + /// Start sampling microphone data into a dummy buffer. + /// Useful to start the microphone 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 + // start dummy sampling because microphone needs some setup time r.sample .ptr .write(|w| unsafe { w.sampleptr().bits(DUMMY_BUFFER.as_ptr() as u32) }); @@ -179,16 +176,16 @@ impl<'d, T: Instance> Pdm<'d, T> { r.tasks_start.write(|w| unsafe { w.bits(1) }); } - /// Stop sampling microphon data inta a dummy buffer + /// Stop sampling microphone 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(); } - /// Sample data into the given buffer. + /// Sample data into the given buffer pub async fn sample(&mut self, buffer: &mut [i16]) -> Result<(), Error> { - if buffer.len() == 0 { + if buffer.is_empty() { return Err(Error::BufferZeroLength); } if buffer.len() > EASY_DMA_SIZE { @@ -303,7 +300,7 @@ impl<'d, T: Instance> Pdm<'d, T> { }); // Don't reorder the start event before the previous writes. Hopefully self - // wouldn't happen anyway. + // wouldn't happen anyway compiler_fence(Ordering::SeqCst); r.tasks_start.write(|w| unsafe { w.bits(1) }); @@ -314,11 +311,11 @@ impl<'d, T: Instance> Pdm<'d, T> { let drop = OnDrop::new(|| { r.tasks_stop.write(|w| unsafe { w.bits(1) }); - // N.B. It would be better if this were async, but Drop only support sync code. + // 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 events and complete when the sampler indicates it has had enough. + // Wait for events and complete when the sampler indicates it has had enough poll_fn(|cx| { let r = T::regs(); @@ -331,7 +328,7 @@ impl<'d, T: Instance> Pdm<'d, T> { r.intenset.write(|w| w.end().set()); if !done { - // Discard the last buffer after the user requested a stop. + // Discard the last buffer after the user requested a stop if sampler(&bufs[current_buffer]) == SamplerState::Sampled { let next_buffer = 1 - current_buffer; current_buffer = next_buffer; @@ -405,7 +402,7 @@ impl Default for Config { } } -/// PDM operation mode. +/// PDM operation mode #[derive(PartialEq)] pub enum OperationMode { /// Mono (1 channel) @@ -476,9 +473,9 @@ pub(crate) mod sealed { } } -/// PDM peripheral instance. +/// PDM peripheral instance pub trait Instance: Peripheral<P = Self> + sealed::Instance + 'static + Send { - /// Interrupt for this peripheral. + /// Interrupt for this peripheral type Interrupt: interrupt::typelevel::Interrupt; } diff --git a/embassy-nrf/src/ppi/mod.rs b/embassy-nrf/src/ppi/mod.rs index 5b4a64388..f5764b8b7 100644 --- a/embassy-nrf/src/ppi/mod.rs +++ b/embassy-nrf/src/ppi/mod.rs @@ -284,6 +284,7 @@ impl ConfigurableChannel for AnyConfigurableChannel { } } +#[cfg(not(feature = "nrf51"))] macro_rules! impl_ppi_channel { ($type:ident, $number:expr) => { impl crate::ppi::sealed::Channel for peripherals::$type {} diff --git a/embassy-nrf/src/ppi/ppi.rs b/embassy-nrf/src/ppi/ppi.rs index 3e9e9fc81..8ff52ece3 100644 --- a/embassy-nrf/src/ppi/ppi.rs +++ b/embassy-nrf/src/ppi/ppi.rs @@ -1,6 +1,6 @@ use embassy_hal_internal::into_ref; -use super::{Channel, ConfigurableChannel, Event, Ppi, StaticChannel, Task}; +use super::{Channel, ConfigurableChannel, Event, Ppi, Task}; use crate::{pac, Peripheral}; impl<'d> Task<'d> { @@ -19,7 +19,7 @@ pub(crate) fn regs() -> &'static pac::ppi::RegisterBlock { } #[cfg(not(feature = "nrf51"))] // Not for nrf51 because of the fork task -impl<'d, C: StaticChannel> Ppi<'d, C, 0, 1> { +impl<'d, C: super::StaticChannel> Ppi<'d, C, 0, 1> { /// Configure PPI channel to trigger `task`. pub fn new_zero_to_one(ch: impl Peripheral<P = C> + 'd, task: Task) -> Self { into_ref!(ch); @@ -84,6 +84,7 @@ impl<'d, C: Channel, const EVENT_COUNT: usize, const TASK_COUNT: usize> Drop for let n = self.ch.number(); r.ch[n].eep.write(|w| unsafe { w.bits(0) }); r.ch[n].tep.write(|w| unsafe { w.bits(0) }); + #[cfg(not(feature = "nrf51"))] r.fork[n].tep.write(|w| unsafe { w.bits(0) }); } } diff --git a/embassy-nrf/src/pwm.rs b/embassy-nrf/src/pwm.rs index 2f0397632..833370d4b 100644 --- a/embassy-nrf/src/pwm.rs +++ b/embassy-nrf/src/pwm.rs @@ -47,6 +47,8 @@ pub enum Error { } const MAX_SEQUENCE_LEN: usize = 32767; +/// The used pwm clock frequency +pub const PWM_CLK_HZ: u32 = 16_000_000; impl<'d, T: Instance> SequencePwm<'d, T> { /// Create a new 1-channel PWM @@ -442,7 +444,7 @@ impl<'d, 's, T: Instance> Sequencer<'d, 's, T> { return Err(Error::SequenceTimesAtLeastOne); } - let _ = self.stop(); + self.stop(); let r = T::regs(); @@ -505,7 +507,7 @@ impl<'d, 's, T: Instance> Sequencer<'d, 's, T> { impl<'d, 's, T: Instance> Drop for Sequencer<'d, 's, T> { fn drop(&mut self) { - let _ = self.stop(); + self.stop(); } } @@ -695,7 +697,7 @@ impl<'d, T: Instance> SimplePwm<'d, T> { // Enable r.enable.write(|w| w.enable().enabled()); - r.seq0.ptr.write(|w| unsafe { w.bits((&pwm.duty).as_ptr() as u32) }); + r.seq0.ptr.write(|w| unsafe { w.bits((pwm.duty).as_ptr() as u32) }); r.seq0.cnt.write(|w| unsafe { w.bits(4) }); r.seq0.refresh.write(|w| unsafe { w.bits(0) }); @@ -713,6 +715,13 @@ impl<'d, T: Instance> SimplePwm<'d, T> { pwm } + /// Returns the enable state of the pwm counter + #[inline(always)] + pub fn is_enabled(&self) -> bool { + let r = T::regs(); + r.enable.read().enable().bit_is_set() + } + /// Enables the PWM generator. #[inline(always)] pub fn enable(&self) { @@ -727,6 +736,11 @@ impl<'d, T: Instance> SimplePwm<'d, T> { r.enable.write(|w| w.enable().disabled()); } + /// Returns the current duty of the channel + pub fn duty(&self, channel: usize) -> u16 { + self.duty[channel] + } + /// Sets duty cycle (15 bit) for a PWM channel. pub fn set_duty(&mut self, channel: usize, duty: u16) { let r = T::regs(); @@ -734,7 +748,7 @@ impl<'d, T: Instance> SimplePwm<'d, T> { self.duty[channel] = duty & 0x7FFF; // reload ptr in case self was moved - r.seq0.ptr.write(|w| unsafe { w.bits((&self.duty).as_ptr() as u32) }); + r.seq0.ptr.write(|w| unsafe { w.bits((self.duty).as_ptr() as u32) }); // defensive before seqstart compiler_fence(Ordering::SeqCst); @@ -746,7 +760,9 @@ impl<'d, T: Instance> SimplePwm<'d, T> { // defensive wait until waveform is loaded after seqstart so set_duty // can't be called again while dma is still reading - while r.events_seqend[0].read().bits() == 0 {} + if self.is_enabled() { + while r.events_seqend[0].read().bits() == 0 {} + } } /// Sets the PWM clock prescaler. @@ -788,7 +804,7 @@ impl<'d, T: Instance> SimplePwm<'d, T> { /// Sets the PWM output frequency. #[inline(always)] pub fn set_period(&self, freq: u32) { - let clk = 16_000_000u32 >> (self.prescaler() as u8); + let clk = PWM_CLK_HZ >> (self.prescaler() as u8); let duty = clk / freq; self.set_max_duty(duty.min(32767) as u16); } @@ -796,7 +812,7 @@ impl<'d, T: Instance> SimplePwm<'d, T> { /// Returns the PWM output frequency. #[inline(always)] pub fn period(&self) -> u32 { - let clk = 16_000_000u32 >> (self.prescaler() as u8); + let clk = PWM_CLK_HZ >> (self.prescaler() as u8); let max_duty = self.max_duty() as u32; clk / max_duty } diff --git a/embassy-nrf/src/qdec.rs b/embassy-nrf/src/qdec.rs index 2aa50a2ba..9455ec925 100644 --- a/embassy-nrf/src/qdec.rs +++ b/embassy-nrf/src/qdec.rs @@ -172,18 +172,17 @@ impl<'d, T: Instance> Qdec<'d, T> { t.intenset.write(|w| w.reportrdy().set()); unsafe { t.tasks_readclracc.write(|w| w.bits(1)) }; - let value = poll_fn(|cx| { + poll_fn(|cx| { T::state().waker.register(cx.waker()); if t.events_reportrdy.read().bits() == 0 { - return Poll::Pending; + Poll::Pending } else { t.events_reportrdy.reset(); let acc = t.accread.read().bits(); Poll::Ready(acc as i16) } }) - .await; - value + .await } } diff --git a/embassy-nrf/src/qspi.rs b/embassy-nrf/src/qspi.rs index 8eec09c96..4134a4c87 100755 --- a/embassy-nrf/src/qspi.rs +++ b/embassy-nrf/src/qspi.rs @@ -402,7 +402,7 @@ impl<'d, T: Instance> Qspi<'d, T> { /// a raw bus, not with flash memory. pub async fn read_raw(&mut self, address: u32, data: &mut [u8]) -> Result<(), Error> { // Avoid blocking_wait_ready() blocking forever on zero-length buffers. - if data.len() == 0 { + if data.is_empty() { return Ok(()); } @@ -423,7 +423,7 @@ impl<'d, T: Instance> Qspi<'d, T> { /// a raw bus, not with flash memory. pub async fn write_raw(&mut self, address: u32, data: &[u8]) -> Result<(), Error> { // Avoid blocking_wait_ready() blocking forever on zero-length buffers. - if data.len() == 0 { + if data.is_empty() { return Ok(()); } @@ -444,7 +444,7 @@ impl<'d, T: Instance> Qspi<'d, T> { /// a raw bus, not with flash memory. pub fn blocking_read_raw(&mut self, address: u32, data: &mut [u8]) -> Result<(), Error> { // Avoid blocking_wait_ready() blocking forever on zero-length buffers. - if data.len() == 0 { + if data.is_empty() { return Ok(()); } @@ -460,7 +460,7 @@ impl<'d, T: Instance> Qspi<'d, T> { /// a raw bus, not with flash memory. pub fn blocking_write_raw(&mut self, address: u32, data: &[u8]) -> Result<(), Error> { // Avoid blocking_wait_ready() blocking forever on zero-length buffers. - if data.len() == 0 { + if data.is_empty() { return Ok(()); } diff --git a/embassy-nrf/src/radio/ble.rs b/embassy-nrf/src/radio/ble.rs new file mode 100644 index 000000000..93003fb19 --- /dev/null +++ b/embassy-nrf/src/radio/ble.rs @@ -0,0 +1,417 @@ +//! Radio driver implementation focused on Bluetooth Low-Energy transmission. + +use core::future::poll_fn; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; + +use embassy_hal_internal::drop::OnDrop; +use embassy_hal_internal::{into_ref, PeripheralRef}; +pub use pac::radio::mode::MODE_A as Mode; +#[cfg(not(feature = "nrf51"))] +use pac::radio::pcnf0::PLEN_A as PreambleLength; + +use crate::interrupt::typelevel::Interrupt; +use crate::radio::*; +pub use crate::radio::{Error, TxPower}; +use crate::util::slice_in_ram_or; + +/// Radio driver. +pub struct Radio<'d, T: Instance> { + _p: PeripheralRef<'d, T>, +} + +impl<'d, T: Instance> Radio<'d, T> { + /// Create a new radio driver. + pub fn new( + radio: impl Peripheral<P = T> + 'd, + _irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd, + ) -> Self { + into_ref!(radio); + + let r = T::regs(); + + r.pcnf1.write(|w| unsafe { + // It is 0 bytes long in a standard BLE packet + w.statlen() + .bits(0) + // MaxLen configures the maximum packet payload plus add-on size in + // number of bytes that can be transmitted or received by the RADIO. This feature can be used to ensure + // that the RADIO does not overwrite, or read beyond, the RAM assigned to the packet payload. This means + // that if the packet payload length defined by PCNF1.STATLEN and the LENGTH field in the packet specifies a + // packet larger than MAXLEN, the payload will be truncated at MAXLEN + // + // To simplify the implementation, It is setted as the maximum value + // and the length of the packet is controlled only by the LENGTH field in the packet + .maxlen() + .bits(255) + // Configure the length of the address field in the packet + // The prefix after the address fields is always appended, so is always 1 byte less than the size of the address + // The base address is truncated from the least significant byte if the BALEN is less than 4 + // + // BLE address is always 4 bytes long + .balen() + .bits(3) // 3 bytes base address (+ 1 prefix); + // Configure the endianess + // For BLE is always little endian (LSB first) + .endian() + .little() + // Data whitening is used to avoid long sequences of zeros or + // ones, e.g., 0b0000000 or 0b1111111, in the data bit stream. + // The whitener and de-whitener are defined the same way, + // using a 7-bit linear feedback shift register with the + // polynomial x7 + x4 + 1. + // + // In BLE Whitening shall be applied on the PDU and CRC of all + // Link Layer packets and is performed after the CRC generation + // in the transmitter. No other parts of the packets are whitened. + // De-whitening is performed before the CRC checking in the receiver + // Before whitening or de-whitening, the shift register should be + // initialized based on the channel index. + .whiteen() + .set_bit() + }); + + // Configure CRC + r.crccnf.write(|w| { + // In BLE the CRC shall be calculated on the PDU of all Link Layer + // packets (even if the packet is encrypted). + // It skips the address field + w.skipaddr() + .skip() + // In BLE 24-bit CRC = 3 bytes + .len() + .three() + }); + + // Ch map between 2400 MHZ .. 2500 MHz + // All modes use this range + #[cfg(not(feature = "nrf51"))] + r.frequency.write(|w| w.map().default()); + + // Configure shortcuts to simplify and speed up sending and receiving packets. + r.shorts.write(|w| { + // start transmission/recv immediately after ramp-up + // disable radio when transmission/recv is done + w.ready_start().enabled().end_disable().enabled() + }); + + // Enable NVIC interrupt + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + Self { _p: radio } + } + + fn state(&self) -> RadioState { + super::state(T::regs()) + } + + /// Set the radio mode + /// + /// The radio must be disabled before calling this function + pub fn set_mode(&mut self, mode: Mode) { + assert!(self.state() == RadioState::DISABLED); + + let r = T::regs(); + r.mode.write(|w| w.mode().variant(mode)); + + #[cfg(not(feature = "nrf51"))] + r.pcnf0.write(|w| { + w.plen().variant(match mode { + Mode::BLE_1MBIT => PreambleLength::_8BIT, + Mode::BLE_2MBIT => PreambleLength::_16BIT, + #[cfg(any( + feature = "nrf52811", + feature = "nrf52820", + feature = "nrf52833", + feature = "nrf52840", + feature = "_nrf5340-net" + ))] + Mode::BLE_LR125KBIT | Mode::BLE_LR500KBIT => PreambleLength::LONG_RANGE, + _ => unimplemented!(), + }) + }); + } + + /// Set the header size changing the S1's len field + /// + /// The radio must be disabled before calling this function + pub fn set_header_expansion(&mut self, use_s1_field: bool) { + assert!(self.state() == RadioState::DISABLED); + + let r = T::regs(); + + // s1 len in bits + let s1len: u8 = match use_s1_field { + false => 0, + true => 8, + }; + + r.pcnf0.write(|w| unsafe { + w + // Configure S0 to 1 byte length, this will represent the Data/Adv header flags + .s0len() + .set_bit() + // Configure the length (in bits) field to 1 byte length, this will represent the length of the payload + // and also be used to know how many bytes to read/write from/to the buffer + .lflen() + .bits(8) + // Configure the lengh (in bits) of bits in the S1 field. It could be used to represent the CTEInfo for data packages in BLE. + .s1len() + .bits(s1len) + }); + } + + /// Set initial data whitening value + /// Data whitening is used to avoid long sequences of zeros or ones, e.g., 0b0000000 or 0b1111111, in the data bit stream + /// On BLE the initial value is the channel index | 0x40 + /// + /// The radio must be disabled before calling this function + pub fn set_whitening_init(&mut self, whitening_init: u8) { + assert!(self.state() == RadioState::DISABLED); + + let r = T::regs(); + + r.datawhiteiv.write(|w| unsafe { w.datawhiteiv().bits(whitening_init) }); + } + + /// Set the central frequency to be used + /// It should be in the range 2400..2500 + /// + /// [The radio must be disabled before calling this function](https://devzone.nordicsemi.com/f/nordic-q-a/15829/radio-frequency-change) + pub fn set_frequency(&mut self, frequency: u32) { + assert!(self.state() == RadioState::DISABLED); + assert!((2400..=2500).contains(&frequency)); + + let r = T::regs(); + + r.frequency + .write(|w| unsafe { w.frequency().bits((frequency - 2400) as u8) }); + } + + /// Set the acess address + /// This address is always constants for advertising + /// And a random value generate on each connection + /// It is used to filter the packages + /// + /// The radio must be disabled before calling this function + pub fn set_access_address(&mut self, access_address: u32) { + assert!(self.state() == RadioState::DISABLED); + + let r = T::regs(); + + // Configure logical address + // The byte ordering on air is always least significant byte first for the address + // So for the address 0xAA_BB_CC_DD, the address on air will be DD CC BB AA + // The package order is BASE, PREFIX so BASE=0xBB_CC_DD and PREFIX=0xAA + r.prefix0 + .write(|w| unsafe { w.ap0().bits((access_address >> 24) as u8) }); + + // The base address is truncated from the least significant byte (because the BALEN is less than 4) + // So it shifts the address to the right + r.base0.write(|w| unsafe { w.bits(access_address << 8) }); + + // Don't match tx address + r.txaddress.write(|w| unsafe { w.txaddress().bits(0) }); + + // Match on logical address + // This config only filter the packets by the address, + // so only packages send to the previous address + // will finish the reception (TODO: check the explanation) + r.rxaddresses.write(|w| { + w.addr0() + .enabled() + .addr1() + .enabled() + .addr2() + .enabled() + .addr3() + .enabled() + .addr4() + .enabled() + }); + } + + /// Set the CRC polynomial + /// It only uses the 24 least significant bits + /// + /// The radio must be disabled before calling this function + pub fn set_crc_poly(&mut self, crc_poly: u32) { + assert!(self.state() == RadioState::DISABLED); + + let r = T::regs(); + + r.crcpoly.write(|w| unsafe { + // Configure the CRC polynomial + // Each term in the CRC polynomial is mapped to a bit in this + // register which index corresponds to the term's exponent. + // The least significant term/bit is hard-wired internally to + // 1, and bit number 0 of the register content is ignored by + // the hardware. The following example is for an 8 bit CRC + // polynomial: x8 + x7 + x3 + x2 + 1 = 1 1000 1101 . + w.crcpoly().bits(crc_poly & 0xFFFFFF) + }); + } + + /// Set the CRC init value + /// It only uses the 24 least significant bits + /// The CRC initial value varies depending of the PDU type + /// + /// The radio must be disabled before calling this function + pub fn set_crc_init(&mut self, crc_init: u32) { + assert!(self.state() == RadioState::DISABLED); + + let r = T::regs(); + + r.crcinit.write(|w| unsafe { w.crcinit().bits(crc_init & 0xFFFFFF) }); + } + + /// Set the radio tx power + /// + /// The radio must be disabled before calling this function + pub fn set_tx_power(&mut self, tx_power: TxPower) { + assert!(self.state() == RadioState::DISABLED); + + let r = T::regs(); + + r.txpower.write(|w| w.txpower().variant(tx_power)); + } + + /// Set buffer to read/write + /// + /// This method is unsound. You should guarantee that the buffer will live + /// for the life time of the transmission or if the buffer will be modified. + /// Also if the buffer is smaller than the packet length, the radio will + /// read/write memory out of the buffer bounds. + fn set_buffer(&mut self, buffer: &[u8]) -> Result<(), Error> { + slice_in_ram_or(buffer, Error::BufferNotInRAM)?; + + let r = T::regs(); + + // Here it consider that the length of the packet is + // correctly set in the buffer, otherwise it will send + // unowned regions of memory + let ptr = buffer.as_ptr(); + + // Configure the payload + r.packetptr.write(|w| unsafe { w.bits(ptr as u32) }); + + Ok(()) + } + + /// Send packet + /// If the length byte in the package is greater than the buffer length + /// the radio will read memory out of the buffer bounds + pub async fn transmit(&mut self, buffer: &[u8]) -> Result<(), Error> { + self.set_buffer(buffer)?; + + let r = T::regs(); + self.trigger_and_wait_end(move || { + // Initialize the transmission + // trace!("txen"); + + r.tasks_txen.write(|w| unsafe { w.bits(1) }); + }) + .await; + + Ok(()) + } + + /// Receive packet + /// If the length byte in the received package is greater than the buffer length + /// the radio will write memory out of the buffer bounds + pub async fn receive(&mut self, buffer: &mut [u8]) -> Result<(), Error> { + self.set_buffer(buffer)?; + + let r = T::regs(); + self.trigger_and_wait_end(move || { + // Initialize the transmission + // trace!("rxen"); + r.tasks_rxen.write(|w| unsafe { w.bits(1) }); + }) + .await; + + Ok(()) + } + + async fn trigger_and_wait_end(&mut self, trigger: impl FnOnce()) { + //self.trace_state(); + + let r = T::regs(); + let s = T::state(); + + // If the Future is dropped before the end of the transmission + // it disable the interrupt and stop the transmission + // to keep the state consistent + let drop = OnDrop::new(|| { + trace!("radio drop: stopping"); + + r.intenclr.write(|w| w.end().clear()); + r.events_end.reset(); + + r.tasks_stop.write(|w| unsafe { w.bits(1) }); + + // The docs don't explicitly mention any event to acknowledge the stop task + while r.events_end.read().bits() == 0 {} + + trace!("radio drop: stopped"); + }); + + // trace!("radio:enable interrupt"); + // Clear some remnant side-effects (TODO: check if this is necessary) + r.events_end.reset(); + + // Enable interrupt + r.intenset.write(|w| w.end().set()); + + compiler_fence(Ordering::SeqCst); + + // Trigger the transmission + trigger(); + // self.trace_state(); + + // On poll check if interrupt happen + poll_fn(|cx| { + s.event_waker.register(cx.waker()); + if r.events_end.read().bits() == 1 { + // trace!("radio:end"); + return core::task::Poll::Ready(()); + } + Poll::Pending + }) + .await; + + compiler_fence(Ordering::SeqCst); + r.events_disabled.reset(); // ACK + + // Everthing ends fine, so it disable the drop + drop.defuse(); + } + + /// Disable the radio + fn disable(&mut self) { + let r = T::regs(); + + compiler_fence(Ordering::SeqCst); + // If it is already disabled, do nothing + if self.state() != RadioState::DISABLED { + trace!("radio:disable"); + // Trigger the disable task + r.tasks_disable.write(|w| unsafe { w.bits(1) }); + + // Wait until the radio is disabled + while r.events_disabled.read().bits() == 0 {} + + compiler_fence(Ordering::SeqCst); + + // Acknowledge it + r.events_disabled.reset(); + } + } +} + +impl<'d, T: Instance> Drop for Radio<'d, T> { + fn drop(&mut self) { + self.disable(); + } +} diff --git a/embassy-nrf/src/radio/ieee802154.rs b/embassy-nrf/src/radio/ieee802154.rs new file mode 100644 index 000000000..298f8a574 --- /dev/null +++ b/embassy-nrf/src/radio/ieee802154.rs @@ -0,0 +1,546 @@ +//! IEEE 802.15.4 radio driver + +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; + +use embassy_hal_internal::drop::OnDrop; +use embassy_hal_internal::{into_ref, PeripheralRef}; + +use super::{state, Error, Instance, InterruptHandler, RadioState, TxPower}; +use crate::interrupt::typelevel::Interrupt; +use crate::interrupt::{self}; +use crate::Peripheral; + +/// Default (IEEE compliant) Start of Frame Delimiter +pub const DEFAULT_SFD: u8 = 0xA7; + +// TODO expose the other variants in `pac::CCAMODE_A` +/// Clear Channel Assessment method +pub enum Cca { + /// Carrier sense + CarrierSense, + /// Energy Detection / Energy Above Threshold + EnergyDetection { + /// Energy measurements above this value mean that the channel is assumed to be busy. + /// Note the measurement range is 0..0xFF - where 0 means that the received power was + /// less than 10 dB above the selected receiver sensitivity. This value is not given in dBm, + /// but can be converted. See the nrf52840 Product Specification Section 6.20.12.4 + /// for details. + ed_threshold: u8, + }, +} + +/// IEEE 802.15.4 radio driver. +pub struct Radio<'d, T: Instance> { + _p: PeripheralRef<'d, T>, + needs_enable: bool, +} + +impl<'d, T: Instance> Radio<'d, T> { + /// Create a new IEEE 802.15.4 radio driver. + pub fn new( + radio: impl Peripheral<P = T> + 'd, + _irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd, + ) -> Self { + into_ref!(radio); + + let r = T::regs(); + + // Disable and enable to reset peripheral + r.power.write(|w| w.power().disabled()); + r.power.write(|w| w.power().enabled()); + + // Enable 802.15.4 mode + r.mode.write(|w| w.mode().ieee802154_250kbit()); + // Configure CRC skip address + r.crccnf.write(|w| w.len().two().skipaddr().ieee802154()); + unsafe { + // Configure CRC polynomial and init + r.crcpoly.write(|w| w.crcpoly().bits(0x0001_1021)); + r.crcinit.write(|w| w.crcinit().bits(0)); + r.pcnf0.write(|w| { + // 8-bit on air length + w.lflen() + .bits(8) + // Zero bytes S0 field length + .s0len() + .clear_bit() + // Zero bytes S1 field length + .s1len() + .bits(0) + // Do not include S1 field in RAM if S1 length > 0 + .s1incl() + .clear_bit() + // Zero code Indicator length + .cilen() + .bits(0) + // 32-bit zero preamble + .plen() + ._32bit_zero() + // Include CRC in length + .crcinc() + .include() + }); + r.pcnf1.write(|w| { + // Maximum packet length + w.maxlen() + .bits(Packet::MAX_PSDU_LEN) + // Zero static length + .statlen() + .bits(0) + // Zero base address length + .balen() + .bits(0) + // Little-endian + .endian() + .clear_bit() + // Disable packet whitening + .whiteen() + .clear_bit() + }); + } + + // Enable NVIC interrupt + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + let mut radio = Self { + _p: radio, + needs_enable: false, + }; + + radio.set_sfd(DEFAULT_SFD); + radio.set_transmission_power(0); + radio.set_channel(11); + radio.set_cca(Cca::CarrierSense); + + radio + } + + /// Changes the radio channel + pub fn set_channel(&mut self, channel: u8) { + let r = T::regs(); + if channel < 11 || channel > 26 { + panic!("Bad 802.15.4 channel"); + } + let frequency_offset = (channel - 10) * 5; + self.needs_enable = true; + r.frequency + .write(|w| unsafe { w.frequency().bits(frequency_offset).map().default() }); + } + + /// Changes the Clear Channel Assessment method + pub fn set_cca(&mut self, cca: Cca) { + let r = T::regs(); + self.needs_enable = true; + match cca { + Cca::CarrierSense => r.ccactrl.write(|w| w.ccamode().carrier_mode()), + Cca::EnergyDetection { ed_threshold } => { + // "[ED] is enabled by first configuring the field CCAMODE=EdMode in CCACTRL + // and writing the CCAEDTHRES field to a chosen value." + r.ccactrl + .write(|w| unsafe { w.ccamode().ed_mode().ccaedthres().bits(ed_threshold) }); + } + } + } + + /// Changes the Start of Frame Delimiter (SFD) + pub fn set_sfd(&mut self, sfd: u8) { + let r = T::regs(); + r.sfd.write(|w| unsafe { w.sfd().bits(sfd) }); + } + + /// Clear interrupts + pub fn clear_all_interrupts(&mut self) { + let r = T::regs(); + r.intenclr.write(|w| unsafe { w.bits(0xffff_ffff) }); + } + + /// Changes the radio transmission power + pub fn set_transmission_power(&mut self, power: i8) { + let r = T::regs(); + self.needs_enable = true; + + let tx_power: TxPower = match power { + #[cfg(not(any(feature = "nrf52811", feature = "_nrf5340-net")))] + 8 => TxPower::POS8D_BM, + #[cfg(not(any(feature = "nrf52811", feature = "_nrf5340-net")))] + 7 => TxPower::POS7D_BM, + #[cfg(not(any(feature = "nrf52811", feature = "_nrf5340-net")))] + 6 => TxPower::POS6D_BM, + #[cfg(not(any(feature = "nrf52811", feature = "_nrf5340-net")))] + 5 => TxPower::POS5D_BM, + #[cfg(not(feature = "_nrf5340-net"))] + 4 => TxPower::POS4D_BM, + #[cfg(not(feature = "_nrf5340-net"))] + 3 => TxPower::POS3D_BM, + #[cfg(not(any(feature = "nrf52811", feature = "_nrf5340-net")))] + 2 => TxPower::POS2D_BM, + 0 => TxPower::_0D_BM, + #[cfg(feature = "_nrf5340-net")] + -1 => TxPower::NEG1D_BM, + #[cfg(feature = "_nrf5340-net")] + -2 => TxPower::NEG2D_BM, + #[cfg(feature = "_nrf5340-net")] + -3 => TxPower::NEG3D_BM, + -4 => TxPower::NEG4D_BM, + #[cfg(feature = "_nrf5340-net")] + -5 => TxPower::NEG5D_BM, + #[cfg(feature = "_nrf5340-net")] + -6 => TxPower::NEG6D_BM, + #[cfg(feature = "_nrf5340-net")] + -7 => TxPower::NEG7D_BM, + -8 => TxPower::NEG8D_BM, + -12 => TxPower::NEG12D_BM, + -16 => TxPower::NEG16D_BM, + -20 => TxPower::NEG20D_BM, + -30 => TxPower::NEG30D_BM, + -40 => TxPower::NEG40D_BM, + _ => panic!("Invalid transmission power value"), + }; + + r.txpower.write(|w| w.txpower().variant(tx_power)); + } + + /// Waits until the radio state matches the given `state` + fn wait_for_radio_state(&self, state: RadioState) { + while self.state() != state {} + } + + /// Get the current radio state + fn state(&self) -> RadioState { + state(T::regs()) + } + + /// Moves the radio from any state to the DISABLED state + fn disable(&mut self) { + let r = T::regs(); + // See figure 110 in nRF52840-PS + loop { + match self.state() { + RadioState::DISABLED => return, + // idle or ramping up + RadioState::RX_RU | RadioState::RX_IDLE | RadioState::TX_RU | RadioState::TX_IDLE => { + r.tasks_disable.write(|w| w.tasks_disable().set_bit()); + self.wait_for_radio_state(RadioState::DISABLED); + return; + } + // ramping down + RadioState::RX_DISABLE | RadioState::TX_DISABLE => { + self.wait_for_radio_state(RadioState::DISABLED); + return; + } + // cancel ongoing transfer or ongoing CCA + RadioState::RX => { + r.tasks_ccastop.write(|w| w.tasks_ccastop().set_bit()); + r.tasks_stop.write(|w| w.tasks_stop().set_bit()); + self.wait_for_radio_state(RadioState::RX_IDLE); + } + RadioState::TX => { + r.tasks_stop.write(|w| w.tasks_stop().set_bit()); + self.wait_for_radio_state(RadioState::TX_IDLE); + } + } + } + } + + fn set_buffer(&mut self, buffer: &[u8]) { + let r = T::regs(); + r.packetptr.write(|w| unsafe { w.bits(buffer.as_ptr() as u32) }); + } + + /// Moves the radio to the RXIDLE state + fn receive_prepare(&mut self) { + // clear related events + T::regs().events_ccabusy.reset(); + T::regs().events_phyend.reset(); + // NOTE to avoid errata 204 (see rev1 v1.4) we do TX_IDLE -> DISABLED -> RX_IDLE + let disable = match self.state() { + RadioState::DISABLED => false, + RadioState::RX_IDLE => self.needs_enable, + _ => true, + }; + if disable { + self.disable(); + } + self.needs_enable = false; + } + + /// Prepare radio for receiving a packet + fn receive_start(&mut self, packet: &mut Packet) { + // NOTE we do NOT check the address of `packet` because the mutable reference ensures it's + // allocated in RAM + let r = T::regs(); + + self.receive_prepare(); + + // Configure shortcuts + // + // The radio goes through following states when receiving a 802.15.4 packet + // + // enable RX → ramp up RX → RX idle → Receive → end (PHYEND) + r.shorts.write(|w| w.rxready_start().enabled()); + + // set up RX buffer + self.set_buffer(packet.buffer.as_mut()); + + // start transfer + dma_start_fence(); + + match self.state() { + // Re-start receiver + RadioState::RX_IDLE => r.tasks_start.write(|w| w.tasks_start().set_bit()), + // Enable receiver + _ => r.tasks_rxen.write(|w| w.tasks_rxen().set_bit()), + } + } + + /// Cancel receiving packet + fn receive_cancel() { + let r = T::regs(); + r.shorts.reset(); + r.tasks_stop.write(|w| w.tasks_stop().set_bit()); + loop { + match state(r) { + RadioState::DISABLED | RadioState::RX_IDLE => break, + _ => (), + } + } + // DMA transfer may have been in progress so synchronize with its memory operations + dma_end_fence(); + } + + /// Receives one radio packet and copies its contents into the given `packet` buffer + /// + /// This methods returns the `Ok` variant if the CRC included the packet was successfully + /// validated by the hardware; otherwise it returns the `Err` variant. In either case, `packet` + /// will be updated with the received packet's data + pub async fn receive(&mut self, packet: &mut Packet) -> Result<(), Error> { + let s = T::state(); + let r = T::regs(); + + // Start the read + self.receive_start(packet); + + let dropper = OnDrop::new(|| Self::receive_cancel()); + + self.clear_all_interrupts(); + // wait until we have received something + core::future::poll_fn(|cx| { + s.event_waker.register(cx.waker()); + + if r.events_phyend.read().events_phyend().bit_is_set() { + r.events_phyend.reset(); + trace!("RX done poll"); + return Poll::Ready(()); + } else { + r.intenset.write(|w| w.phyend().set()); + }; + + Poll::Pending + }) + .await; + + dma_end_fence(); + dropper.defuse(); + + let crc = r.rxcrc.read().rxcrc().bits() as u16; + if r.crcstatus.read().crcstatus().bit_is_set() { + Ok(()) + } else { + Err(Error::CrcFailed(crc)) + } + } + + /// Tries to send the given `packet` + /// + /// This method performs Clear Channel Assessment (CCA) first and sends the `packet` only if the + /// channel is observed to be *clear* (no transmission is currently ongoing), otherwise no + /// packet is transmitted and the `Err` variant is returned + /// + /// NOTE this method will *not* modify the `packet` argument. The mutable reference is used to + /// ensure the `packet` buffer is allocated in RAM, which is required by the RADIO peripheral + // NOTE we do NOT check the address of `packet` because the mutable reference ensures it's + // allocated in RAM + pub async fn try_send(&mut self, packet: &mut Packet) -> Result<(), Error> { + let s = T::state(); + let r = T::regs(); + + // enable radio to perform cca + self.receive_prepare(); + + /// transmit result + #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub enum TransmitResult { + /// Success + Success, + /// Clear channel assessment reported channel in use + ChannelInUse, + } + + // Configure shortcuts + // + // The radio goes through following states when sending a 802.15.4 packet + // + // enable RX → ramp up RX → clear channel assessment (CCA) → CCA result + // CCA idle → enable TX → start TX → TX → end (PHYEND) → disabled + // + // CCA might end up in the event CCABUSY in which there will be no transmission + r.shorts.write(|w| { + w.rxready_ccastart() + .enabled() + .ccaidle_txen() + .enabled() + .txready_start() + .enabled() + .ccabusy_disable() + .enabled() + .phyend_disable() + .enabled() + }); + + // Set transmission buffer + self.set_buffer(packet.buffer.as_mut()); + + // the DMA transfer will start at some point after the following write operation so + // we place the compiler fence here + dma_start_fence(); + // start CCA. In case the channel is clear, the data at packetptr will be sent automatically + + match self.state() { + // Re-start receiver + RadioState::RX_IDLE => r.tasks_ccastart.write(|w| w.tasks_ccastart().set_bit()), + // Enable receiver + _ => r.tasks_rxen.write(|w| w.tasks_rxen().set_bit()), + } + + self.clear_all_interrupts(); + let result = core::future::poll_fn(|cx| { + s.event_waker.register(cx.waker()); + + if r.events_phyend.read().events_phyend().bit_is_set() { + r.events_phyend.reset(); + r.events_ccabusy.reset(); + trace!("TX done poll"); + return Poll::Ready(TransmitResult::Success); + } else if r.events_ccabusy.read().events_ccabusy().bit_is_set() { + r.events_ccabusy.reset(); + trace!("TX no CCA"); + return Poll::Ready(TransmitResult::ChannelInUse); + } + + r.intenset.write(|w| w.phyend().set().ccabusy().set()); + + Poll::Pending + }) + .await; + + match result { + TransmitResult::Success => Ok(()), + TransmitResult::ChannelInUse => Err(Error::ChannelInUse), + } + } +} + +/// An IEEE 802.15.4 packet +/// +/// This `Packet` is a PHY layer packet. It's made up of the physical header (PHR) and the PSDU +/// (PHY service data unit). The PSDU of this `Packet` will always include the MAC level CRC, AKA +/// the FCS (Frame Control Sequence) -- the CRC is fully computed in hardware and automatically +/// appended on transmission and verified on reception. +/// +/// The API lets users modify the usable part (not the CRC) of the PSDU via the `deref` and +/// `copy_from_slice` methods. These methods will automatically update the PHR. +/// +/// See figure 119 in the Product Specification of the nRF52840 for more details +pub struct Packet { + buffer: [u8; Self::SIZE], +} + +// See figure 124 in nRF52840-PS +impl Packet { + // for indexing purposes + const PHY_HDR: usize = 0; + const DATA: core::ops::RangeFrom<usize> = 1..; + + /// Maximum amount of usable payload (CRC excluded) a single packet can contain, in bytes + pub const CAPACITY: u8 = 125; + const CRC: u8 = 2; // size of the CRC, which is *never* copied to / from RAM + const MAX_PSDU_LEN: u8 = Self::CAPACITY + Self::CRC; + const SIZE: usize = 1 /* PHR */ + Self::MAX_PSDU_LEN as usize; + + /// Returns an empty packet (length = 0) + pub fn new() -> Self { + let mut packet = Self { + buffer: [0; Self::SIZE], + }; + packet.set_len(0); + packet + } + + /// Fills the packet payload with given `src` data + /// + /// # Panics + /// + /// This function panics if `src` is larger than `Self::CAPACITY` + pub fn copy_from_slice(&mut self, src: &[u8]) { + assert!(src.len() <= Self::CAPACITY as usize); + let len = src.len() as u8; + self.buffer[Self::DATA][..len as usize].copy_from_slice(&src[..len.into()]); + self.set_len(len); + } + + /// Returns the size of this packet's payload + pub fn len(&self) -> u8 { + self.buffer[Self::PHY_HDR] - Self::CRC + } + + /// Changes the size of the packet's payload + /// + /// # Panics + /// + /// This function panics if `len` is larger than `Self::CAPACITY` + pub fn set_len(&mut self, len: u8) { + assert!(len <= Self::CAPACITY); + self.buffer[Self::PHY_HDR] = len + Self::CRC; + } + + /// Returns the LQI (Link Quality Indicator) of the received packet + /// + /// Note that the LQI is stored in the `Packet`'s internal buffer by the hardware so the value + /// returned by this method is only valid after a `Radio.recv` operation. Operations that + /// modify the `Packet`, like `copy_from_slice` or `set_len`+`deref_mut`, will overwrite the + /// stored LQI value. + /// + /// Also note that the hardware will *not* compute a LQI for packets smaller than 3 bytes so + /// this method will return an invalid value for those packets. + pub fn lqi(&self) -> u8 { + self.buffer[1 /* PHY_HDR */ + self.len() as usize /* data */] + } +} + +impl core::ops::Deref for Packet { + type Target = [u8]; + + fn deref(&self) -> &[u8] { + &self.buffer[Self::DATA][..self.len() as usize] + } +} + +impl core::ops::DerefMut for Packet { + fn deref_mut(&mut self) -> &mut [u8] { + let len = self.len(); + &mut self.buffer[Self::DATA][..len as usize] + } +} + +/// NOTE must be followed by a volatile write operation +fn dma_start_fence() { + compiler_fence(Ordering::Release); +} + +/// NOTE must be preceded by a volatile read operation +fn dma_end_fence() { + compiler_fence(Ordering::Acquire); +} diff --git a/embassy-nrf/src/radio/mod.rs b/embassy-nrf/src/radio/mod.rs new file mode 100644 index 000000000..4c0cc3280 --- /dev/null +++ b/embassy-nrf/src/radio/mod.rs @@ -0,0 +1,110 @@ +//! Integrated 2.4 GHz Radio +//! +//! The 2.4 GHz radio transceiver is compatible with multiple radio standards +//! such as 1Mbps, 2Mbps and Long Range Bluetooth Low Energy. + +#![macro_use] + +/// Bluetooth Low Energy Radio driver. +pub mod ble; +#[cfg(any( + feature = "nrf52811", + feature = "nrf52820", + feature = "nrf52833", + feature = "nrf52840", + feature = "_nrf5340-net" +))] +/// IEEE 802.15.4 +pub mod ieee802154; + +use core::marker::PhantomData; + +use pac::radio::state::STATE_A as RadioState; +pub use pac::radio::txpower::TXPOWER_A as TxPower; + +use crate::{interrupt, pac, Peripheral}; + +/// RADIO error. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum Error { + /// Buffer was too long. + BufferTooLong, + /// Buffer was too short. + BufferTooShort, + /// The buffer is not in data RAM. It's most likely in flash, and nRF's DMA cannot access flash. + BufferNotInRAM, + /// Clear channel assessment reported channel in use + ChannelInUse, + /// CRC check failed + CrcFailed(u16), +} + +/// Interrupt handler +pub struct InterruptHandler<T: Instance> { + _phantom: PhantomData<T>, +} + +impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> { + unsafe fn on_interrupt() { + let r = T::regs(); + let s = T::state(); + // clear all interrupts + r.intenclr.write(|w| w.bits(0xffff_ffff)); + s.event_waker.wake(); + } +} + +pub(crate) mod sealed { + use embassy_sync::waitqueue::AtomicWaker; + + pub struct State { + /// end packet transmission or reception + pub event_waker: AtomicWaker, + } + impl State { + pub const fn new() -> Self { + Self { + event_waker: AtomicWaker::new(), + } + } + } + + pub trait Instance { + fn regs() -> &'static crate::pac::radio::RegisterBlock; + fn state() -> &'static State; + } +} + +macro_rules! impl_radio { + ($type:ident, $pac_type:ident, $irq:ident) => { + impl crate::radio::sealed::Instance for peripherals::$type { + fn regs() -> &'static pac::radio::RegisterBlock { + unsafe { &*pac::$pac_type::ptr() } + } + + fn state() -> &'static crate::radio::sealed::State { + static STATE: crate::radio::sealed::State = crate::radio::sealed::State::new(); + &STATE + } + } + impl crate::radio::Instance for peripherals::$type { + type Interrupt = crate::interrupt::typelevel::$irq; + } + }; +} + +/// Radio peripheral instance. +pub trait Instance: Peripheral<P = Self> + sealed::Instance + 'static + Send { + /// Interrupt for this peripheral. + type Interrupt: interrupt::typelevel::Interrupt; +} + +/// Get the state of the radio +pub(crate) fn state(radio: &pac::radio::RegisterBlock) -> RadioState { + match radio.state.read().state().variant() { + Some(state) => state, + None => unreachable!(), + } +} diff --git a/embassy-nrf/src/rng.rs b/embassy-nrf/src/rng.rs index e2803f0d3..1c463fb7c 100644 --- a/embassy-nrf/src/rng.rs +++ b/embassy-nrf/src/rng.rs @@ -5,12 +5,10 @@ use core::future::poll_fn; use core::marker::PhantomData; use core::ptr; -use core::sync::atomic::{AtomicPtr, Ordering}; use core::task::Poll; use embassy_hal_internal::drop::OnDrop; use embassy_hal_internal::{into_ref, PeripheralRef}; -use embassy_sync::waitqueue::AtomicWaker; use crate::interrupt::typelevel::Interrupt; use crate::{interrupt, Peripheral}; @@ -22,7 +20,6 @@ pub struct InterruptHandler<T: Instance> { impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> { unsafe fn on_interrupt() { - let s = T::state(); let r = T::regs(); // Clear the event. @@ -30,46 +27,25 @@ impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandl // 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 = s.ptr.load(Ordering::Relaxed); + critical_section::with(|cs| { + let mut state = T::state().borrow_mut(cs); // 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 = s.end.load(Ordering::Relaxed); - if !ptr.is_null() && ptr != end { + if !state.ptr.is_null() && state.ptr != state.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 = r.value.read().value().bits(); + *state.ptr = r.value.read().value().bits(); + state.ptr = state.ptr.add(1); + } + + if state.ptr == state.end { + state.waker.wake(); } } - (ptr, end) }); - - if ptr.is_null() || ptr == end { - // If the future was dropped, there's nothing to do. - // If `ptr == end`, we were called by mistake, so return. - return; - } - - let new_ptr = unsafe { ptr.add(1) }; - match s - .ptr - .compare_exchange(ptr, new_ptr, Ordering::Relaxed, Ordering::Relaxed) - { - Ok(_) => { - 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 { - s.waker.wake(); - } - } - Err(_) => { - // If the future was dropped or finished, there's no point trying to wake it. - // It will have already stopped the RNG, so there's no need to do that either. - } - } } } @@ -132,17 +108,18 @@ impl<'d, T: Instance> Rng<'d, T> { /// Fill the buffer with random bytes. pub async fn fill_bytes(&mut self, dest: &mut [u8]) { - if dest.len() == 0 { + if dest.is_empty() { 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. - s.ptr.store(range.start, Ordering::Relaxed); - s.end.store(range.end, Ordering::Relaxed); + critical_section::with(|cs| { + let mut state = T::state().borrow_mut(cs); + state.ptr = range.start; + state.end = range.end; + }); self.enable_irq(); self.start(); @@ -151,24 +128,24 @@ impl<'d, T: Instance> Rng<'d, T> { self.stop(); self.disable_irq(); - // The interrupt is now disabled and can't preempt us anymore, so the order doesn't matter here. - s.ptr.store(ptr::null_mut(), Ordering::Relaxed); - s.end.store(ptr::null_mut(), Ordering::Relaxed); + critical_section::with(|cs| { + let mut state = T::state().borrow_mut(cs); + state.ptr = ptr::null_mut(); + state.end = ptr::null_mut(); + }); }); poll_fn(|cx| { - 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 = s.end.load(Ordering::Relaxed); - let ptr = s.ptr.load(Ordering::Relaxed); - - if ptr == end { - // We're done. - Poll::Ready(()) - } else { - Poll::Pending - } + critical_section::with(|cs| { + let mut s = T::state().borrow_mut(cs); + s.waker.register(cx.waker()); + if s.ptr == s.end { + // We're done. + Poll::Ready(()) + } else { + Poll::Pending + } + }) }) .await; @@ -194,9 +171,11 @@ impl<'d, T: Instance> Rng<'d, T> { impl<'d, T: Instance> Drop for Rng<'d, T> { fn drop(&mut self) { self.stop(); - let s = T::state(); - s.ptr.store(ptr::null_mut(), Ordering::Relaxed); - s.end.store(ptr::null_mut(), Ordering::Relaxed); + critical_section::with(|cs| { + let mut state = T::state().borrow_mut(cs); + state.ptr = ptr::null_mut(); + state.end = ptr::null_mut(); + }); } } @@ -227,21 +206,48 @@ impl<'d, T: Instance> rand_core::RngCore for Rng<'d, T> { impl<'d, T: Instance> rand_core::CryptoRng for Rng<'d, T> {} pub(crate) mod sealed { + use core::cell::{Ref, RefCell, RefMut}; + + use critical_section::{CriticalSection, Mutex}; + use embassy_sync::waitqueue::WakerRegistration; + use super::*; /// Peripheral static state pub struct State { - pub ptr: AtomicPtr<u8>, - pub end: AtomicPtr<u8>, - pub waker: AtomicWaker, + inner: Mutex<RefCell<InnerState>>, } + pub struct InnerState { + pub ptr: *mut u8, + pub end: *mut u8, + pub waker: WakerRegistration, + } + + unsafe impl Send for InnerState {} + impl State { pub const fn new() -> Self { Self { - ptr: AtomicPtr::new(ptr::null_mut()), - end: AtomicPtr::new(ptr::null_mut()), - waker: AtomicWaker::new(), + inner: Mutex::new(RefCell::new(InnerState::new())), + } + } + + pub fn borrow<'cs>(&'cs self, cs: CriticalSection<'cs>) -> Ref<'cs, InnerState> { + self.inner.borrow(cs).borrow() + } + + pub fn borrow_mut<'cs>(&'cs self, cs: CriticalSection<'cs>) -> RefMut<'cs, InnerState> { + self.inner.borrow(cs).borrow_mut() + } + } + + impl InnerState { + pub const fn new() -> Self { + Self { + ptr: ptr::null_mut(), + end: ptr::null_mut(), + waker: WakerRegistration::new(), } } } diff --git a/embassy-nrf/src/spim.rs b/embassy-nrf/src/spim.rs index b0723d495..c45d45e68 100644 --- a/embassy-nrf/src/spim.rs +++ b/embassy-nrf/src/spim.rs @@ -10,13 +10,14 @@ use core::task::Poll; use embassy_embedded_hal::SetConfig; use embassy_hal_internal::{into_ref, PeripheralRef}; pub use embedded_hal_02::spi::{Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3}; +pub use pac::spim0::config::ORDER_A as BitOrder; pub use pac::spim0::frequency::FREQUENCY_A as Frequency; -use crate::chip::FORCE_COPY_BUFFER_SIZE; +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::gpio::{self, convert_drive, AnyPin, OutputDrive, Pin as GpioPin, PselBits}; use crate::interrupt::typelevel::Interrupt; -use crate::util::{slice_in_ram_or, slice_ptr_parts, slice_ptr_parts_mut}; +use crate::util::{slice_in_ram_or, slice_ptr_len, slice_ptr_parts, slice_ptr_parts_mut}; use crate::{interrupt, pac, Peripheral}; /// SPIM error @@ -24,10 +25,6 @@ use crate::{interrupt, pac, Peripheral}; #[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, } @@ -41,11 +38,23 @@ pub struct Config { /// SPI mode pub mode: Mode, + /// Bit order + pub bit_order: BitOrder, + /// 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, + + /// Drive strength for the SCK line. + pub sck_drive: OutputDrive, + + /// Drive strength for the MOSI line. + pub mosi_drive: OutputDrive, + + /// Drive strength for the MISO line. + pub miso_drive: OutputDrive, } impl Default for Config { @@ -53,7 +62,11 @@ impl Default for Config { Self { frequency: Frequency::M1, mode: MODE_0, + bit_order: BitOrder::MSB_FIRST, orc: 0x00, + sck_drive: OutputDrive::HighDrive, + mosi_drive: OutputDrive::HighDrive, + miso_drive: OutputDrive::HighDrive, } } } @@ -69,9 +82,13 @@ impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandl let s = T::state(); #[cfg(feature = "_nrf52832_anomaly_109")] - if r.events_started.read().bits() != 0 { - s.waker.wake(); - r.intenclr.write(|w| w.started().clear()); + { + // Ideally we should call this only during the first chunk transfer, + // but so far calling this every time doesn't seem to be causing any issues. + if r.events_started.read().bits() != 0 { + s.waker.wake(); + r.intenclr.write(|w| w.started().clear()); + } } if r.events_end.read().bits() != 0 { @@ -154,13 +171,16 @@ impl<'d, T: Instance> Spim<'d, T> { // Configure pins if let Some(sck) = &sck { - sck.conf().write(|w| w.dir().output().drive().h0h1()); + sck.conf() + .write(|w| w.dir().output().drive().variant(convert_drive(config.sck_drive))); } if let Some(mosi) = &mosi { - mosi.conf().write(|w| w.dir().output().drive().h0h1()); + mosi.conf() + .write(|w| w.dir().output().drive().variant(convert_drive(config.mosi_drive))); } if let Some(miso) = &miso { - miso.conf().write(|w| w.input().connect().drive().h0h1()); + miso.conf() + .write(|w| w.input().connect().drive().variant(convert_drive(config.miso_drive))); } match config.mode.polarity { @@ -204,27 +224,39 @@ impl<'d, T: Instance> Spim<'d, T> { spim } - 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. - + fn prepare_dma_transfer(&mut self, rx: *mut [u8], tx: *const [u8], offset: usize, length: usize) { compiler_fence(Ordering::SeqCst); let r = T::regs(); - // Set up the DMA write. - let (ptr, tx_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(tx_len as _) }); + fn xfer_params(ptr: u32, total: usize, offset: usize, length: usize) -> (u32, usize) { + if total > offset { + (ptr.wrapping_add(offset as _), core::cmp::min(total - offset, length)) + } else { + (ptr, 0) + } + } // Set up the DMA read. - let (ptr, rx_len) = slice_ptr_parts_mut(rx); - r.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as _) }); + let (ptr, len) = slice_ptr_parts_mut(rx); + let (rx_ptr, rx_len) = xfer_params(ptr as _, len as _, offset, length); + r.rxd.ptr.write(|w| unsafe { w.ptr().bits(rx_ptr) }); r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(rx_len as _) }); + // Set up the DMA write. + let (ptr, len) = slice_ptr_parts(tx); + let (tx_ptr, tx_len) = xfer_params(ptr as _, len as _, offset, length); + r.txd.ptr.write(|w| unsafe { w.ptr().bits(tx_ptr) }); + r.txd.maxcnt.write(|w| unsafe { w.maxcnt().bits(tx_len as _) }); + + /* + trace!("XFER: offset: {}, length: {}", offset, length); + trace!("RX(len: {}, ptr: {=u32:02x})", rx_len, rx_ptr as u32); + trace!("TX(len: {}, ptr: {=u32:02x})", tx_len, tx_ptr as u32); + */ + #[cfg(feature = "_nrf52832_anomaly_109")] - { + if offset == 0 { let s = T::state(); r.events_started.reset(); @@ -247,21 +279,32 @@ impl<'d, T: Instance> Spim<'d, T> { // Start SPI transaction. r.tasks_start.write(|w| unsafe { w.bits(1) }); - - Ok(()) } - fn blocking_inner_from_ram(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(), Error> { - self.prepare(rx, tx)?; + fn blocking_inner_from_ram_chunk(&mut self, rx: *mut [u8], tx: *const [u8], offset: usize, length: usize) { + self.prepare_dma_transfer(rx, tx, offset, length); #[cfg(feature = "_nrf52832_anomaly_109")] - while let Poll::Pending = self.nrf52832_dma_workaround_status() {} + if offset == 0 { + while self.nrf52832_dma_workaround_status().is_pending() {} + } // Wait for 'end' event. while T::regs().events_end.read().bits() == 0 {} compiler_fence(Ordering::SeqCst); + } + fn blocking_inner_from_ram(&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. + + let xfer_len = core::cmp::max(slice_ptr_len(rx), slice_ptr_len(tx)); + for offset in (0..xfer_len).step_by(EASY_DMA_SIZE) { + let length = core::cmp::min(xfer_len - offset, EASY_DMA_SIZE); + self.blocking_inner_from_ram_chunk(rx, tx, offset, length); + } Ok(()) } @@ -269,27 +312,28 @@ impl<'d, T: Instance> Spim<'d, T> { match self.blocking_inner_from_ram(rx, tx) { Ok(_) => Ok(()), Err(Error::BufferNotInRAM) => { - trace!("Copying SPIM tx buffer into RAM for DMA"); + // 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); 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<(), Error> { - self.prepare(rx, tx)?; + async fn async_inner_from_ram_chunk(&mut self, rx: *mut [u8], tx: *const [u8], offset: usize, length: usize) { + self.prepare_dma_transfer(rx, tx, offset, length); #[cfg(feature = "_nrf52832_anomaly_109")] - poll_fn(|cx| { - let s = T::state(); + if offset == 0 { + poll_fn(|cx| { + let s = T::state(); - s.waker.register(cx.waker()); + s.waker.register(cx.waker()); - self.nrf52832_dma_workaround_status() - }) - .await; + self.nrf52832_dma_workaround_status() + }) + .await; + } // Wait for 'end' event. poll_fn(|cx| { @@ -303,7 +347,18 @@ impl<'d, T: Instance> Spim<'d, T> { .await; compiler_fence(Ordering::SeqCst); + } + async fn async_inner_from_ram(&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. + + let xfer_len = core::cmp::max(slice_ptr_len(rx), slice_ptr_len(tx)); + for offset in (0..xfer_len).step_by(EASY_DMA_SIZE) { + let length = core::cmp::min(xfer_len - offset, EASY_DMA_SIZE); + self.async_inner_from_ram_chunk(rx, tx, offset, length).await; + } Ok(()) } @@ -311,12 +366,11 @@ impl<'d, T: Instance> Spim<'d, T> { match self.async_inner_from_ram(rx, tx).await { Ok(_) => Ok(()), Err(Error::BufferNotInRAM) => { - trace!("Copying SPIM tx buffer into RAM for DMA"); + // 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); self.async_inner_from_ram(rx, tx_ram_buf).await } - Err(error) => Err(error), } } @@ -515,8 +569,6 @@ mod eh02 { impl embedded_hal_1::spi::Error for Error { fn kind(&self) -> embedded_hal_1::spi::ErrorKind { match *self { - Self::TxBufferTooLong => embedded_hal_1::spi::ErrorKind::Other, - Self::RxBufferTooLong => embedded_hal_1::spi::ErrorKind::Other, Self::BufferNotInRAM => embedded_hal_1::spi::ErrorKind::Other, } } @@ -580,22 +632,22 @@ impl<'d, T: Instance> SetConfig for Spim<'d, T> { r.config.write(|w| { match mode { MODE_0 => { - w.order().msb_first(); + w.order().variant(config.bit_order); w.cpol().active_high(); w.cpha().leading(); } MODE_1 => { - w.order().msb_first(); + w.order().variant(config.bit_order); w.cpol().active_high(); w.cpha().trailing(); } MODE_2 => { - w.order().msb_first(); + w.order().variant(config.bit_order); w.cpol().active_low(); w.cpha().leading(); } MODE_3 => { - w.order().msb_first(); + w.order().variant(config.bit_order); w.cpol().active_low(); w.cpha().trailing(); } diff --git a/embassy-nrf/src/spis.rs b/embassy-nrf/src/spis.rs index 3aad25298..772ca40cc 100644 --- a/embassy-nrf/src/spis.rs +++ b/embassy-nrf/src/spis.rs @@ -9,8 +9,9 @@ use core::task::Poll; use embassy_embedded_hal::SetConfig; use embassy_hal_internal::{into_ref, PeripheralRef}; pub use embedded_hal_02::spi::{Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3}; +pub use pac::spis0::config::ORDER_A as BitOrder; -use crate::chip::FORCE_COPY_BUFFER_SIZE; +use crate::chip::{EASY_DMA_SIZE, FORCE_COPY_BUFFER_SIZE}; use crate::gpio::sealed::Pin as _; use crate::gpio::{self, AnyPin, Pin as GpioPin}; use crate::interrupt::typelevel::Interrupt; @@ -36,6 +37,9 @@ pub struct Config { /// SPI mode pub mode: Mode, + /// Bit order + pub bit_order: BitOrder, + /// Overread character. /// /// If the master keeps clocking the bus after all the bytes in the TX buffer have @@ -56,6 +60,7 @@ impl Default for Config { fn default() -> Self { Self { mode: MODE_0, + bit_order: BitOrder::MSB_FIRST, orc: 0x00, def: 0x00, auto_acquire: true, @@ -222,11 +227,17 @@ impl<'d, T: Instance> Spis<'d, T> { // Set up the DMA write. let (ptr, len) = slice_ptr_parts(tx); + if len > EASY_DMA_SIZE { + return Err(Error::TxBufferTooLong); + } 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); + if len > EASY_DMA_SIZE { + return Err(Error::RxBufferTooLong); + } r.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as _) }); r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(len as _) }); @@ -503,22 +514,22 @@ impl<'d, T: Instance> SetConfig for Spis<'d, T> { r.config.write(|w| { match mode { MODE_0 => { - w.order().msb_first(); + w.order().variant(config.bit_order); w.cpol().active_high(); w.cpha().leading(); } MODE_1 => { - w.order().msb_first(); + w.order().variant(config.bit_order); w.cpol().active_high(); w.cpha().trailing(); } MODE_2 => { - w.order().msb_first(); + w.order().variant(config.bit_order); w.cpol().active_low(); w.cpha().leading(); } MODE_3 => { - w.order().msb_first(); + w.order().variant(config.bit_order); w.cpol().active_low(); w.cpha().trailing(); } diff --git a/embassy-nrf/src/temp.rs b/embassy-nrf/src/temp.rs index 5e2998b10..ed4a47713 100644 --- a/embassy-nrf/src/temp.rs +++ b/embassy-nrf/src/temp.rs @@ -83,7 +83,7 @@ impl<'d> Temp<'d> { let value = poll_fn(|cx| { WAKER.register(cx.waker()); if t.events_datardy.read().bits() == 0 { - return Poll::Pending; + Poll::Pending } else { t.events_datardy.reset(); let raw = t.temp.read().bits(); diff --git a/embassy-nrf/src/time_driver.rs b/embassy-nrf/src/time_driver.rs index 042f7c5f7..3407c9504 100644 --- a/embassy-nrf/src/time_driver.rs +++ b/embassy-nrf/src/time_driver.rs @@ -171,7 +171,8 @@ impl RtcDriver { fn next_period(&self) { critical_section::with(|cs| { let r = rtc(); - let period = self.period.fetch_add(1, Ordering::Relaxed) + 1; + let period = self.period.load(Ordering::Relaxed) + 1; + self.period.store(period, Ordering::Relaxed); let t = (period as u64) << 23; for n in 0..ALARM_COUNT { @@ -219,18 +220,15 @@ impl Driver for RtcDriver { } unsafe fn allocate_alarm(&self) -> Option<AlarmHandle> { - let id = self.alarm_count.fetch_update(Ordering::AcqRel, Ordering::Acquire, |x| { - if x < ALARM_COUNT as u8 { - Some(x + 1) + critical_section::with(|_| { + let id = self.alarm_count.load(Ordering::Relaxed); + if id < ALARM_COUNT as u8 { + self.alarm_count.store(id + 1, Ordering::Relaxed); + Some(AlarmHandle::new(id)) } else { None } - }); - - match id { - Ok(id) => Some(AlarmHandle::new(id)), - Err(_) => None, - } + }) } fn set_alarm_callback(&self, alarm: AlarmHandle, callback: fn(*mut ()), ctx: *mut ()) { diff --git a/embassy-nrf/src/timer.rs b/embassy-nrf/src/timer.rs index 3dbfdac42..3c35baee5 100644 --- a/embassy-nrf/src/timer.rs +++ b/embassy-nrf/src/timer.rs @@ -111,7 +111,7 @@ impl<'d, T: Instance> Timer<'d, T> { Self::new_inner(timer, true) } - fn new_inner(timer: impl Peripheral<P = T> + 'd, is_counter: bool) -> Self { + fn new_inner(timer: impl Peripheral<P = T> + 'd, _is_counter: bool) -> Self { into_ref!(timer); let regs = T::regs(); @@ -122,12 +122,16 @@ impl<'d, T: Instance> Timer<'d, T> { // since changing BITMODE while running can cause 'unpredictable behaviour' according to the specification. this.stop(); - if is_counter { + #[cfg(not(feature = "nrf51"))] + if _is_counter { regs.mode.write(|w| w.mode().low_power_counter()); } else { regs.mode.write(|w| w.mode().timer()); } + #[cfg(feature = "nrf51")] + 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? regs.bitmode.write(|w| w.bitmode()._32bit()); @@ -238,7 +242,11 @@ pub struct Cc<'d, T: Instance> { 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() + #[cfg(not(feature = "nrf51"))] + return T::regs().cc[self.n].read().cc().bits(); + + #[cfg(feature = "nrf51")] + return T::regs().cc[self.n].read().bits(); } /// Set the value stored in the register. @@ -246,7 +254,11 @@ impl<'d, T: Instance> Cc<'d, T> { /// `event_compare` will fire when the timer's counter reaches this value. pub fn write(&self, value: u32) { // SAFETY: there are no invalid values for the CC register. - T::regs().cc[self.n].write(|w| unsafe { w.cc().bits(value) }) + #[cfg(not(feature = "nrf51"))] + T::regs().cc[self.n].write(|w| unsafe { w.cc().bits(value) }); + + #[cfg(feature = "nrf51")] + T::regs().cc[self.n].write(|w| unsafe { w.bits(value) }); } /// Capture the current value of the timer's counter in this register, and return it. diff --git a/embassy-nrf/src/twim.rs b/embassy-nrf/src/twim.rs index da8e15d02..24810a08c 100644 --- a/embassy-nrf/src/twim.rs +++ b/embassy-nrf/src/twim.rs @@ -372,7 +372,7 @@ impl<'d, T: Instance> Twim<'d, T> { // Start write operation. r.shorts.write(|w| w.lasttx_stop().enabled()); r.tasks_starttx.write(|w| unsafe { w.bits(1) }); - if buffer.len() == 0 { + if buffer.is_empty() { // With a zero-length buffer, LASTTX doesn't fire (because there's no last byte!), so do the STOP ourselves. r.tasks_stop.write(|w| unsafe { w.bits(1) }); } @@ -403,7 +403,7 @@ impl<'d, T: Instance> Twim<'d, T> { // Start read operation. r.shorts.write(|w| w.lastrx_stop().enabled()); r.tasks_startrx.write(|w| unsafe { w.bits(1) }); - if buffer.len() == 0 { + if buffer.is_empty() { // With a zero-length buffer, LASTRX doesn't fire (because there's no last byte!), so do the STOP ourselves. r.tasks_stop.write(|w| unsafe { w.bits(1) }); } @@ -447,7 +447,7 @@ impl<'d, T: Instance> Twim<'d, T> { w }); r.tasks_starttx.write(|w| unsafe { w.bits(1) }); - if wr_buffer.len() == 0 && rd_buffer.len() == 0 { + if wr_buffer.is_empty() && rd_buffer.is_empty() { // With a zero-length buffer, LASTRX/LASTTX doesn't fire (because there's no last byte!), so do the STOP ourselves. // TODO handle when only one of the buffers is zero length r.tasks_stop.write(|w| unsafe { w.bits(1) }); @@ -469,7 +469,7 @@ impl<'d, T: Instance> Twim<'d, T> { 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); - self.setup_write_read_from_ram(address, &tx_ram_buf, rd_buffer, inten) + self.setup_write_read_from_ram(address, tx_ram_buf, rd_buffer, inten) } Err(error) => Err(error), } @@ -482,7 +482,7 @@ impl<'d, T: Instance> Twim<'d, T> { 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); - self.setup_write_from_ram(address, &tx_ram_buf, inten) + self.setup_write_from_ram(address, tx_ram_buf, inten) } Err(error) => Err(error), } @@ -779,7 +779,7 @@ mod eh02 { impl<'a, T: Instance> embedded_hal_02::blocking::i2c::Write for Twim<'a, T> { type Error = Error; - fn write<'w>(&mut self, addr: u8, bytes: &'w [u8]) -> Result<(), Error> { + fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), Error> { if slice_in_ram(bytes) { self.blocking_write(addr, bytes) } else { @@ -796,7 +796,7 @@ mod eh02 { impl<'a, T: Instance> embedded_hal_02::blocking::i2c::Read for Twim<'a, T> { type Error = Error; - fn read<'w>(&mut self, addr: u8, bytes: &'w mut [u8]) -> Result<(), Error> { + fn read(&mut self, addr: u8, bytes: &mut [u8]) -> Result<(), Error> { self.blocking_read(addr, bytes) } } @@ -847,10 +847,10 @@ impl<'d, T: Instance> embedded_hal_1::i2c::I2c for Twim<'d, T> { self.blocking_write_read(address, wr_buffer, rd_buffer) } - fn transaction<'a>( + fn transaction( &mut self, _address: u8, - _operations: &mut [embedded_hal_1::i2c::Operation<'a>], + _operations: &mut [embedded_hal_1::i2c::Operation<'_>], ) -> Result<(), Self::Error> { todo!(); } diff --git a/embassy-nrf/src/twis.rs b/embassy-nrf/src/twis.rs index c6c020557..415150447 100644 --- a/embassy-nrf/src/twis.rs +++ b/embassy-nrf/src/twis.rs @@ -577,7 +577,7 @@ impl<'d, T: Instance> Twis<'d, T> { 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) + self.setup_respond_from_ram(tx_ram_buf, inten) } Err(error) => Err(error), } diff --git a/embassy-nrf/src/uarte.rs b/embassy-nrf/src/uarte.rs index 95434e7a7..cbd5dccbc 100644 --- a/embassy-nrf/src/uarte.rs +++ b/embassy-nrf/src/uarte.rs @@ -52,6 +52,37 @@ impl Default for Config { } } +bitflags::bitflags! { + /// Error source flags + pub struct ErrorSource: u32 { + /// Buffer overrun + const OVERRUN = 0x01; + /// Parity error + const PARITY = 0x02; + /// Framing error + const FRAMING = 0x04; + /// Break condition + const BREAK = 0x08; + } +} + +impl ErrorSource { + #[inline] + fn check(self) -> Result<(), Error> { + if self.contains(ErrorSource::OVERRUN) { + Err(Error::Overrun) + } else if self.contains(ErrorSource::PARITY) { + Err(Error::Parity) + } else if self.contains(ErrorSource::FRAMING) { + Err(Error::Framing) + } else if self.contains(ErrorSource::BREAK) { + Err(Error::Break) + } else { + Ok(()) + } + } +} + /// UART error. #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -61,6 +92,14 @@ pub enum Error { BufferTooLong, /// The buffer is not in data RAM. It's most likely in flash, and nRF's DMA cannot access flash. BufferNotInRAM, + /// Framing Error + Framing, + /// Parity Error + Parity, + /// Buffer Overrun + Overrun, + /// Break condition + Break, } /// Interrupt handler. @@ -73,12 +112,19 @@ impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandl 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()); + let endrx = r.events_endrx.read().bits(); + let error = r.events_error.read().bits(); + if endrx != 0 || error != 0 { + s.rx_waker.wake(); + if endrx != 0 { + r.intenclr.write(|w| w.endrx().clear()); + } + if error != 0 { + r.intenclr.write(|w| w.error().clear()); + } } if r.events_endtx.read().bits() != 0 { - s.endtx_waker.wake(); + s.tx_waker.wake(); r.intenclr.write(|w| w.endtx().clear()); } } @@ -113,7 +159,7 @@ impl<'d, T: Instance> Uarte<'d, T> { txd: impl Peripheral<P = impl GpioPin> + 'd, config: Config, ) -> Self { - into_ref!(rxd, txd); + into_ref!(uarte, rxd, txd); Self::new_inner(uarte, rxd.map_into(), txd.map_into(), None, None, config) } @@ -127,7 +173,7 @@ impl<'d, T: Instance> Uarte<'d, T> { rts: impl Peripheral<P = impl GpioPin> + 'd, config: Config, ) -> Self { - into_ref!(rxd, txd, cts, rts); + into_ref!(uarte, rxd, txd, cts, rts); Self::new_inner( uarte, rxd.map_into(), @@ -139,17 +185,22 @@ impl<'d, T: Instance> Uarte<'d, T> { } fn new_inner( - uarte: impl Peripheral<P = T> + 'd, + uarte: PeripheralRef<'d, T>, rxd: PeripheralRef<'d, AnyPin>, txd: PeripheralRef<'d, AnyPin>, cts: Option<PeripheralRef<'d, AnyPin>>, rts: Option<PeripheralRef<'d, AnyPin>>, config: Config, ) -> Self { - into_ref!(uarte); - let r = T::regs(); + let hardware_flow_control = match (rts.is_some(), cts.is_some()) { + (false, false) => false, + (true, true) => true, + _ => panic!("RTS and CTS pins must be either both set or none set."), + }; + configure(r, config, hardware_flow_control); + rxd.conf().write(|w| w.input().connect().drive().h0h1()); r.psel.rxd.write(|w| unsafe { w.bits(rxd.psel_bits()) }); @@ -171,13 +222,6 @@ impl<'d, T: Instance> Uarte<'d, T> { T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; - let hardware_flow_control = match (rts.is_some(), cts.is_some()) { - (false, false) => false, - (true, true) => true, - _ => panic!("RTS and CTS pins must be either both set or none set."), - }; - configure(r, config, hardware_flow_control); - let s = T::state(); s.tx_rx_refcount.store(2, Ordering::Relaxed); @@ -196,6 +240,14 @@ impl<'d, T: Instance> Uarte<'d, T> { (self.tx, self.rx) } + /// Split the UART in reader and writer parts, by reference. + /// + /// The returned halves borrow from `self`, so you can drop them and go back to using + /// the "un-split" `self`. This allows temporarily splitting the UART. + pub fn split_by_ref(&mut self) -> (&mut UarteTx<'d, T>, &mut UarteRx<'d, T>) { + (&mut self.tx, &mut 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. @@ -245,7 +297,7 @@ impl<'d, T: Instance> Uarte<'d, T> { } } -fn configure(r: &RegisterBlock, config: Config, hardware_flow_control: bool) { +pub(crate) fn configure(r: &RegisterBlock, config: Config, hardware_flow_control: bool) { r.config.write(|w| { w.hwfc().bit(hardware_flow_control); w.parity().variant(config.parity); @@ -261,8 +313,14 @@ fn configure(r: &RegisterBlock, config: Config, hardware_flow_control: bool) { r.events_rxstarted.reset(); r.events_txstarted.reset(); + // reset all pins + r.psel.txd.write(|w| w.connect().disconnected()); + r.psel.rxd.write(|w| w.connect().disconnected()); + r.psel.cts.write(|w| w.connect().disconnected()); + r.psel.rts.write(|w| w.connect().disconnected()); + // Enable - apply_workaround_for_enable_anomaly(&r); + apply_workaround_for_enable_anomaly(r); r.enable.write(|w| w.enable().enabled()); } @@ -274,7 +332,7 @@ impl<'d, T: Instance> UarteTx<'d, T> { txd: impl Peripheral<P = impl GpioPin> + 'd, config: Config, ) -> Self { - into_ref!(txd); + into_ref!(uarte, txd); Self::new_inner(uarte, txd.map_into(), None, config) } @@ -286,20 +344,20 @@ impl<'d, T: Instance> UarteTx<'d, T> { cts: impl Peripheral<P = impl GpioPin> + 'd, config: Config, ) -> Self { - into_ref!(txd, cts); + into_ref!(uarte, txd, cts); Self::new_inner(uarte, txd.map_into(), Some(cts.map_into()), config) } fn new_inner( - uarte: impl Peripheral<P = T> + 'd, + uarte: PeripheralRef<'d, T>, txd: PeripheralRef<'d, AnyPin>, cts: Option<PeripheralRef<'d, AnyPin>>, config: Config, ) -> Self { - into_ref!(uarte); - let r = T::regs(); + configure(r, config, cts.is_some()); + txd.set_high(); txd.conf().write(|w| w.dir().output().drive().s0s1()); r.psel.txd.write(|w| unsafe { w.bits(txd.psel_bits()) }); @@ -309,12 +367,6 @@ impl<'d, T: Instance> UarteTx<'d, T> { } r.psel.cts.write(|w| unsafe { w.bits(cts.psel_bits()) }); - r.psel.rxd.write(|w| w.connect().disconnected()); - r.psel.rts.write(|w| w.connect().disconnected()); - - let hardware_flow_control = cts.is_some(); - configure(r, config, hardware_flow_control); - T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; @@ -332,7 +384,7 @@ impl<'d, T: Instance> UarteTx<'d, T> { 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); - self.write_from_ram(&ram_buf).await + self.write_from_ram(ram_buf).await } Err(error) => Err(error), } @@ -340,7 +392,7 @@ 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> { - if buffer.len() == 0 { + if buffer.is_empty() { return Ok(()); } @@ -379,7 +431,7 @@ impl<'d, T: Instance> UarteTx<'d, T> { r.tasks_starttx.write(|w| unsafe { w.bits(1) }); poll_fn(|cx| { - s.endtx_waker.register(cx.waker()); + s.tx_waker.register(cx.waker()); if r.events_endtx.read().bits() != 0 { return Poll::Ready(()); } @@ -402,7 +454,7 @@ impl<'d, T: Instance> UarteTx<'d, T> { 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); - self.blocking_write_from_ram(&ram_buf) + self.blocking_write_from_ram(ram_buf) } Err(error) => Err(error), } @@ -410,7 +462,7 @@ 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> { - if buffer.len() == 0 { + if buffer.is_empty() { return Ok(()); } @@ -458,7 +510,7 @@ impl<'a, T: Instance> Drop for UarteTx<'a, T> { let s = T::state(); - drop_tx_rx(&r, &s); + drop_tx_rx(r, s); } } @@ -470,7 +522,7 @@ impl<'d, T: Instance> UarteRx<'d, T> { rxd: impl Peripheral<P = impl GpioPin> + 'd, config: Config, ) -> Self { - into_ref!(rxd); + into_ref!(uarte, rxd); Self::new_inner(uarte, rxd.map_into(), None, config) } @@ -482,20 +534,28 @@ impl<'d, T: Instance> UarteRx<'d, T> { rts: impl Peripheral<P = impl GpioPin> + 'd, config: Config, ) -> Self { - into_ref!(rxd, rts); + into_ref!(uarte, rxd, rts); Self::new_inner(uarte, rxd.map_into(), Some(rts.map_into()), config) } + /// Check for errors and clear the error register if an error occured. + fn check_and_clear_errors(&mut self) -> Result<(), Error> { + let r = T::regs(); + let err_bits = r.errorsrc.read().bits(); + r.errorsrc.write(|w| unsafe { w.bits(err_bits) }); + ErrorSource::from_bits_truncate(err_bits).check() + } + fn new_inner( - uarte: impl Peripheral<P = T> + 'd, + uarte: PeripheralRef<'d, T>, rxd: PeripheralRef<'d, AnyPin>, rts: Option<PeripheralRef<'d, AnyPin>>, config: Config, ) -> Self { - into_ref!(uarte); - let r = T::regs(); + configure(r, config, rts.is_some()); + rxd.conf().write(|w| w.input().connect().drive().h0h1()); r.psel.rxd.write(|w| unsafe { w.bits(rxd.psel_bits()) }); @@ -505,15 +565,9 @@ impl<'d, T: Instance> UarteRx<'d, T> { } r.psel.rts.write(|w| unsafe { w.bits(rts.psel_bits()) }); - r.psel.txd.write(|w| w.connect().disconnected()); - r.psel.cts.write(|w| w.connect().disconnected()); - T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; - let hardware_flow_control = rts.is_some(); - configure(r, config, hardware_flow_control); - let s = T::state(); s.tx_rx_refcount.store(1, Ordering::Relaxed); @@ -565,14 +619,14 @@ impl<'d, T: Instance> UarteRx<'d, T> { UarteRxWithIdle { rx: self, timer, - ppi_ch1: 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 { + if buffer.is_empty() { return Ok(()); } if buffer.len() > EASY_DMA_SIZE { @@ -588,8 +642,13 @@ impl<'d, T: Instance> UarteRx<'d, T> { let drop = OnDrop::new(move || { trace!("read drop: stopping"); - r.intenclr.write(|w| w.endrx().clear()); + r.intenclr.write(|w| { + w.endrx().clear(); + w.error().clear() + }); r.events_rxto.reset(); + r.events_error.reset(); + r.errorsrc.reset(); r.tasks_stoprx.write(|w| unsafe { w.bits(1) }); while r.events_endrx.read().bits() == 0 {} @@ -601,17 +660,26 @@ impl<'d, T: Instance> UarteRx<'d, T> { r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(len as _) }); r.events_endrx.reset(); - r.intenset.write(|w| w.endrx().set()); + r.events_error.reset(); + r.intenset.write(|w| { + w.endrx().set(); + w.error().set() + }); compiler_fence(Ordering::SeqCst); trace!("startrx"); r.tasks_startrx.write(|w| unsafe { w.bits(1) }); - poll_fn(|cx| { - s.endrx_waker.register(cx.waker()); + let result = poll_fn(|cx| { + s.rx_waker.register(cx.waker()); + + if let Err(e) = self.check_and_clear_errors() { + r.tasks_stoprx.write(|w| unsafe { w.bits(1) }); + return Poll::Ready(Err(e)); + } if r.events_endrx.read().bits() != 0 { - return Poll::Ready(()); + return Poll::Ready(Ok(())); } Poll::Pending }) @@ -621,12 +689,12 @@ impl<'d, T: Instance> UarteRx<'d, T> { r.events_rxstarted.reset(); drop.defuse(); - Ok(()) + result } /// Read bytes until the buffer is filled. pub fn blocking_read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { - if buffer.len() == 0 { + if buffer.is_empty() { return Ok(()); } if buffer.len() > EASY_DMA_SIZE { @@ -642,19 +710,23 @@ impl<'d, T: Instance> UarteRx<'d, T> { r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(len as _) }); r.events_endrx.reset(); - r.intenclr.write(|w| w.endrx().clear()); + r.events_error.reset(); + r.intenclr.write(|w| { + w.endrx().clear(); + w.error().clear() + }); compiler_fence(Ordering::SeqCst); trace!("startrx"); r.tasks_startrx.write(|w| unsafe { w.bits(1) }); - while r.events_endrx.read().bits() == 0 {} + while r.events_endrx.read().bits() == 0 && r.events_error.read().bits() == 0 {} compiler_fence(Ordering::SeqCst); r.events_rxstarted.reset(); - Ok(()) + self.check_and_clear_errors() } } @@ -672,7 +744,7 @@ impl<'a, T: Instance> Drop for UarteRx<'a, T> { let s = T::state(); - drop_tx_rx(&r, &s); + drop_tx_rx(r, s); } } @@ -703,7 +775,7 @@ impl<'d, T: Instance, U: TimerInstance> UarteRxWithIdle<'d, T, U> { /// /// Returns the amount of bytes read. pub async fn read_until_idle(&mut self, buffer: &mut [u8]) -> Result<usize, Error> { - if buffer.len() == 0 { + if buffer.is_empty() { return Ok(0); } if buffer.len() > EASY_DMA_SIZE { @@ -721,8 +793,12 @@ impl<'d, T: Instance, U: TimerInstance> UarteRxWithIdle<'d, T, U> { let drop = OnDrop::new(|| { self.timer.stop(); - r.intenclr.write(|w| w.endrx().clear()); + r.intenclr.write(|w| { + w.endrx().clear(); + w.error().clear() + }); r.events_rxto.reset(); + r.events_error.reset(); r.tasks_stoprx.write(|w| unsafe { w.bits(1) }); while r.events_endrx.read().bits() == 0 {} @@ -732,17 +808,27 @@ impl<'d, T: Instance, U: TimerInstance> UarteRxWithIdle<'d, T, U> { r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(len as _) }); r.events_endrx.reset(); - r.intenset.write(|w| w.endrx().set()); + r.events_error.reset(); + r.intenset.write(|w| { + w.endrx().set(); + w.error().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(()); + let result = poll_fn(|cx| { + s.rx_waker.register(cx.waker()); + + if let Err(e) = self.rx.check_and_clear_errors() { + r.tasks_stoprx.write(|w| unsafe { w.bits(1) }); + return Poll::Ready(Err(e)); } + if r.events_endrx.read().bits() != 0 { + return Poll::Ready(Ok(())); + } + Poll::Pending }) .await; @@ -755,14 +841,14 @@ impl<'d, T: Instance, U: TimerInstance> UarteRxWithIdle<'d, T, U> { drop.defuse(); - Ok(n) + result.map(|_| 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<usize, Error> { - if buffer.len() == 0 { + if buffer.is_empty() { return Ok(0); } if buffer.len() > EASY_DMA_SIZE { @@ -780,13 +866,17 @@ impl<'d, T: Instance, U: TimerInstance> UarteRxWithIdle<'d, T, U> { r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(len as _) }); r.events_endrx.reset(); - r.intenclr.write(|w| w.endrx().clear()); + r.events_error.reset(); + r.intenclr.write(|w| { + w.endrx().clear(); + w.error().clear() + }); compiler_fence(Ordering::SeqCst); r.tasks_startrx.write(|w| unsafe { w.bits(1) }); - while r.events_endrx.read().bits() == 0 {} + while r.events_endrx.read().bits() == 0 && r.events_error.read().bits() == 0 {} compiler_fence(Ordering::SeqCst); let n = r.rxd.amount.read().amount().bits() as usize; @@ -794,7 +884,7 @@ impl<'d, T: Instance, U: TimerInstance> UarteRxWithIdle<'d, T, U> { self.timer.stop(); r.events_rxstarted.reset(); - Ok(n) + self.rx.check_and_clear_errors().map(|_| n) } } @@ -872,15 +962,15 @@ pub(crate) mod sealed { use super::*; pub struct State { - pub endrx_waker: AtomicWaker, - pub endtx_waker: AtomicWaker, + pub rx_waker: AtomicWaker, + pub tx_waker: AtomicWaker, pub tx_rx_refcount: AtomicU8, } impl State { pub const fn new() -> Self { Self { - endrx_waker: AtomicWaker::new(), - endtx_waker: AtomicWaker::new(), + rx_waker: AtomicWaker::new(), + tx_waker: AtomicWaker::new(), tx_rx_refcount: AtomicU8::new(0), } } diff --git a/embassy-nrf/src/util.rs b/embassy-nrf/src/util.rs index cd0f59490..13aba7dec 100644 --- a/embassy-nrf/src/util.rs +++ b/embassy-nrf/src/util.rs @@ -1,8 +1,22 @@ +#![allow(dead_code)] use core::mem; const SRAM_LOWER: usize = 0x2000_0000; const SRAM_UPPER: usize = 0x3000_0000; +// #![feature(const_slice_ptr_len)] +// https://github.com/rust-lang/rust/issues/71146 +pub(crate) fn slice_ptr_len<T>(ptr: *const [T]) -> usize { + use core::ptr::NonNull; + let ptr = ptr.cast_mut(); + if let Some(ptr) = NonNull::new(ptr) { + ptr.len() + } else { + // We know ptr is null, so we know ptr.wrapping_byte_add(1) is not null. + NonNull::new(ptr.wrapping_byte_add(1)).unwrap().len() + } +} + // TODO: replace transmutes with core::ptr::metadata once it's stable pub(crate) fn slice_ptr_parts<T>(slice: *const [T]) -> (*const T, usize) { unsafe { mem::transmute(slice) } @@ -20,7 +34,6 @@ pub(crate) fn slice_in_ram<T>(slice: *const [T]) -> bool { } /// Return an error if slice is not in RAM. Skips check if slice is zero-length. -#[cfg(not(feature = "nrf51"))] pub(crate) fn slice_in_ram_or<T, E>(slice: *const [T], err: E) -> Result<(), E> { let (_, len) = slice_ptr_parts(slice); if len == 0 || slice_in_ram(slice) { diff --git a/embassy-rp/src/gpio.rs b/embassy-rp/src/gpio.rs index 7eb57bbe6..62eeb4cf6 100644 --- a/embassy-rp/src/gpio.rs +++ b/embassy-rp/src/gpio.rs @@ -8,6 +8,7 @@ use core::task::{Context, Poll}; use embassy_hal_internal::{impl_peripheral, into_ref, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; +use self::sealed::Pin as _; use crate::interrupt::InterruptExt; use crate::pac::common::{Reg, RW}; use crate::pac::SIO; @@ -105,14 +106,14 @@ pub struct DormantWakeConfig { } /// GPIO input driver. -pub struct Input<'d, T: Pin> { - pin: Flex<'d, T>, +pub struct Input<'d> { + pin: Flex<'d>, } -impl<'d, T: Pin> Input<'d, T> { +impl<'d> Input<'d> { /// Create GPIO input driver for a [Pin] with the provided [Pull] configuration. #[inline] - pub fn new(pin: impl Peripheral<P = T> + 'd, pull: Pull) -> Self { + pub fn new(pin: impl Peripheral<P = impl Pin> + 'd, pull: Pull) -> Self { let mut pin = Flex::new(pin); pin.set_as_input(); pin.set_pull(pull); @@ -175,7 +176,7 @@ impl<'d, T: Pin> Input<'d, T> { /// Configure dormant wake. #[inline] - pub fn dormant_wake(&mut self, cfg: DormantWakeConfig) -> DormantWake<T> { + pub fn dormant_wake(&mut self, cfg: DormantWakeConfig) -> DormantWake<'_> { self.pin.dormant_wake(cfg) } } @@ -255,14 +256,12 @@ fn IO_IRQ_QSPI() { } #[must_use = "futures do nothing unless you `.await` or poll them"] -struct InputFuture<'a, T: Pin> { - pin: PeripheralRef<'a, T>, +struct InputFuture<'d> { + pin: PeripheralRef<'d, AnyPin>, } -impl<'d, T: Pin> InputFuture<'d, T> { - /// Create a new future wiating for input trigger. - pub fn new(pin: impl Peripheral<P = T> + 'd, level: InterruptTrigger) -> Self { - into_ref!(pin); +impl<'d> InputFuture<'d> { + fn new(pin: PeripheralRef<'d, AnyPin>, level: InterruptTrigger) -> Self { 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 @@ -305,7 +304,7 @@ impl<'d, T: Pin> InputFuture<'d, T> { } } -impl<'d, T: Pin> Future for InputFuture<'d, T> { +impl<'d> Future for InputFuture<'d> { type Output = (); fn poll(self: FuturePin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { @@ -344,14 +343,14 @@ impl<'d, T: Pin> Future for InputFuture<'d, T> { } /// GPIO output driver. -pub struct Output<'d, T: Pin> { - pin: Flex<'d, T>, +pub struct Output<'d> { + pin: Flex<'d>, } -impl<'d, T: Pin> Output<'d, T> { +impl<'d> Output<'d> { /// Create GPIO output driver for a [Pin] with the provided [Level]. #[inline] - pub fn new(pin: impl Peripheral<P = T> + 'd, initial_output: Level) -> Self { + pub fn new(pin: impl Peripheral<P = impl Pin> + 'd, initial_output: Level) -> Self { let mut pin = Flex::new(pin); match initial_output { Level::High => pin.set_high(), @@ -418,14 +417,14 @@ impl<'d, T: Pin> Output<'d, T> { } /// GPIO output open-drain. -pub struct OutputOpenDrain<'d, T: Pin> { - pin: Flex<'d, T>, +pub struct OutputOpenDrain<'d> { + pin: Flex<'d>, } -impl<'d, T: Pin> OutputOpenDrain<'d, T> { +impl<'d> OutputOpenDrain<'d> { /// Create GPIO output driver for a [Pin] in open drain mode with the provided [Level]. #[inline] - pub fn new(pin: impl Peripheral<P = T> + 'd, initial_output: Level) -> Self { + pub fn new(pin: impl Peripheral<P = impl Pin> + 'd, initial_output: Level) -> Self { let mut pin = Flex::new(pin); pin.set_low(); match initial_output { @@ -548,17 +547,17 @@ impl<'d, T: Pin> OutputOpenDrain<'d, T> { /// This pin can be either an input or output pin. The output level register bit will remain /// set while not in output mode, so the pin's level will be 'remembered' when it is not in output /// mode. -pub struct Flex<'d, T: Pin> { - pin: PeripheralRef<'d, T>, +pub struct Flex<'d> { + pin: PeripheralRef<'d, AnyPin>, } -impl<'d, T: Pin> Flex<'d, T> { +impl<'d> Flex<'d> { /// Wrap the pin in a `Flex`. /// /// The pin remains disconnected. The initial output level is unspecified, but can be changed /// before the pin is put into output mode. #[inline] - pub fn new(pin: impl Peripheral<P = T> + 'd) -> Self { + pub fn new(pin: impl Peripheral<P = impl Pin> + 'd) -> Self { into_ref!(pin); pin.pad_ctrl().write(|w| { @@ -569,7 +568,7 @@ impl<'d, T: Pin> Flex<'d, T> { w.set_funcsel(pac::io::vals::Gpio0ctrlFuncsel::SIO_0 as _); }); - Self { pin } + Self { pin: pin.map_into() } } #[inline] @@ -716,36 +715,36 @@ impl<'d, T: Pin> Flex<'d, T> { /// Wait until the pin is high. If it is already high, return immediately. #[inline] pub async fn wait_for_high(&mut self) { - InputFuture::new(&mut self.pin, InterruptTrigger::LevelHigh).await; + InputFuture::new(self.pin.reborrow(), InterruptTrigger::LevelHigh).await; } /// Wait until the pin is low. If it is already low, return immediately. #[inline] pub async fn wait_for_low(&mut self) { - InputFuture::new(&mut self.pin, InterruptTrigger::LevelLow).await; + InputFuture::new(self.pin.reborrow(), InterruptTrigger::LevelLow).await; } /// Wait for the pin to undergo a transition from low to high. #[inline] pub async fn wait_for_rising_edge(&mut self) { - InputFuture::new(&mut self.pin, InterruptTrigger::EdgeHigh).await; + InputFuture::new(self.pin.reborrow(), InterruptTrigger::EdgeHigh).await; } /// Wait for the pin to undergo a transition from high to low. #[inline] pub async fn wait_for_falling_edge(&mut self) { - InputFuture::new(&mut self.pin, InterruptTrigger::EdgeLow).await; + InputFuture::new(self.pin.reborrow(), InterruptTrigger::EdgeLow).await; } /// Wait for the pin to undergo any transition, i.e low to high OR high to low. #[inline] pub async fn wait_for_any_edge(&mut self) { - InputFuture::new(&mut self.pin, InterruptTrigger::AnyEdge).await; + InputFuture::new(self.pin.reborrow(), InterruptTrigger::AnyEdge).await; } /// Configure dormant wake. #[inline] - pub fn dormant_wake(&mut self, cfg: DormantWakeConfig) -> DormantWake<T> { + pub fn dormant_wake(&mut self, cfg: DormantWakeConfig) -> DormantWake<'_> { let idx = self.pin._pin() as usize; self.pin.io().intr(idx / 8).write(|w| { w.set_edge_high(idx % 8, cfg.edge_high); @@ -764,7 +763,7 @@ impl<'d, T: Pin> Flex<'d, T> { } } -impl<'d, T: Pin> Drop for Flex<'d, T> { +impl<'d> Drop for Flex<'d> { #[inline] fn drop(&mut self) { let idx = self.pin._pin() as usize; @@ -782,12 +781,12 @@ impl<'d, T: Pin> Drop for Flex<'d, T> { } /// Dormant wake driver. -pub struct DormantWake<'w, T: Pin> { - pin: PeripheralRef<'w, T>, +pub struct DormantWake<'w> { + pin: PeripheralRef<'w, AnyPin>, cfg: DormantWakeConfig, } -impl<'w, T: Pin> Drop for DormantWake<'w, T> { +impl<'w> Drop for DormantWake<'w> { fn drop(&mut self) { let idx = self.pin._pin() as usize; self.pin.io().intr(idx / 8).write(|w| { @@ -816,7 +815,7 @@ pub(crate) mod sealed { #[inline] fn _bank(&self) -> Bank { - match self.pin_bank() & 0x20 { + match self.pin_bank() >> 5 { #[cfg(feature = "qspi-as-gpio")] 1 => Bank::Qspi, _ => Bank::Bank0, @@ -890,6 +889,17 @@ pub struct AnyPin { pin_bank: u8, } +impl AnyPin { + /// Unsafely create a new type-erased pin. + /// + /// # Safety + /// + /// You must ensure that you’re only using one instance of this type at a time. + pub unsafe fn steal(pin_bank: u8) -> Self { + Self { pin_bank } + } +} + impl_peripheral!(AnyPin); impl Pin for AnyPin {} @@ -970,7 +980,7 @@ mod eh02 { use super::*; - impl<'d, T: Pin> embedded_hal_02::digital::v2::InputPin for Input<'d, T> { + impl<'d> embedded_hal_02::digital::v2::InputPin for Input<'d> { type Error = Infallible; fn is_high(&self) -> Result<bool, Self::Error> { @@ -982,7 +992,7 @@ mod eh02 { } } - impl<'d, T: Pin> embedded_hal_02::digital::v2::OutputPin for Output<'d, T> { + impl<'d> embedded_hal_02::digital::v2::OutputPin for Output<'d> { type Error = Infallible; fn set_high(&mut self) -> Result<(), Self::Error> { @@ -994,7 +1004,7 @@ mod eh02 { } } - impl<'d, T: Pin> embedded_hal_02::digital::v2::StatefulOutputPin for Output<'d, T> { + impl<'d> embedded_hal_02::digital::v2::StatefulOutputPin for Output<'d> { fn is_set_high(&self) -> Result<bool, Self::Error> { Ok(self.is_set_high()) } @@ -1004,7 +1014,7 @@ mod eh02 { } } - impl<'d, T: Pin> embedded_hal_02::digital::v2::ToggleableOutputPin for Output<'d, T> { + impl<'d> embedded_hal_02::digital::v2::ToggleableOutputPin for Output<'d> { type Error = Infallible; #[inline] fn toggle(&mut self) -> Result<(), Self::Error> { @@ -1012,7 +1022,7 @@ mod eh02 { } } - impl<'d, T: Pin> embedded_hal_02::digital::v2::InputPin for OutputOpenDrain<'d, T> { + impl<'d> embedded_hal_02::digital::v2::InputPin for OutputOpenDrain<'d> { type Error = Infallible; fn is_high(&self) -> Result<bool, Self::Error> { @@ -1024,7 +1034,7 @@ mod eh02 { } } - impl<'d, T: Pin> embedded_hal_02::digital::v2::OutputPin for OutputOpenDrain<'d, T> { + impl<'d> embedded_hal_02::digital::v2::OutputPin for OutputOpenDrain<'d> { type Error = Infallible; #[inline] @@ -1038,7 +1048,7 @@ mod eh02 { } } - impl<'d, T: Pin> embedded_hal_02::digital::v2::StatefulOutputPin for OutputOpenDrain<'d, T> { + impl<'d> embedded_hal_02::digital::v2::StatefulOutputPin for OutputOpenDrain<'d> { fn is_set_high(&self) -> Result<bool, Self::Error> { Ok(self.is_set_high()) } @@ -1048,7 +1058,7 @@ mod eh02 { } } - impl<'d, T: Pin> embedded_hal_02::digital::v2::ToggleableOutputPin for OutputOpenDrain<'d, T> { + impl<'d> embedded_hal_02::digital::v2::ToggleableOutputPin for OutputOpenDrain<'d> { type Error = Infallible; #[inline] fn toggle(&mut self) -> Result<(), Self::Error> { @@ -1056,7 +1066,7 @@ mod eh02 { } } - impl<'d, T: Pin> embedded_hal_02::digital::v2::InputPin for Flex<'d, T> { + impl<'d> embedded_hal_02::digital::v2::InputPin for Flex<'d> { type Error = Infallible; fn is_high(&self) -> Result<bool, Self::Error> { @@ -1068,7 +1078,7 @@ mod eh02 { } } - impl<'d, T: Pin> embedded_hal_02::digital::v2::OutputPin for Flex<'d, T> { + impl<'d> embedded_hal_02::digital::v2::OutputPin for Flex<'d> { type Error = Infallible; fn set_high(&mut self) -> Result<(), Self::Error> { @@ -1080,7 +1090,7 @@ mod eh02 { } } - impl<'d, T: Pin> embedded_hal_02::digital::v2::StatefulOutputPin for Flex<'d, T> { + impl<'d> embedded_hal_02::digital::v2::StatefulOutputPin for Flex<'d> { fn is_set_high(&self) -> Result<bool, Self::Error> { Ok(self.is_set_high()) } @@ -1090,7 +1100,7 @@ mod eh02 { } } - impl<'d, T: Pin> embedded_hal_02::digital::v2::ToggleableOutputPin for Flex<'d, T> { + impl<'d> embedded_hal_02::digital::v2::ToggleableOutputPin for Flex<'d> { type Error = Infallible; #[inline] fn toggle(&mut self) -> Result<(), Self::Error> { @@ -1099,11 +1109,11 @@ mod eh02 { } } -impl<'d, T: Pin> embedded_hal_1::digital::ErrorType for Input<'d, T> { +impl<'d> embedded_hal_1::digital::ErrorType for Input<'d> { type Error = Infallible; } -impl<'d, T: Pin> embedded_hal_1::digital::InputPin for Input<'d, T> { +impl<'d> embedded_hal_1::digital::InputPin for Input<'d> { fn is_high(&mut self) -> Result<bool, Self::Error> { Ok((*self).is_high()) } @@ -1113,11 +1123,11 @@ impl<'d, T: Pin> embedded_hal_1::digital::InputPin for Input<'d, T> { } } -impl<'d, T: Pin> embedded_hal_1::digital::ErrorType for Output<'d, T> { +impl<'d> embedded_hal_1::digital::ErrorType for Output<'d> { type Error = Infallible; } -impl<'d, T: Pin> embedded_hal_1::digital::OutputPin for Output<'d, T> { +impl<'d> embedded_hal_1::digital::OutputPin for Output<'d> { fn set_high(&mut self) -> Result<(), Self::Error> { Ok(self.set_high()) } @@ -1127,7 +1137,7 @@ impl<'d, T: Pin> embedded_hal_1::digital::OutputPin for Output<'d, T> { } } -impl<'d, T: Pin> embedded_hal_1::digital::StatefulOutputPin for Output<'d, T> { +impl<'d> embedded_hal_1::digital::StatefulOutputPin for Output<'d> { fn is_set_high(&mut self) -> Result<bool, Self::Error> { Ok((*self).is_set_high()) } @@ -1137,11 +1147,11 @@ impl<'d, T: Pin> embedded_hal_1::digital::StatefulOutputPin for Output<'d, T> { } } -impl<'d, T: Pin> embedded_hal_1::digital::ErrorType for OutputOpenDrain<'d, T> { +impl<'d> embedded_hal_1::digital::ErrorType for OutputOpenDrain<'d> { type Error = Infallible; } -impl<'d, T: Pin> embedded_hal_1::digital::OutputPin for OutputOpenDrain<'d, T> { +impl<'d> embedded_hal_1::digital::OutputPin for OutputOpenDrain<'d> { fn set_high(&mut self) -> Result<(), Self::Error> { Ok(self.set_high()) } @@ -1151,7 +1161,7 @@ impl<'d, T: Pin> embedded_hal_1::digital::OutputPin for OutputOpenDrain<'d, T> { } } -impl<'d, T: Pin> embedded_hal_1::digital::StatefulOutputPin for OutputOpenDrain<'d, T> { +impl<'d> embedded_hal_1::digital::StatefulOutputPin for OutputOpenDrain<'d> { fn is_set_high(&mut self) -> Result<bool, Self::Error> { Ok((*self).is_set_high()) } @@ -1161,7 +1171,7 @@ impl<'d, T: Pin> embedded_hal_1::digital::StatefulOutputPin for OutputOpenDrain< } } -impl<'d, T: Pin> embedded_hal_1::digital::InputPin for OutputOpenDrain<'d, T> { +impl<'d> embedded_hal_1::digital::InputPin for OutputOpenDrain<'d> { fn is_high(&mut self) -> Result<bool, Self::Error> { Ok((*self).is_high()) } @@ -1171,11 +1181,11 @@ impl<'d, T: Pin> embedded_hal_1::digital::InputPin for OutputOpenDrain<'d, T> { } } -impl<'d, T: Pin> embedded_hal_1::digital::ErrorType for Flex<'d, T> { +impl<'d> embedded_hal_1::digital::ErrorType for Flex<'d> { type Error = Infallible; } -impl<'d, T: Pin> embedded_hal_1::digital::InputPin for Flex<'d, T> { +impl<'d> embedded_hal_1::digital::InputPin for Flex<'d> { fn is_high(&mut self) -> Result<bool, Self::Error> { Ok((*self).is_high()) } @@ -1185,7 +1195,7 @@ impl<'d, T: Pin> embedded_hal_1::digital::InputPin for Flex<'d, T> { } } -impl<'d, T: Pin> embedded_hal_1::digital::OutputPin for Flex<'d, T> { +impl<'d> embedded_hal_1::digital::OutputPin for Flex<'d> { fn set_high(&mut self) -> Result<(), Self::Error> { Ok(self.set_high()) } @@ -1195,7 +1205,7 @@ impl<'d, T: Pin> embedded_hal_1::digital::OutputPin for Flex<'d, T> { } } -impl<'d, T: Pin> embedded_hal_1::digital::StatefulOutputPin for Flex<'d, T> { +impl<'d> embedded_hal_1::digital::StatefulOutputPin for Flex<'d> { fn is_set_high(&mut self) -> Result<bool, Self::Error> { Ok((*self).is_set_high()) } @@ -1205,7 +1215,7 @@ impl<'d, T: Pin> embedded_hal_1::digital::StatefulOutputPin for Flex<'d, T> { } } -impl<'d, T: Pin> embedded_hal_async::digital::Wait for Flex<'d, T> { +impl<'d> embedded_hal_async::digital::Wait for Flex<'d> { async fn wait_for_high(&mut self) -> Result<(), Self::Error> { self.wait_for_high().await; Ok(()) @@ -1232,7 +1242,7 @@ impl<'d, T: Pin> embedded_hal_async::digital::Wait for Flex<'d, T> { } } -impl<'d, T: Pin> embedded_hal_async::digital::Wait for Input<'d, T> { +impl<'d> embedded_hal_async::digital::Wait for Input<'d> { async fn wait_for_high(&mut self) -> Result<(), Self::Error> { self.wait_for_high().await; Ok(()) @@ -1259,7 +1269,7 @@ impl<'d, T: Pin> embedded_hal_async::digital::Wait for Input<'d, T> { } } -impl<'d, T: Pin> embedded_hal_async::digital::Wait for OutputOpenDrain<'d, T> { +impl<'d> embedded_hal_async::digital::Wait for OutputOpenDrain<'d> { async fn wait_for_high(&mut self) -> Result<(), Self::Error> { self.wait_for_high().await; Ok(()) diff --git a/embassy-rp/src/i2c.rs b/embassy-rp/src/i2c.rs index 74d015792..ac0eac96d 100644 --- a/embassy-rp/src/i2c.rs +++ b/embassy-rp/src/i2c.rs @@ -43,6 +43,18 @@ pub enum Error { AddressReserved(u16), } +/// I2C Config error +#[derive(Debug, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ConfigError { + /// Max i2c speed is 1MHz + FrequencyTooHigh, + /// The sys clock is too slow to support given frequency + ClockTooSlow, + /// The sys clock is too fast to support given frequency + ClockTooFast, +} + /// I2C config. #[non_exhaustive] #[derive(Copy, Clone)] @@ -365,37 +377,32 @@ impl<'d, T: Instance + 'd, M: Mode> I2c<'d, T, M> { ) -> 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 set_up_i2c_pin(&scl); set_up_i2c_pin(&sda); + let mut me = Self { phantom: PhantomData }; + + if let Err(e) = me.set_config_inner(&config) { + panic!("Error configuring i2c: {:?}", e); + } + + me + } + + fn set_config_inner(&mut self, config: &Config) -> Result<(), ConfigError> { + if config.frequency > 1_000_000 { + return Err(ConfigError::FrequencyTooHigh); + } + + let p = T::regs(); + + p.ic_enable().write(|w| w.set_enable(false)); + // Configure baudrate // There are some subtleties to I2C timing which we are completely @@ -408,10 +415,12 @@ impl<'d, T: Instance + 'd, M: Mode> I2c<'d, T, M> { 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); + if hcnt > 0xffff || lcnt > 0xffff { + return Err(ConfigError::ClockTooFast); + } + if hcnt < 8 || lcnt < 8 { + return Err(ConfigError::ClockTooSlow); + } // Per I2C-bus specification a device in standard or fast mode must // internally provide a hold time of at least 300ns for the SDA @@ -424,14 +433,19 @@ impl<'d, T: Instance + 'd, M: Mode> I2c<'d, T, M> { ((clk_base * 3) / 10_000_000) + 1 } else { // fast mode plus requires a clk_base > 32MHz - assert!(clk_base >= 32_000_000); + if clk_base <= 32_000_000 { + return Err(ConfigError::ClockTooSlow); + } // 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); + + if sda_tx_hold_count > lcnt - 2 { + return Err(ConfigError::ClockTooSlow); + } 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)); @@ -440,10 +454,9 @@ impl<'d, T: Instance + 'd, M: Mode> I2c<'d, T, M> { 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 } + Ok(()) } fn setup(addr: u16) -> Result<(), Error> { @@ -757,6 +770,15 @@ where } } +impl<'d, T: Instance, M: Mode> embassy_embedded_hal::SetConfig for I2c<'d, T, M> { + type Config = Config; + type ConfigError = ConfigError; + + fn set_config(&mut self, config: &Self::Config) -> Result<(), Self::ConfigError> { + self.set_config_inner(config) + } +} + /// Check if address is reserved. pub fn i2c_reserved_addr(addr: u16) -> bool { ((addr & 0x78) == 0 || (addr & 0x78) == 0x78) && addr != 0 diff --git a/embassy-rp/src/i2c_slave.rs b/embassy-rp/src/i2c_slave.rs index 721b7a1f6..97ca17295 100644 --- a/embassy-rp/src/i2c_slave.rs +++ b/embassy-rp/src/i2c_slave.rs @@ -21,6 +21,16 @@ pub enum Error { Abort(AbortReason), /// User passed in a response buffer that was 0 length InvalidResponseBufferLength, + /// The response buffer length was too short to contain the message + /// + /// The length parameter will always be the length of the buffer, and is + /// provided as a convenience for matching alongside `Command::Write`. + PartialWrite(usize), + /// The response buffer length was too short to contain the message + /// + /// The length parameter will always be the length of the buffer, and is + /// provided as a convenience for matching alongside `Command::GeneralCall`. + PartialGeneralCall(usize), } /// Received command @@ -56,17 +66,24 @@ pub enum ReadStatus { pub struct Config { /// Target Address pub addr: u16, + /// Control if the peripheral should ack to and report general calls. + pub general_call: bool, } impl Default for Config { fn default() -> Self { - Self { addr: 0x55 } + Self { + addr: 0x55, + general_call: true, + } } } /// I2CSlave driver. pub struct I2cSlave<'d, T: Instance> { phantom: PhantomData<&'d mut T>, + pending_byte: Option<u8>, + config: Config, } impl<'d, T: Instance> I2cSlave<'d, T> { @@ -83,6 +100,25 @@ impl<'d, T: Instance> I2cSlave<'d, T> { assert!(!i2c_reserved_addr(config.addr)); assert!(config.addr != 0); + // Configure SCL & SDA pins + set_up_i2c_pin(&scl); + set_up_i2c_pin(&sda); + + let mut ret = Self { + phantom: PhantomData, + pending_byte: None, + config, + }; + + ret.reset(); + + ret + } + + /// Reset the i2c peripheral. If you cancel a respond_to_read, you may stall the bus. + /// You can recover the bus by calling this function, but doing so will almost certainly cause + /// an i/o error in the master. + pub fn reset(&mut self) { let p = T::regs(); let reset = T::reset(); @@ -91,12 +127,24 @@ impl<'d, T: Instance> I2cSlave<'d, T> { p.ic_enable().write(|w| w.set_enable(false)); - p.ic_sar().write(|w| w.set_ic_sar(config.addr)); + p.ic_sar().write(|w| w.set_ic_sar(self.config.addr)); p.ic_con().modify(|w| { w.set_master_mode(false); w.set_ic_slave_disable(false); w.set_tx_empty_ctrl(true); + w.set_rx_fifo_full_hld_ctrl(true); + + // This typically makes no sense for a slave, but it is used to + // tune spike suppression, according to the datasheet. + w.set_speed(pac::i2c::vals::Speed::FAST); + + // Generate stop interrupts for general calls + // This also causes stop interrupts for other devices on the bus but those will not be + // propagated up to the application. + w.set_stop_det_ifaddressed(!self.config.general_call); }); + p.ic_ack_general_call() + .write(|w| w.set_ack_gen_call(self.config.general_call)); // Set FIFO watermarks to 1 to make things simpler. This is encoded // by a register value of 0. Rx watermark should never change, but Tx watermark will be @@ -104,10 +152,6 @@ impl<'d, T: Instance> I2cSlave<'d, T> { 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 - set_up_i2c_pin(&scl); - set_up_i2c_pin(&sda); - // Clear interrupts p.ic_clr_intr().read(); @@ -118,8 +162,6 @@ impl<'d, T: Instance> I2cSlave<'d, T> { p.ic_intr_mask().write_value(i2c::regs::IcIntrMask(0)); T::Interrupt::unpend(); unsafe { T::Interrupt::enable() }; - - Self { phantom: PhantomData } } /// Calls `f` to check if we are ready or not. @@ -133,8 +175,6 @@ impl<'d, T: Instance> I2cSlave<'d, T> { future::poll_fn(|cx| { let r = f(self); - trace!("intr p: {:013b}", T::regs().ic_raw_intr_stat().read().0); - if r.is_pending() { T::waker().register(cx.waker()); g(self); @@ -146,71 +186,103 @@ impl<'d, T: Instance> I2cSlave<'d, T> { } #[inline(always)] - fn drain_fifo(&mut self, buffer: &mut [u8], offset: usize) -> usize { + fn drain_fifo(&mut self, buffer: &mut [u8], offset: &mut usize) { let p = T::regs(); - let len = p.ic_rxflr().read().rxflr() as usize; - let end = offset + len; - for i in offset..end { - buffer[i] = p.ic_data_cmd().read().dat(); - } - end - } - #[inline(always)] - fn write_to_fifo(&mut self, buffer: &[u8]) { - let p = T::regs(); - for byte in buffer { - p.ic_data_cmd().write(|w| w.set_dat(*byte)); + if let Some(pending) = self.pending_byte.take() { + buffer[*offset] = pending; + *offset += 1; + } + + for b in &mut buffer[*offset..] { + if !p.ic_status().read().rfne() { + break; + } + + let dat = p.ic_data_cmd().read(); + if *offset != 0 && dat.first_data_byte() { + // The RP2040 state machine will keep placing bytes into the + // FIFO, even if they are part of a subsequent write transaction. + // + // Unfortunately merely reading ic_data_cmd will consume that + // byte, the first byte of the next transaction, so we need + // to store it elsewhere + self.pending_byte = Some(dat.dat()); + break; + } + + *b = dat.dat(); + *offset += 1; } } /// Wait asynchronously for commands from an I2C master. - /// `buffer` is provided in case master does a 'write' and is unused for 'read'. + /// `buffer` is provided in case master does a 'write', 'write read', or 'general call' and is unused for 'read'. pub async fn listen(&mut self, buffer: &mut [u8]) -> Result<Command, Error> { let p = T::regs(); - p.ic_clr_intr().read(); // set rx fifo watermark to 1 byte p.ic_rx_tl().write(|w| w.set_rx_tl(0)); let mut len = 0; - let ret = self - .wait_on( - |me| { - let stat = p.ic_raw_intr_stat().read(); - if p.ic_rxflr().read().rxflr() > 0 { - len = me.drain_fifo(buffer, len); - // we're recieving data, set rx fifo watermark to 12 bytes to reduce interrupt noise - p.ic_rx_tl().write(|w| w.set_rx_tl(11)); - } + self.wait_on( + |me| { + let stat = p.ic_raw_intr_stat().read(); + trace!("ls:{:013b} len:{}", stat.0, len); - if stat.restart_det() && stat.rd_req() { - Poll::Ready(Ok(Command::WriteRead(len))) - } else if stat.gen_call() && stat.stop_det() && len > 0 { - Poll::Ready(Ok(Command::GeneralCall(len))) - } else if stat.stop_det() { - Poll::Ready(Ok(Command::Write(len))) - } else if stat.rd_req() { - Poll::Ready(Ok(Command::Read)) + if p.ic_rxflr().read().rxflr() > 0 || me.pending_byte.is_some() { + me.drain_fifo(buffer, &mut len); + // we're recieving data, set rx fifo watermark to 12 bytes (3/4 full) to reduce interrupt noise + p.ic_rx_tl().write(|w| w.set_rx_tl(11)); + } + + if buffer.len() == len { + if stat.gen_call() { + return Poll::Ready(Err(Error::PartialGeneralCall(buffer.len()))); } else { - Poll::Pending + return Poll::Ready(Err(Error::PartialWrite(buffer.len()))); } - }, - |_me| { - p.ic_intr_mask().modify(|w| { - w.set_m_stop_det(true); - w.set_m_restart_det(true); - w.set_m_gen_call(true); - w.set_m_rd_req(true); - w.set_m_rx_full(true); - }); - }, - ) - .await; + } + trace!("len:{}, pend:{:?}", len, me.pending_byte); + if me.pending_byte.is_some() { + warn!("pending") + } - p.ic_clr_intr().read(); - - ret + if stat.restart_det() && stat.rd_req() { + p.ic_clr_restart_det().read(); + Poll::Ready(Ok(Command::WriteRead(len))) + } else if stat.gen_call() && stat.stop_det() && len > 0 { + p.ic_clr_gen_call().read(); + p.ic_clr_stop_det().read(); + Poll::Ready(Ok(Command::GeneralCall(len))) + } else if stat.stop_det() && len > 0 { + p.ic_clr_stop_det().read(); + Poll::Ready(Ok(Command::Write(len))) + } else if stat.rd_req() { + p.ic_clr_stop_det().read(); + p.ic_clr_restart_det().read(); + p.ic_clr_gen_call().read(); + Poll::Ready(Ok(Command::Read)) + } else if stat.stop_det() { + // clear stuck stop bit + // This can happen if the SDA/SCL pullups are enabled after calling this func + p.ic_clr_stop_det().read(); + Poll::Pending + } else { + Poll::Pending + } + }, + |_me| { + p.ic_intr_mask().write(|w| { + w.set_m_stop_det(true); + w.set_m_restart_det(true); + w.set_m_gen_call(true); + w.set_m_rd_req(true); + w.set_m_rx_full(true); + }); + }, + ) + .await } /// Respond to an I2C master READ command, asynchronously. @@ -223,54 +295,61 @@ impl<'d, T: Instance> I2cSlave<'d, T> { let mut chunks = buffer.chunks(FIFO_SIZE as usize); - let ret = self - .wait_on( - |me| { + self.wait_on( + |me| { + let stat = p.ic_raw_intr_stat().read(); + trace!("rs:{:013b}", stat.0); + + if stat.tx_abrt() { if let Err(abort_reason) = me.read_and_clear_abort_reason() { if let Error::Abort(AbortReason::TxNotEmpty(bytes)) = abort_reason { + p.ic_clr_intr().read(); return Poll::Ready(Ok(ReadStatus::LeftoverBytes(bytes))); } else { return Poll::Ready(Err(abort_reason)); } } + } - if let Some(chunk) = chunks.next() { - me.write_to_fifo(chunk); - - Poll::Pending - } else { - let stat = p.ic_raw_intr_stat().read(); - - if stat.rx_done() && stat.stop_det() { - Poll::Ready(Ok(ReadStatus::Done)) - } else if stat.rd_req() { - Poll::Ready(Ok(ReadStatus::NeedMoreBytes)) - } else { - Poll::Pending - } + if let Some(chunk) = chunks.next() { + for byte in chunk { + p.ic_clr_rd_req().read(); + p.ic_data_cmd().write(|w| w.set_dat(*byte)); } - }, - |_me| { - p.ic_intr_mask().modify(|w| { - w.set_m_stop_det(true); - w.set_m_rx_done(true); - w.set_m_tx_empty(true); - w.set_m_tx_abrt(true); - }) - }, - ) - .await; - p.ic_clr_intr().read(); - - ret + Poll::Pending + } else { + if stat.rx_done() { + p.ic_clr_rx_done().read(); + Poll::Ready(Ok(ReadStatus::Done)) + } else if stat.rd_req() && stat.tx_empty() { + Poll::Ready(Ok(ReadStatus::NeedMoreBytes)) + } else { + Poll::Pending + } + } + }, + |_me| { + p.ic_intr_mask().write(|w| { + w.set_m_rx_done(true); + w.set_m_tx_empty(true); + w.set_m_tx_abrt(true); + }) + }, + ) + .await } /// Respond to reads with the fill byte until the controller stops asking pub async fn respond_till_stop(&mut self, fill: u8) -> Result<(), Error> { + // Send fill bytes a full fifo at a time, to reduce interrupt noise. + // This does mean we'll almost certainly abort the write, but since these are fill bytes, + // we don't care. + let buff = [fill; FIFO_SIZE as usize]; loop { - match self.respond_to_read(&[fill]).await { + match self.respond_to_read(&buff).await { Ok(ReadStatus::NeedMoreBytes) => (), + Ok(ReadStatus::LeftoverBytes(_)) => break Ok(()), Ok(_) => break Ok(()), Err(e) => break Err(e), } @@ -292,14 +371,7 @@ impl<'d, T: Instance> I2cSlave<'d, T> { #[inline(always)] fn read_and_clear_abort_reason(&mut self) -> Result<(), Error> { let p = T::regs(); - let mut abort_reason = p.ic_tx_abrt_source().read(); - - // Mask off fifo flush count - let tx_flush_cnt = abort_reason.tx_flush_cnt(); - abort_reason.set_tx_flush_cnt(0); - - // Mask off master_dis - abort_reason.set_abrt_master_dis(false); + 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 @@ -314,8 +386,8 @@ impl<'d, T: Instance> I2cSlave<'d, T> { AbortReason::NoAcknowledge } else if abort_reason.arb_lost() { AbortReason::ArbitrationLoss - } else if abort_reason.abrt_slvflush_txfifo() { - AbortReason::TxNotEmpty(tx_flush_cnt) + } else if abort_reason.tx_flush_cnt() > 0 { + AbortReason::TxNotEmpty(abort_reason.tx_flush_cnt()) } else { AbortReason::Other(abort_reason.0) }; diff --git a/embassy-rp/src/uart/mod.rs b/embassy-rp/src/uart/mod.rs index 99fce0fc9..f372cb640 100644 --- a/embassy-rp/src/uart/mod.rs +++ b/embassy-rp/src/uart/mod.rs @@ -118,6 +118,17 @@ pub enum Error { Framing, } +/// Read To Break error +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub enum ReadToBreakError { + /// Read this many bytes, but never received a line break. + MissingBreak(usize), + /// Other, standard issue with the serial request + Other(Error), +} + /// Internal DMA state of UART RX. pub struct DmaState { rx_err_waker: AtomicWaker, @@ -274,14 +285,17 @@ impl<'d, T: Instance, M: Mode> UartRx<'d, T, M> { /// Read from UART RX blocking execution until done. pub fn blocking_read(&mut self, mut buffer: &mut [u8]) -> Result<(), Error> { - while buffer.len() > 0 { - let received = self.drain_fifo(buffer)?; + while !buffer.is_empty() { + let received = self.drain_fifo(buffer).map_err(|(_i, e)| e)?; buffer = &mut buffer[received..]; } Ok(()) } - fn drain_fifo(&mut self, buffer: &mut [u8]) -> Result<usize, Error> { + /// Returns Ok(len) if no errors occurred. Returns Err((len, err)) if an error was + /// encountered. in both cases, `len` is the number of *good* bytes copied into + /// `buffer`. + fn drain_fifo(&mut self, buffer: &mut [u8]) -> Result<usize, (usize, Error)> { let r = T::regs(); for (i, b) in buffer.iter_mut().enumerate() { if r.uartfr().read().rxfe() { @@ -291,13 +305,13 @@ impl<'d, T: Instance, M: Mode> UartRx<'d, T, M> { let dr = r.uartdr().read(); if dr.oe() { - return Err(Error::Overrun); + return Err((i, Error::Overrun)); } else if dr.be() { - return Err(Error::Break); + return Err((i, Error::Break)); } else if dr.pe() { - return Err(Error::Parity); + return Err((i, Error::Parity)); } else if dr.fe() { - return Err(Error::Framing); + return Err((i, Error::Framing)); } else { *b = dr.data(); } @@ -389,7 +403,7 @@ impl<'d, T: Instance> UartRx<'d, T, Async> { } { Ok(len) if len < buffer.len() => &mut buffer[len..], Ok(_) => return Ok(()), - Err(e) => return Err(e), + Err((_i, e)) => return Err(e), }; // start a dma transfer. if errors have happened in the interim some error @@ -426,13 +440,25 @@ impl<'d, T: Instance> UartRx<'d, T, Async> { .await; let errors = match transfer_result { - Either::First(()) => return Ok(()), - Either::Second(e) => e, + Either::First(()) => { + // We're here because the DMA finished, BUT if an error occurred on the LAST + // byte, then we may still need to grab the error state! + Uartris(T::dma_state().rx_errs.swap(0, Ordering::Relaxed) as u32) + } + Either::Second(e) => { + // We're here because we errored, which means this is the error that + // was problematic. + e + } }; + // If we got no error, just return at this point if errors.0 == 0 { return Ok(()); - } else if errors.oeris() { + } + + // If we DID get an error, we need to figure out which one it was. + if errors.oeris() { return Err(Error::Overrun); } else if errors.beris() { return Err(Error::Break); @@ -443,6 +469,173 @@ impl<'d, T: Instance> UartRx<'d, T, Async> { } unreachable!("unrecognized rx error"); } + + /// Read from the UART, waiting for a line break. + /// + /// We read until one of the following occurs: + /// + /// * We read `buffer.len()` bytes without a line break + /// * returns `Err(ReadToBreakError::MissingBreak(buffer.len()))` + /// * We read `n` bytes then a line break occurs + /// * returns `Ok(n)` + /// * We encounter some error OTHER than a line break + /// * returns `Err(ReadToBreakError::Other(error))` + /// + /// **NOTE**: you MUST provide a buffer one byte larger than your largest expected + /// message to reliably detect the framing on one single call to `read_to_break()`. + /// + /// * If you expect a message of 20 bytes + line break, and provide a 20-byte buffer: + /// * The first call to `read_to_break()` will return `Err(ReadToBreakError::MissingBreak(20))` + /// * The next call to `read_to_break()` will immediately return `Ok(0)`, from the "stale" line break + /// * If you expect a message of 20 bytes + line break, and provide a 21-byte buffer: + /// * The first call to `read_to_break()` will return `Ok(20)`. + /// * The next call to `read_to_break()` will work as expected + pub async fn read_to_break(&mut self, buffer: &mut [u8]) -> Result<usize, ReadToBreakError> { + // 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 sbuffer = match { + let limit = buffer.len().min(32); + self.drain_fifo(&mut buffer[0..limit]) + } { + // Drained fifo, still some room left! + Ok(len) if len < buffer.len() => &mut buffer[len..], + // Drained (some/all of the fifo), no room left + Ok(len) => return Err(ReadToBreakError::MissingBreak(len)), + // We got a break WHILE draining the FIFO, return what we did get before the break + Err((i, Error::Break)) => return Ok(i), + // Some other error, just return the error + Err((_i, e)) => return Err(ReadToBreakError::Other(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 mut 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(&mut ch, T::regs().uartdr().as_ptr() as *const _, sbuffer, 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; + + // Figure out our error state + let errors = match transfer_result { + Either::First(()) => { + // We're here because the DMA finished, BUT if an error occurred on the LAST + // byte, then we may still need to grab the error state! + Uartris(T::dma_state().rx_errs.swap(0, Ordering::Relaxed) as u32) + } + Either::Second(e) => { + // We're here because we errored, which means this is the error that + // was problematic. + e + } + }; + + if errors.0 == 0 { + // No errors? That means we filled the buffer without a line break. + // For THIS function, that's a problem. + return Err(ReadToBreakError::MissingBreak(buffer.len())); + } else if errors.beris() { + // We got a Line Break! By this point, we've finished/aborted the DMA + // transaction, which means that we need to figure out where it left off + // by looking at the write_addr. + // + // First, we do a sanity check to make sure the write value is within the + // range of DMA we just did. + let sval = buffer.as_ptr() as usize; + let eval = sval + buffer.len(); + + // This is the address where the DMA would write to next + let next_addr = ch.regs().write_addr().read() as usize; + + // If we DON'T end up inside the range, something has gone really wrong. + // Note that it's okay that `eval` is one past the end of the slice, as + // this is where the write pointer will end up at the end of a full + // transfer. + if (next_addr < sval) || (next_addr > eval) { + unreachable!("UART DMA reported invalid `write_addr`"); + } + + let regs = T::regs(); + let all_full = next_addr == eval; + + // NOTE: This is off label usage of RSR! See the issue below for + // why I am not checking if there is an "extra" FIFO byte, and why + // I am checking RSR directly (it seems to report the status of the LAST + // POPPED value, rather than the NEXT TO POP value like the datasheet + // suggests!) + // + // issue: https://github.com/raspberrypi/pico-feedback/issues/367 + let last_was_break = regs.uartrsr().read().be(); + + return match (all_full, last_was_break) { + (true, true) | (false, _) => { + // We got less than the full amount + a break, or the full amount + // and the last byte was a break. Subtract the break off by adding one to sval. + Ok(next_addr.saturating_sub(1 + sval)) + } + (true, false) => { + // We finished the whole DMA, and the last DMA'd byte was NOT a break + // character. This is an error. + // + // NOTE: we COULD potentially return Ok(buffer.len()) here, since we + // know a line break occured at SOME POINT after the DMA completed. + // + // However, we have no way of knowing if there was extra data BEFORE + // that line break, so instead return an Err to signal to the caller + // that there are "leftovers", and they'll catch the actual line break + // on the next call. + // + // Doing it like this also avoids racyness: now whether you finished + // the full read BEFORE the line break occurred or AFTER the line break + // occurs, you still get `MissingBreak(buffer.len())` instead of sometimes + // getting `Ok(buffer.len())` if you were "late enough" to observe the + // line break. + Err(ReadToBreakError::MissingBreak(buffer.len())) + } + }; + } else if errors.oeris() { + return Err(ReadToBreakError::Other(Error::Overrun)); + } else if errors.peris() { + return Err(ReadToBreakError::Other(Error::Parity)); + } else if errors.feris() { + return Err(ReadToBreakError::Other(Error::Framing)); + } + unreachable!("unrecognized rx error"); + } } impl<'d, T: Instance> Uart<'d, T, Blocking> { @@ -743,6 +936,13 @@ impl<'d, T: Instance> Uart<'d, T, Async> { pub async fn read(&mut self, buffer: &mut [u8]) -> Result<(), Error> { self.rx.read(buffer).await } + + /// Read until the buffer is full or a line break occurs. + /// + /// See [`UartRx::read_to_break()`] for more details + pub async fn read_to_break<'a>(&mut self, buf: &'a mut [u8]) -> Result<usize, ReadToBreakError> { + self.rx.read_to_break(buf).await + } } impl<'d, T: Instance, M: Mode> embedded_hal_02::serial::Read<u8> for UartRx<'d, T, M> { diff --git a/embassy-stm32-wpan/Cargo.toml b/embassy-stm32-wpan/Cargo.toml index 4f53a400a..360ca5f4b 100644 --- a/embassy-stm32-wpan/Cargo.toml +++ b/embassy-stm32-wpan/Cargo.toml @@ -44,6 +44,8 @@ defmt = ["dep:defmt", "embassy-sync/defmt", "embassy-embedded-hal/defmt", "embas ble = ["dep:stm32wb-hci"] mac = ["dep:bitflags", "dep:embassy-net-driver" ] +extended = [] + stm32wb10cc = [ "embassy-stm32/stm32wb10cc" ] stm32wb15cc = [ "embassy-stm32/stm32wb15cc" ] stm32wb30ce = [ "embassy-stm32/stm32wb30ce" ] diff --git a/embassy-stm32-wpan/build.rs b/embassy-stm32-wpan/build.rs index 94aac070d..7ab458bf2 100644 --- a/embassy-stm32-wpan/build.rs +++ b/embassy-stm32-wpan/build.rs @@ -18,9 +18,22 @@ fn main() { // 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(); + let in_file; + if env::var_os("CARGO_FEATURE_EXTENDED").is_some() { + if env::vars() + .map(|(a, _)| a) + .any(|x| x.starts_with("CARGO_FEATURE_STM32WB1")) + { + in_file = "tl_mbox_extended_wb1.x.in"; + } else { + in_file = "tl_mbox_extended_wbx5.x.in"; + } + } else { + in_file = "tl_mbox.x.in"; + } + fs::write(out_file, fs::read_to_string(in_file).unwrap()).unwrap(); println!("cargo:rustc-link-search={}", out_dir.display()); - println!("cargo:rerun-if-changed=tl_mbox.x.in"); + println!("cargo:rerun-if-changed={}", in_file); } enum GetOneError { diff --git a/embassy-stm32-wpan/tl_mbox_extended_wb1.x.in b/embassy-stm32-wpan/tl_mbox_extended_wb1.x.in new file mode 100644 index 000000000..4cffdaddd --- /dev/null +++ b/embassy-stm32-wpan/tl_mbox_extended_wb1.x.in @@ -0,0 +1,16 @@ +MEMORY +{ + RAM_SHARED (xrw) : ORIGIN = 0x20030000, LENGTH = 4K + RAMB_SHARED (xrw) : ORIGIN = 0x20030028, LENGTH = 4K +} + +/* + * Scatter the mailbox interface memory sections in shared memory + */ +SECTIONS +{ + TL_REF_TABLE (NOLOAD) : { *(TL_REF_TABLE) } >RAM_SHARED + + MB_MEM1 (NOLOAD) : { *(MB_MEM1) } >RAMB_SHARED + MB_MEM2 (NOLOAD) : { _sMB_MEM2 = . ; *(MB_MEM2) ; _eMB_MEM2 = . ; } >RAMB_SHARED +} diff --git a/embassy-stm32-wpan/tl_mbox_extended_wbx5.x.in b/embassy-stm32-wpan/tl_mbox_extended_wbx5.x.in new file mode 100644 index 000000000..281d637a9 --- /dev/null +++ b/embassy-stm32-wpan/tl_mbox_extended_wbx5.x.in @@ -0,0 +1,16 @@ +MEMORY +{ + RAM_SHARED (xrw) : ORIGIN = 0x20030000, LENGTH = 2K + RAMB_SHARED (xrw) : ORIGIN = 0x20038000, 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) } >RAMB_SHARED + MB_MEM2 (NOLOAD) : { _sMB_MEM2 = . ; *(MB_MEM2) ; _eMB_MEM2 = . ; } >RAMB_SHARED +} diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index b80509098..89c17cb3d 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml @@ -55,10 +55,12 @@ embedded-hal-02 = { package = "embedded-hal", version = "0.2.6", features = ["un embedded-hal-1 = { package = "embedded-hal", version = "1.0" } embedded-hal-async = { version = "1.0" } embedded-hal-nb = { version = "1.0" } +embedded-can = "0.4" embedded-storage = "0.3.1" embedded-storage-async = { version = "0.4.1" } + defmt = { version = "0.3", optional = true } log = { version = "0.4.14", optional = true } cortex-m-rt = ">=0.6.15,<0.8" @@ -67,8 +69,8 @@ futures = { version = "0.3.17", default-features = false, features = ["async-awa rand_core = "0.6.3" sdio-host = "0.5.0" critical-section = "1.1" -# stm32-metapac = { version = "15" } -stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-2d51fbe7363a376606cb670cc2cec0f634251022" } +#stm32-metapac = { version = "15" } +stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-e7f91751fbbf856e0cb30e50ae6db79f0409b085" } vcell = "0.1.3" bxcan = "0.7.0" nb = "1.0.0" @@ -80,14 +82,20 @@ chrono = { version = "^0.4", default-features = false, optional = true} bit_field = "0.10.2" document-features = "0.2.7" +static_assertions = { version = "1.1" } +volatile-register = { version = "0.2.1" } + + + [dev-dependencies] critical-section = { version = "1.1", features = ["std"] } [build-dependencies] proc-macro2 = "1.0.36" quote = "1.0.15" -# stm32-metapac = { version = "15", default-features = false, features = ["metadata"]} -stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-2d51fbe7363a376606cb670cc2cec0f634251022", default-features = false, features = ["metadata"]} + +#stm32-metapac = { version = "15", default-features = false, features = ["metadata"]} +stm32-metapac = { git = "https://github.com/embassy-rs/stm32-data-generated", tag = "stm32-data-e7f91751fbbf856e0cb30e50ae6db79f0409b085", default-features = false, features = ["metadata"]} [features] @@ -123,6 +131,8 @@ _time-driver = ["dep:embassy-time-driver", "time"] ## Use any time driver time-driver-any = ["_time-driver"] +## Use TIM1 as time driver +time-driver-tim1 = ["_time-driver"] ## Use TIM2 as time driver time-driver-tim2 = ["_time-driver"] ## Use TIM3 as time driver @@ -131,18 +141,24 @@ time-driver-tim3 = ["_time-driver"] time-driver-tim4 = ["_time-driver"] ## Use TIM5 as time driver time-driver-tim5 = ["_time-driver"] +## Use TIM8 as time driver +time-driver-tim8 = ["_time-driver"] ## Use TIM9 as time driver time-driver-tim9 = ["_time-driver"] -## Use TIM11 as time driver -time-driver-tim11 = ["_time-driver"] ## Use TIM12 as time driver time-driver-tim12 = ["_time-driver"] ## Use TIM15 as time driver time-driver-tim15 = ["_time-driver"] +## Use TIM20 as time driver +time-driver-tim20 = ["_time-driver"] ## Use TIM21 as time driver time-driver-tim21 = ["_time-driver"] ## Use TIM22 as time driver time-driver-tim22 = ["_time-driver"] +## Use TIM23 as time driver +time-driver-tim23 = ["_time-driver"] +## Use TIM24 as time driver +time-driver-tim24 = ["_time-driver"] #! ## Analog Switch Pins (Pxy_C) on STM32H7 series diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index f1ead6d46..3a30ba2fd 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -5,8 +5,9 @@ use std::{env, fs}; use proc_macro2::{Ident, TokenStream}; use quote::{format_ident, quote}; -use stm32_metapac::metadata::ir::{BlockItemInner, Enum, FieldSet}; -use stm32_metapac::metadata::{MemoryRegionKind, PeripheralRccRegister, StopMode, METADATA}; +use stm32_metapac::metadata::{ + MemoryRegionKind, PeripheralRccKernelClock, PeripheralRccRegister, PeripheralRegisters, StopMode, METADATA, +}; fn main() { let target = env::var("TARGET").unwrap(); @@ -183,40 +184,33 @@ fn main() { let time_driver_singleton = match time_driver.as_ref().map(|x| x.as_ref()) { None => "", + Some("tim1") => "TIM1", Some("tim2") => "TIM2", Some("tim3") => "TIM3", Some("tim4") => "TIM4", Some("tim5") => "TIM5", + Some("tim8") => "TIM8", Some("tim9") => "TIM9", - Some("tim11") => "TIM11", Some("tim12") => "TIM12", Some("tim15") => "TIM15", + Some("tim20") => "TIM20", Some("tim21") => "TIM21", Some("tim22") => "TIM22", + Some("tim23") => "TIM23", + Some("tim24") => "TIM24", 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(&"TIM9".to_string()) { - "TIM9" - } else if singletons.contains(&"TIM11".to_string()) { - "TIM11" - } else if singletons.contains(&"TIM12".to_string()) { - "TIM12" - } else if singletons.contains(&"TIM15".to_string()) { - "TIM15" - } else if singletons.contains(&"TIM21".to_string()) { - "TIM21" - } else if singletons.contains(&"TIM22".to_string()) { - "TIM22" - } else { - panic!("time-driver-any requested, but the chip doesn't have TIM2, TIM3, TIM4, TIM5, TIM9, TIM11, TIM12 or TIM15.") - } + // Order of TIM candidators: + // 1. 2CH -> 2CH_CMP -> GP16 -> GP32 -> ADV + // 2. In same catagory: larger TIM number first + [ + "TIM22", "TIM21", "TIM12", "TIM9", // 2CH + "TIM15", // 2CH_CMP + "TIM19", "TIM4", "TIM3", // GP16 + "TIM24", "TIM23", "TIM5", "TIM2", // GP32 + "TIM20", "TIM8", "TIM1", //ADV + ] + .iter() + .find(|tim| singletons.contains(&tim.to_string())).expect("time-driver-any requested, but the chip doesn't have TIM1, TIM2, TIM3, TIM4, TIM5, TIM8, TIM9, TIM12, TIM15, TIM20, TIM21, TIM22, TIM23 or TIM24.") } _ => panic!("unknown time_driver {:?}", time_driver), }; @@ -361,50 +355,6 @@ fn main() { g.extend(quote! { pub mod flash_regions { #flash_regions } }); - // ======== - // Generate DMA IRQs. - - let mut dma_irqs: BTreeMap<&str, Vec<(&str, &str, &str)>> = BTreeMap::new(); - - for p in METADATA.peripherals { - if let Some(r) = &p.registers { - if r.kind == "dma" || r.kind == "bdma" || r.kind == "gpdma" { - if p.name == "BDMA1" { - // BDMA1 in H7 doesn't use DMAMUX, which breaks - continue; - } - for irq in p.interrupts { - dma_irqs - .entry(irq.interrupt) - .or_default() - .push((r.kind, p.name, irq.signal)); - } - } - } - } - - let dma_irqs: TokenStream = dma_irqs - .iter() - .map(|(irq, channels)| { - let irq = format_ident!("{}", irq); - - let xdma = format_ident!("{}", channels[0].0); - let channels = channels.iter().map(|(_, dma, ch)| format_ident!("{}_{}", dma, ch)); - - quote! { - #[cfg(feature = "rt")] - #[crate::interrupt] - unsafe fn #irq () { - #( - <crate::peripherals::#channels as crate::dma::#xdma::sealed::Channel>::on_irq(); - )* - } - } - }) - .collect(); - - g.extend(dma_irqs); - // ======== // Extract the rcc registers let rcc_registers = METADATA @@ -414,43 +364,142 @@ fn main() { .find(|r| r.kind == "rcc") .unwrap(); - // ======== - // Generate rcc fieldset and enum maps - let rcc_enum_map: HashMap<&str, HashMap<&str, &Enum>> = { - let rcc_blocks = rcc_registers.ir.blocks.iter().find(|b| b.name == "Rcc").unwrap().items; - let rcc_fieldsets: HashMap<&str, &FieldSet> = rcc_registers.ir.fieldsets.iter().map(|f| (f.name, f)).collect(); - let rcc_enums: HashMap<&str, &Enum> = rcc_registers.ir.enums.iter().map(|e| (e.name, e)).collect(); - - rcc_blocks - .iter() - .filter_map(|b| match &b.inner { - BlockItemInner::Register(register) => register.fieldset.map(|f| (b.name, f)), - _ => None, - }) - .filter_map(|(b, f)| { - rcc_fieldsets.get(f).map(|f| { - ( - b, - f.fields - .iter() - .filter_map(|f| { - let enumm = f.enumm?; - let enumm = rcc_enums.get(enumm)?; - - Some((f.name, *enumm)) - }) - .collect(), - ) - }) - }) - .collect() - }; - // ======== // Generate RccPeripheral impls - let refcounted_peripherals = HashSet::from(["usart", "adc"]); - let mut refcount_statics = BTreeSet::new(); + // count how many times each xxENR field is used, to enable refcounting if used more than once. + let mut rcc_field_count: HashMap<_, usize> = HashMap::new(); + for p in METADATA.peripherals { + if let Some(rcc) = &p.rcc { + let en = rcc.enable.as_ref().unwrap(); + *rcc_field_count.entry((en.register, en.field)).or_insert(0) += 1; + } + } + + struct ClockGen<'a> { + rcc_registers: &'a PeripheralRegisters, + chained_muxes: HashMap<&'a str, &'a PeripheralRccRegister>, + force_refcount: HashSet<&'a str>, + + refcount_statics: BTreeSet<Ident>, + clock_names: BTreeSet<String>, + muxes: BTreeSet<(Ident, Ident, Ident)>, + } + + let mut clock_gen = ClockGen { + rcc_registers, + chained_muxes: HashMap::new(), + force_refcount: HashSet::from(["usart"]), + + refcount_statics: BTreeSet::new(), + clock_names: BTreeSet::new(), + muxes: BTreeSet::new(), + }; + if chip_name.starts_with("stm32h5") { + clock_gen.chained_muxes.insert( + "PER", + &PeripheralRccRegister { + register: "CCIPR5", + field: "PERSEL", + }, + ); + } + if chip_name.starts_with("stm32h7") { + clock_gen.chained_muxes.insert( + "PER", + &PeripheralRccRegister { + register: "D1CCIPR", + field: "PERSEL", + }, + ); + } + if chip_name.starts_with("stm32u5") { + clock_gen.chained_muxes.insert( + "ICLK", + &PeripheralRccRegister { + register: "CCIPR1", + field: "ICLKSEL", + }, + ); + } + if chip_name.starts_with("stm32wb") && !chip_name.starts_with("stm32wba") { + clock_gen.chained_muxes.insert( + "CLK48", + &PeripheralRccRegister { + register: "CCIPR", + field: "CLK48SEL", + }, + ); + } + if chip_name.starts_with("stm32f7") { + clock_gen.chained_muxes.insert( + "CLK48", + &PeripheralRccRegister { + register: "DCKCFGR2", + field: "CLK48SEL", + }, + ); + } + if chip_name.starts_with("stm32f4") && !chip_name.starts_with("stm32f410") { + clock_gen.chained_muxes.insert( + "CLK48", + &PeripheralRccRegister { + register: "DCKCFGR", + field: "CLK48SEL", + }, + ); + } + + impl<'a> ClockGen<'a> { + fn gen_clock(&mut self, name: &str) -> TokenStream { + let clock_name = format_ident!("{}", name.to_ascii_lowercase()); + self.clock_names.insert(name.to_ascii_lowercase()); + quote!( unsafe { crate::rcc::get_freqs().#clock_name.unwrap() } ) + } + + fn gen_mux(&mut self, mux: &PeripheralRccRegister) -> TokenStream { + let ir = &self.rcc_registers.ir; + let fieldset_name = mux.register.to_ascii_lowercase(); + let fieldset = ir + .fieldsets + .iter() + .find(|i| i.name.eq_ignore_ascii_case(&fieldset_name)) + .unwrap(); + let field_name = mux.field.to_ascii_lowercase(); + let field = fieldset.fields.iter().find(|i| i.name == field_name).unwrap(); + let enum_name = field.enumm.unwrap(); + let enumm = ir.enums.iter().find(|i| i.name == enum_name).unwrap(); + + let fieldset_name = format_ident!("{}", fieldset_name); + let field_name = format_ident!("{}", field_name); + let enum_name = format_ident!("{}", enum_name); + + self.muxes + .insert((fieldset_name.clone(), field_name.clone(), enum_name.clone())); + + let mut match_arms = TokenStream::new(); + + for v in enumm.variants.iter().filter(|v| v.name != "DISABLE") { + let variant_name = format_ident!("{}", v.name); + let expr = if let Some(mux) = self.chained_muxes.get(&v.name) { + self.gen_mux(mux) + } else { + self.gen_clock(&v.name) + }; + match_arms.extend(quote! { + crate::pac::rcc::vals::#enum_name::#variant_name => #expr, + }); + } + + quote! { + match crate::pac::RCC.#fieldset_name().read().#field_name() { + #match_arms + #[allow(unreachable_patterns)] + _ => unreachable!(), + } + } + } + } for p in METADATA.peripherals { if !singletons.contains(&p.name.to_string()) { @@ -483,15 +532,16 @@ fn main() { let ptype = if let Some(reg) = &p.registers { reg.kind } else { "" }; let pname = format_ident!("{}", p.name); - let clk = format_ident!("{}", rcc.clock); - let en_reg = format_ident!("{}", en.register); - let set_en_field = format_ident!("set_{}", en.field); + let en_reg = format_ident!("{}", en.register.to_ascii_lowercase()); + let set_en_field = format_ident!("set_{}", en.field.to_ascii_lowercase()); - let (before_enable, before_disable) = if refcounted_peripherals.contains(ptype) { + let refcount = + clock_gen.force_refcount.contains(ptype) || *rcc_field_count.get(&(en.register, en.field)).unwrap() > 1; + let (before_enable, before_disable) = if refcount { let refcount_static = format_ident!("{}_{}", en.register.to_ascii_uppercase(), en.field.to_ascii_uppercase()); - refcount_statics.insert(refcount_static.clone()); + clock_gen.refcount_statics.insert(refcount_static.clone()); ( quote! { @@ -511,68 +561,13 @@ fn main() { (TokenStream::new(), TokenStream::new()) }; - let mux_supported = HashSet::from(["c0", "h5", "h50", "h7", "h7ab", "h7rm0433", "g0", "g4", "l4"]) - .contains(rcc_registers.version); - let mux_for = |mux: Option<&'static PeripheralRccRegister>| { - // restrict mux implementation to supported versions - if !mux_supported { - return None; - } - - let mux = mux?; - let fieldset = rcc_enum_map.get(mux.register)?; - let enumm = fieldset.get(mux.field)?; - - Some((mux, *enumm)) + let clock_frequency = match &rcc.kernel_clock { + PeripheralRccKernelClock::Mux(mux) => clock_gen.gen_mux(mux), + PeripheralRccKernelClock::Clock(clock) => clock_gen.gen_clock(clock), }; - let clock_frequency = match mux_for(rcc.mux.as_ref()) { - Some((mux, rcc_enumm)) => { - let fieldset_name = format_ident!("{}", mux.register); - let field_name = format_ident!("{}", mux.field); - let enum_name = format_ident!("{}", rcc_enumm.name); - - let match_arms: TokenStream = rcc_enumm - .variants - .iter() - .filter(|v| v.name != "DISABLE") - .map(|v| { - let variant_name = format_ident!("{}", v.name); - let clock_name = format_ident!("{}", v.name.to_ascii_lowercase()); - - if v.name.starts_with("HCLK") || v.name.starts_with("PCLK") || v.name == "SYS" { - quote! { - #enum_name::#variant_name => unsafe { crate::rcc::get_freqs().#clock_name }, - } - } else { - quote! { - #enum_name::#variant_name => unsafe { crate::rcc::get_freqs().#clock_name.unwrap() }, - } - } - }) - .collect(); - - quote! { - use crate::pac::rcc::vals::#enum_name; - - #[allow(unreachable_patterns)] - match crate::pac::RCC.#fieldset_name().read().#field_name() { - #match_arms - - _ => unreachable!(), - } - } - } - None => quote! { - unsafe { crate::rcc::get_freqs().#clk } - }, - }; - - /* - A refcount leak can result if the same field is shared by peripherals with different stop modes - - This condition should be checked in stm32-data - */ + // A refcount leak can result if the same field is shared by peripherals with different stop modes + // This condition should be checked in stm32-data let stop_refcount = match rcc.stop_mode { StopMode::Standby => None, StopMode::Stop2 => Some(quote! { REFCOUNT_STOP2 }), @@ -617,7 +612,110 @@ fn main() { } } - let refcount_mod: TokenStream = refcount_statics + let struct_fields: Vec<_> = clock_gen + .muxes + .iter() + .map(|(_fieldset, fieldname, enum_name)| { + quote! { + pub #fieldname: #enum_name + } + }) + .collect(); + + let mut inits = TokenStream::new(); + for fieldset in clock_gen + .muxes + .iter() + .map(|(f, _, _)| f) + .collect::<BTreeSet<_>>() + .into_iter() + { + let setters: Vec<_> = clock_gen + .muxes + .iter() + .filter(|(f, _, _)| f == fieldset) + .map(|(_, fieldname, _)| { + let setter = format_ident!("set_{}", fieldname); + quote! { + w.#setter(self.#fieldname); + } + }) + .collect(); + + inits.extend(quote! { + crate::pac::RCC.#fieldset().modify(|w| { + #(#setters)* + }); + }) + } + + let enum_names: BTreeSet<_> = clock_gen.muxes.iter().map(|(_, _, enum_name)| enum_name).collect(); + + g.extend(quote! { + pub mod mux { + #(pub use crate::pac::rcc::vals::#enum_names as #enum_names; )* + + #[derive(Clone, Copy)] + pub struct ClockMux { + #( #struct_fields, )* + } + + impl ClockMux { + pub(crate) const fn default() -> Self { + // safety: zero value is valid for all PAC enums. + unsafe { ::core::mem::zeroed() } + } + } + + impl Default for ClockMux { + fn default() -> Self { + Self::default() + } + } + + impl ClockMux { + pub(crate) fn init(&self) { + #inits + } + } + } + }); + + // Generate RCC + clock_gen.clock_names.insert("sys".to_string()); + clock_gen.clock_names.insert("rtc".to_string()); + let clock_idents: Vec<_> = clock_gen.clock_names.iter().map(|n| format_ident!("{}", n)).collect(); + g.extend(quote! { + #[derive(Clone, Copy, Debug)] + #[cfg_attr(feature = "defmt", derive(defmt::Format))] + pub struct Clocks { + #( + pub #clock_idents: Option<crate::time::Hertz>, + )* + } + }); + + let clocks_macro = quote!( + macro_rules! set_clocks { + ($($(#[$m:meta])* $k:ident: $v:expr,)*) => { + { + #[allow(unused)] + struct Temp { + $($(#[$m])* $k: Option<crate::time::Hertz>,)* + } + let all = Temp { + $($(#[$m])* $k: $v,)* + }; + crate::rcc::set_freqs(crate::rcc::Clocks { + #( #clock_idents: all.#clock_idents, )* + }); + } + }; + } + ); + + let refcount_mod: TokenStream = clock_gen + .refcount_statics .iter() .map(|refcount_static| { quote! { @@ -665,7 +763,7 @@ fn main() { #[rustfmt::skip] let signals: HashMap<_, _> = [ - // (kind, signal) => trait + // (kind, signal) => trait (("usart", "TX"), quote!(crate::usart::TxPin)), (("usart", "RX"), quote!(crate::usart::RxPin)), (("usart", "CTS"), quote!(crate::usart::CtsPin)), @@ -735,13 +833,20 @@ fn main() { (("can", "TX"), quote!(crate::can::TxPin)), (("can", "RX"), quote!(crate::can::RxPin)), (("eth", "REF_CLK"), quote!(crate::eth::RefClkPin)), + (("eth", "RX_CLK"), quote!(crate::eth::RXClkPin)), + (("eth", "TX_CLK"), quote!(crate::eth::TXClkPin)), (("eth", "MDIO"), quote!(crate::eth::MDIOPin)), (("eth", "MDC"), quote!(crate::eth::MDCPin)), (("eth", "CRS_DV"), quote!(crate::eth::CRSPin)), + (("eth", "RX_DV"), quote!(crate::eth::RXDVPin)), (("eth", "RXD0"), quote!(crate::eth::RXD0Pin)), (("eth", "RXD1"), quote!(crate::eth::RXD1Pin)), + (("eth", "RXD2"), quote!(crate::eth::RXD2Pin)), + (("eth", "RXD3"), quote!(crate::eth::RXD3Pin)), (("eth", "TXD0"), quote!(crate::eth::TXD0Pin)), (("eth", "TXD1"), quote!(crate::eth::TXD1Pin)), + (("eth", "TXD2"), quote!(crate::eth::TXD2Pin)), + (("eth", "TXD3"), quote!(crate::eth::TXD3Pin)), (("eth", "TX_EN"), quote!(crate::eth::TXEnPin)), (("fmc", "A0"), quote!(crate::fmc::A0Pin)), (("fmc", "A1"), quote!(crate::fmc::A1Pin)), @@ -905,6 +1010,7 @@ fn main() { (("octospi", "NCLK"), quote!(crate::ospi::NckPin)), ].into(); + for p in METADATA.peripherals { if let Some(regs) = &p.registers { for pin in p.pins { @@ -954,9 +1060,9 @@ fn main() { } else if pin.signal.starts_with("INN") { // TODO handle in the future when embassy supports differential measurements None - } else if pin.signal.starts_with("IN") && pin.signal.ends_with("b") { + } else if pin.signal.starts_with("IN") && pin.signal.ends_with('b') { // we number STM32L1 ADC bank 1 as 0..=31, bank 2 as 32..=63 - let signal = pin.signal.strip_prefix("IN").unwrap().strip_suffix("b").unwrap(); + let signal = pin.signal.strip_prefix("IN").unwrap().strip_suffix('b').unwrap(); Some(32u8 + signal.parse::<u8>().unwrap()) } else if pin.signal.starts_with("IN") { Some(pin.signal.strip_prefix("IN").unwrap().parse().unwrap()) @@ -965,7 +1071,7 @@ fn main() { }; if let Some(ch) = ch { g.extend(quote! { - impl_adc_pin!( #peri, #pin_name, #ch); + impl_adc_pin!( #peri, #pin_name, #ch); }) } } @@ -997,7 +1103,7 @@ fn main() { let ch: u8 = pin.signal.strip_prefix("OUT").unwrap().parse().unwrap(); g.extend(quote! { - impl_dac_pin!( #peri, #pin_name, #ch); + impl_dac_pin!( #peri, #pin_name, #ch); }) } } @@ -1028,6 +1134,11 @@ fn main() { (("dac", "CH1"), quote!(crate::dac::DacDma1)), (("dac", "CH2"), quote!(crate::dac::DacDma2)), (("timer", "UP"), quote!(crate::timer::UpDma)), + (("hash", "IN"), quote!(crate::hash::Dma)), + (("timer", "CH1"), quote!(crate::timer::Ch1Dma)), + (("timer", "CH2"), quote!(crate::timer::Ch2Dma)), + (("timer", "CH3"), quote!(crate::timer::Ch3Dma)), + (("timer", "CH4"), quote!(crate::timer::Ch4Dma)), ] .into(); @@ -1043,16 +1154,6 @@ fn main() { } if let Some(tr) = signals.get(&(regs.kind, ch.signal)) { - // TIM6 of stm32f334 is special, DMA channel for TIM6 depending on SYSCFG state - if chip_name.starts_with("stm32f334") && p.name == "TIM6" { - continue; - } - - // TIM6 of stm32f378 is special, DMA channel for TIM6 depending on SYSCFG state - if chip_name.starts_with("stm32f378") && p.name == "TIM6" { - continue; - } - let peri = format_ident!("{}", p.name); let channel = if let Some(channel) = &ch.channel { @@ -1201,7 +1302,6 @@ fn main() { let mut interrupts_table: Vec<Vec<String>> = Vec::new(); let mut peripherals_table: Vec<Vec<String>> = Vec::new(); let mut pins_table: Vec<Vec<String>> = Vec::new(); - let mut dma_channels_table: Vec<Vec<String>> = Vec::new(); let mut adc_common_table: Vec<Vec<String>> = Vec::new(); /* @@ -1211,7 +1311,7 @@ fn main() { ADC3 and higher are assigned to the adc34 clock in the table The adc3_common cfg directive is added if ADC3_COMMON exists */ - let has_adc3 = METADATA.peripherals.iter().find(|p| p.name == "ADC3_COMMON").is_some(); + let has_adc3 = METADATA.peripherals.iter().any(|p| p.name == "ADC3_COMMON"); let set_adc345 = HashSet::from(["ADC3", "ADC4", "ADC5"]); for m in METADATA @@ -1295,51 +1395,108 @@ fn main() { } } - let mut dma_channel_count: usize = 0; - let mut bdma_channel_count: usize = 0; - let mut gpdma_channel_count: usize = 0; + let mut dmas = TokenStream::new(); + let has_dmamux = METADATA + .peripherals + .iter() + .flat_map(|p| &p.registers) + .any(|p| p.kind == "dmamux"); + + for (ch_idx, ch) in METADATA.dma_channels.iter().enumerate() { + // Some H7 chips have BDMA1 hardcoded for DFSDM, ie no DMAMUX. It's unsupported, skip it. + if has_dmamux && ch.dmamux.is_none() { + continue; + } + + let name = format_ident!("{}", ch.name); + let idx = ch_idx as u8; + g.extend(quote!(dma_channel_impl!(#name, #idx);)); + + let dma = format_ident!("{}", ch.dma); + let ch_num = ch.channel as usize; - for ch in METADATA.dma_channels { - let mut row = Vec::new(); let dma_peri = METADATA.peripherals.iter().find(|p| p.name == ch.dma).unwrap(); let bi = dma_peri.registers.as_ref().unwrap(); - let num; - match bi.kind { - "dma" => { - num = dma_channel_count; - dma_channel_count += 1; - } - "bdma" => { - num = bdma_channel_count; - bdma_channel_count += 1; - } - "gpdma" => { - num = gpdma_channel_count; - gpdma_channel_count += 1; - } + let dma_info = match bi.kind { + "dma" => quote!(crate::dma::DmaInfo::Dma(crate::pac::#dma)), + "bdma" => quote!(crate::dma::DmaInfo::Bdma(crate::pac::#dma)), + "gpdma" => quote!(crate::pac::#dma), _ => panic!("bad dma channel kind {}", bi.kind), - } + }; - row.push(ch.name.to_string()); - row.push(ch.dma.to_string()); - row.push(bi.kind.to_string()); - row.push(ch.channel.to_string()); - row.push(num.to_string()); - if let Some(dmamux) = &ch.dmamux { - let dmamux_channel = ch.dmamux_channel.unwrap(); - row.push(format!("{{dmamux: {}, dmamux_channel: {}}}", dmamux, dmamux_channel)); - } else { - row.push("{}".to_string()); - } + let dmamux = match &ch.dmamux { + Some(dmamux) => { + let dmamux = format_ident!("{}", dmamux); + let num = ch.dmamux_channel.unwrap() as usize; - dma_channels_table.push(row); + g.extend(quote!(dmamux_channel_impl!(#name, #dmamux);)); + + quote! { + dmamux: crate::dma::DmamuxInfo { + mux: crate::pac::#dmamux, + num: #num, + }, + } + } + None => quote!(), + }; + + dmas.extend(quote! { + crate::dma::ChannelInfo { + dma: #dma_info, + num: #ch_num, + #dmamux + }, + }); } + // ======== + // Generate DMA IRQs. + + let mut dma_irqs: BTreeMap<&str, Vec<String>> = BTreeMap::new(); + + for p in METADATA.peripherals { + if let Some(r) = &p.registers { + if r.kind == "dma" || r.kind == "bdma" || r.kind == "gpdma" { + for irq in p.interrupts { + let ch_name = format!("{}_{}", p.name, irq.signal); + let ch = METADATA.dma_channels.iter().find(|c| c.name == ch_name).unwrap(); + + // Some H7 chips have BDMA1 hardcoded for DFSDM, ie no DMAMUX. It's unsupported, skip it. + if has_dmamux && ch.dmamux.is_none() { + continue; + } + + dma_irqs.entry(irq.interrupt).or_default().push(ch_name); + } + } + } + } + + let dma_irqs: TokenStream = dma_irqs + .iter() + .map(|(irq, channels)| { + let irq = format_ident!("{}", irq); + + let channels = channels.iter().map(|c| format_ident!("{}", c)); + + quote! { + #[cfg(feature = "rt")] + #[crate::interrupt] + unsafe fn #irq () { + #( + <crate::peripherals::#channels as crate::dma::sealed::ChannelInterrupt>::on_irq(); + )* + } + } + }) + .collect(); + + g.extend(dma_irqs); + g.extend(quote! { - pub(crate) const DMA_CHANNEL_COUNT: usize = #dma_channel_count; - pub(crate) const BDMA_CHANNEL_COUNT: usize = #bdma_channel_count; - pub(crate) const GPDMA_CHANNEL_COUNT: usize = #gpdma_channel_count; + pub(crate) const DMA_CHANNELS: &[crate::dma::ChannelInfo] = &[#dmas]; }); for irq in METADATA.interrupts { @@ -1350,7 +1507,7 @@ fn main() { } } - let mut m = String::new(); + let mut m = clocks_macro.to_string(); // DO NOT ADD more macros like these. // These turned to be a bad idea! @@ -1359,7 +1516,6 @@ fn main() { make_table(&mut m, "foreach_interrupt", &interrupts_table); make_table(&mut m, "foreach_peripheral", &peripherals_table); make_table(&mut m, "foreach_pin", &pins_table); - make_table(&mut m, "foreach_dma_channel", &dma_channels_table); make_table(&mut m, "foreach_adc", &adc_common_table); let out_dir = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); @@ -1395,6 +1551,7 @@ fn main() { // ======= // ADC3_COMMON is present + #[allow(clippy::print_literal)] if has_adc3 { println!("cargo:rustc-cfg={}", "adc3_common"); } diff --git a/embassy-stm32/src/adc/f1.rs b/embassy-stm32/src/adc/f1.rs index fb27bb87b..b27b99827 100644 --- a/embassy-stm32/src/adc/f1.rs +++ b/embassy-stm32/src/adc/f1.rs @@ -6,7 +6,6 @@ use embassy_hal_internal::into_ref; use embedded_hal_02::blocking::delay::DelayUs; use crate::adc::{Adc, AdcPin, Instance, SampleTime}; -use crate::rcc::get_freqs; use crate::time::Hertz; use crate::{interrupt, Peripheral}; @@ -75,24 +74,24 @@ impl<'d, T: Instance> Adc<'d, T> { Self { adc, - sample_time: Default::default(), + sample_time: SampleTime::from_bits(0), } } fn freq() -> Hertz { - unsafe { get_freqs() }.adc.unwrap() + T::frequency() } pub fn sample_time_for_us(&self, us: u32) -> SampleTime { match us * Self::freq().0 / 1_000_000 { - 0..=1 => SampleTime::Cycles1_5, - 2..=7 => SampleTime::Cycles7_5, - 8..=13 => SampleTime::Cycles13_5, - 14..=28 => SampleTime::Cycles28_5, - 29..=41 => SampleTime::Cycles41_5, - 42..=55 => SampleTime::Cycles55_5, - 56..=71 => SampleTime::Cycles71_5, - _ => SampleTime::Cycles239_5, + 0..=1 => SampleTime::CYCLES1_5, + 2..=7 => SampleTime::CYCLES7_5, + 8..=13 => SampleTime::CYCLES13_5, + 14..=28 => SampleTime::CYCLES28_5, + 29..=41 => SampleTime::CYCLES41_5, + 42..=55 => SampleTime::CYCLES55_5, + 56..=71 => SampleTime::CYCLES71_5, + _ => SampleTime::CYCLES239_5, } } diff --git a/embassy-stm32/src/adc/f3.rs b/embassy-stm32/src/adc/f3.rs index 6f59c230f..efade1f64 100644 --- a/embassy-stm32/src/adc/f3.rs +++ b/embassy-stm32/src/adc/f3.rs @@ -97,23 +97,23 @@ impl<'d, T: Instance> Adc<'d, T> { Self { adc, - sample_time: Default::default(), + sample_time: SampleTime::from_bits(0), } } fn freq() -> Hertz { - <T as crate::adc::sealed::Instance>::frequency() + <T as crate::rcc::sealed::RccPeripheral>::frequency() } pub fn sample_time_for_us(&self, us: u32) -> SampleTime { match us * Self::freq().0 / 1_000_000 { - 0..=1 => SampleTime::Cycles1_5, - 2..=4 => SampleTime::Cycles4_5, - 5..=7 => SampleTime::Cycles7_5, - 8..=19 => SampleTime::Cycles19_5, - 20..=61 => SampleTime::Cycles61_5, - 62..=181 => SampleTime::Cycles181_5, - _ => SampleTime::Cycles601_5, + 0..=1 => SampleTime::CYCLES1_5, + 2..=4 => SampleTime::CYCLES4_5, + 5..=7 => SampleTime::CYCLES7_5, + 8..=19 => SampleTime::CYCLES19_5, + 20..=61 => SampleTime::CYCLES61_5, + 62..=181 => SampleTime::CYCLES181_5, + _ => SampleTime::CYCLES601_5, } } diff --git a/embassy-stm32/src/adc/f3_v1_1.rs b/embassy-stm32/src/adc/f3_v1_1.rs index 6915a8f1c..f842893fa 100644 --- a/embassy-stm32/src/adc/f3_v1_1.rs +++ b/embassy-stm32/src/adc/f3_v1_1.rs @@ -107,12 +107,12 @@ impl Calibration { /// Returns a calibrated voltage value as in microvolts (uV) pub fn cal_uv(&self, raw: u16, resolution: super::Resolution) -> u32 { - (self.vdda_uv() / resolution.to_max_count()) * raw as u32 + (self.vdda_uv() / super::resolution_to_max_count(resolution)) * raw as u32 } /// Returns a calibrated voltage value as an f32 pub fn cal_f32(&self, raw: u16, resolution: super::Resolution) -> f32 { - raw as f32 * self.vdda_f32() / resolution.to_max_count() as f32 + raw as f32 * self.vdda_f32() / super::resolution_to_max_count(resolution) as f32 } } @@ -175,12 +175,7 @@ impl<'d, T: Instance> Adc<'d, T> { } pub fn resolution(&self) -> Resolution { - match T::regs().cr1().read().res() { - crate::pac::adc::vals::Res::TWELVEBIT => Resolution::TwelveBit, - crate::pac::adc::vals::Res::TENBIT => Resolution::TenBit, - crate::pac::adc::vals::Res::EIGHTBIT => Resolution::EightBit, - crate::pac::adc::vals::Res::SIXBIT => Resolution::SixBit, - } + T::regs().cr1().read().res() } pub fn enable_vref(&self) -> Vref<T> { @@ -359,23 +354,23 @@ impl<'d, T: Instance> Adc<'d, T> { fn get_res_clks(res: Resolution) -> u32 { match res { - Resolution::TwelveBit => 12, - Resolution::TenBit => 11, - Resolution::EightBit => 9, - Resolution::SixBit => 7, + Resolution::BITS12 => 12, + Resolution::BITS10 => 11, + Resolution::BITS8 => 9, + Resolution::BITS6 => 7, } } fn get_sample_time_clks(sample_time: SampleTime) -> u32 { match sample_time { - SampleTime::Cycles4 => 4, - SampleTime::Cycles9 => 9, - SampleTime::Cycles16 => 16, - SampleTime::Cycles24 => 24, - SampleTime::Cycles48 => 48, - SampleTime::Cycles96 => 96, - SampleTime::Cycles192 => 192, - SampleTime::Cycles384 => 384, + SampleTime::CYCLES4 => 4, + SampleTime::CYCLES9 => 9, + SampleTime::CYCLES16 => 16, + SampleTime::CYCLES24 => 24, + SampleTime::CYCLES48 => 48, + SampleTime::CYCLES96 => 96, + SampleTime::CYCLES192 => 192, + SampleTime::CYCLES384 => 384, } } @@ -384,14 +379,14 @@ impl<'d, T: Instance> Adc<'d, T> { let us_clks = us * Self::freq().0 / 1_000_000; let clks = us_clks.saturating_sub(res_clks); match clks { - 0..=4 => SampleTime::Cycles4, - 5..=9 => SampleTime::Cycles9, - 10..=16 => SampleTime::Cycles16, - 17..=24 => SampleTime::Cycles24, - 25..=48 => SampleTime::Cycles48, - 49..=96 => SampleTime::Cycles96, - 97..=192 => SampleTime::Cycles192, - 193.. => SampleTime::Cycles384, + 0..=4 => SampleTime::CYCLES4, + 5..=9 => SampleTime::CYCLES9, + 10..=16 => SampleTime::CYCLES16, + 17..=24 => SampleTime::CYCLES24, + 25..=48 => SampleTime::CYCLES48, + 49..=96 => SampleTime::CYCLES96, + 97..=192 => SampleTime::CYCLES192, + 193.. => SampleTime::CYCLES384, } } diff --git a/embassy-stm32/src/adc/mod.rs b/embassy-stm32/src/adc/mod.rs index e4dd35c34..0d0d40549 100644 --- a/embassy-stm32/src/adc/mod.rs +++ b/embassy-stm32/src/adc/mod.rs @@ -8,23 +8,19 @@ #[cfg_attr(adc_f3, path = "f3.rs")] #[cfg_attr(adc_f3_v1_1, path = "f3_v1_1.rs")] #[cfg_attr(adc_v1, path = "v1.rs")] +#[cfg_attr(adc_l0, path = "v1.rs")] #[cfg_attr(adc_v2, path = "v2.rs")] -#[cfg_attr(any(adc_v3, adc_g0), path = "v3.rs")] +#[cfg_attr(any(adc_v3, adc_g0, adc_h5), path = "v3.rs")] #[cfg_attr(adc_v4, path = "v4.rs")] mod _version; -#[cfg(not(any(adc_f1, adc_f3_v2)))] -mod resolution; -mod sample_time; - #[allow(unused)] #[cfg(not(adc_f3_v2))] pub use _version::*; -#[cfg(not(any(adc_f1, adc_f3, adc_f3_v2)))] -pub use resolution::Resolution; -#[cfg(not(adc_f3_v2))] -pub use sample_time::SampleTime; +#[cfg(not(any(adc_f1, adc_f3_v2)))] +pub use crate::pac::adc::vals::Res as Resolution; +pub use crate::pac::adc::vals::SampleTime; use crate::peripherals; /// Analog to Digital driver. @@ -36,15 +32,15 @@ pub struct Adc<'d, T: Instance> { } pub(crate) mod sealed { - #[cfg(any(adc_f1, adc_f3, adc_v1, adc_f3_v1_1))] + #[cfg(any(adc_f1, adc_f3, adc_v1, adc_l0, adc_f3_v1_1))] use embassy_sync::waitqueue::AtomicWaker; - #[cfg(any(adc_f1, adc_f3, adc_v1, adc_f3_v1_1))] + #[cfg(any(adc_f1, adc_f3, adc_v1, adc_l0, adc_f3_v1_1))] pub struct State { pub waker: AtomicWaker, } - #[cfg(any(adc_f1, adc_f3, adc_v1, adc_f3_v1_1))] + #[cfg(any(adc_f1, adc_f3, adc_v1, adc_l0, adc_f3_v1_1))] impl State { pub const fn new() -> Self { Self { @@ -59,16 +55,14 @@ pub(crate) mod sealed { pub trait Instance: InterruptableInstance { fn regs() -> crate::pac::adc::Adc; - #[cfg(not(any(adc_f1, adc_v1, adc_f3_v2, adc_f3_v1_1, adc_g0)))] + #[cfg(not(any(adc_f1, adc_v1, adc_l0, adc_f3_v2, adc_f3_v1_1, adc_g0)))] fn common_regs() -> crate::pac::adccommon::AdcCommon; - #[cfg(adc_f3)] - fn frequency() -> crate::time::Hertz; - #[cfg(any(adc_f1, adc_f3, adc_v1, adc_f3_v1_1))] + #[cfg(any(adc_f1, adc_f3, adc_v1, adc_l0, adc_f3_v1_1))] fn state() -> &'static State; } pub trait AdcPin<T: Instance> { - #[cfg(any(adc_v1, adc_v2))] + #[cfg(any(adc_v1, adc_l0, adc_v2))] fn set_as_analog(&mut self) {} fn channel(&self) -> u8; @@ -80,10 +74,10 @@ pub(crate) mod sealed { } /// ADC instance. -#[cfg(not(any(adc_f1, adc_v1, adc_v2, adc_v3, adc_v4, adc_f3, adc_f3_v1_1, adc_g0)))] +#[cfg(not(any(adc_f1, adc_v1, adc_l0, adc_v2, adc_v3, adc_v4, adc_f3, adc_f3_v1_1, adc_g0, adc_h5)))] pub trait Instance: sealed::Instance + crate::Peripheral<P = Self> {} /// ADC instance. -#[cfg(any(adc_f1, adc_v1, adc_v2, adc_v3, adc_v4, adc_f3, adc_f3_v1_1, adc_g0))] +#[cfg(any(adc_f1, adc_v1, adc_l0, adc_v2, adc_v3, adc_v4, adc_f3, adc_f3_v1_1, adc_g0, adc_h5))] pub trait Instance: sealed::Instance + crate::Peripheral<P = Self> + crate::rcc::RccPeripheral {} /// ADC pin. @@ -98,17 +92,12 @@ foreach_adc!( crate::pac::$inst } - #[cfg(not(any(adc_f1, adc_v1, adc_f3_v2, adc_f3_v1_1, adc_g0)))] + #[cfg(not(any(adc_f1, adc_v1, adc_l0, adc_f3_v2, adc_f3_v1_1, adc_g0)))] fn common_regs() -> crate::pac::adccommon::AdcCommon { return crate::pac::$common_inst } - #[cfg(adc_f3)] - fn frequency() -> crate::time::Hertz { - unsafe { crate::rcc::get_freqs() }.$clock.unwrap() - } - - #[cfg(any(adc_f1, adc_f3, adc_v1, adc_f3_v1_1))] + #[cfg(any(adc_f1, adc_f3, adc_v1, adc_l0, adc_f3_v1_1))] fn state() -> &'static sealed::State { static STATE: sealed::State = sealed::State::new(); &STATE @@ -132,7 +121,7 @@ macro_rules! impl_adc_pin { impl crate::adc::AdcPin<peripherals::$inst> for crate::peripherals::$pin {} impl crate::adc::sealed::AdcPin<peripherals::$inst> for crate::peripherals::$pin { - #[cfg(any(adc_v1, adc_v2))] + #[cfg(any(adc_v1, adc_l0, adc_v2))] fn set_as_analog(&mut self) { <Self as crate::gpio::sealed::Pin>::set_as_analog(self); } @@ -143,3 +132,27 @@ macro_rules! impl_adc_pin { } }; } + +/// Get the maximum reading value for this resolution. +/// +/// This is `2**n - 1`. +#[cfg(not(any(adc_f1, adc_f3_v2)))] +pub const fn resolution_to_max_count(res: Resolution) -> u32 { + match res { + #[cfg(adc_v4)] + Resolution::BITS16 => (1 << 16) - 1, + #[cfg(adc_v4)] + Resolution::BITS14 => (1 << 14) - 1, + #[cfg(adc_v4)] + Resolution::BITS14V => (1 << 14) - 1, + #[cfg(adc_v4)] + Resolution::BITS12V => (1 << 12) - 1, + Resolution::BITS12 => (1 << 12) - 1, + Resolution::BITS10 => (1 << 10) - 1, + Resolution::BITS8 => (1 << 8) - 1, + #[cfg(any(adc_v1, adc_v2, adc_v3, adc_l0, adc_g0, adc_f3, adc_f3_v1_1, adc_h5))] + Resolution::BITS6 => (1 << 6) - 1, + #[allow(unreachable_patterns)] + _ => core::unreachable!(), + } +} diff --git a/embassy-stm32/src/adc/resolution.rs b/embassy-stm32/src/adc/resolution.rs deleted file mode 100644 index 64c25a776..000000000 --- a/embassy-stm32/src/adc/resolution.rs +++ /dev/null @@ -1,72 +0,0 @@ -/// ADC resolution -#[allow(missing_docs)] -#[cfg(any(adc_v1, adc_v2, adc_v3, adc_g0, adc_f3, adc_f3_v1_1))] -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum Resolution { - TwelveBit, - TenBit, - EightBit, - SixBit, -} - -/// ADC resolution -#[allow(missing_docs)] -#[cfg(adc_v4)] -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -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, adc_f3, adc_f3_v1_1))] - { - Self::TwelveBit - } - #[cfg(adc_v4)] - { - Self::SixteenBit - } - } -} - -impl From<Resolution> 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, adc_f3, adc_f3_v1_1))] - Resolution::SixBit => crate::pac::adc::vals::Res::SIXBIT, - } - } -} - -impl Resolution { - /// Get the maximum reading value for this resolution. - /// - /// This is `2**n - 1`. - 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, adc_f3, adc_f3_v1_1))] - Resolution::SixBit => (1 << 6) - 1, - } - } -} diff --git a/embassy-stm32/src/adc/sample_time.rs b/embassy-stm32/src/adc/sample_time.rs deleted file mode 100644 index 5a06f1a5a..000000000 --- a/embassy-stm32/src/adc/sample_time.rs +++ /dev/null @@ -1,148 +0,0 @@ -#[cfg(not(adc_f3_v2))] -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)] - #[cfg_attr(feature = "defmt", derive(defmt::Format))] - pub enum SampleTime { - $( - #[doc = concat!($doc, " ADC clock cycles.")] - $variant, - )* - } - - impl From<SampleTime> 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 From<crate::pac::adc::vals::SampleTime> for SampleTime { - fn from(sample_time: crate::pac::adc::vals::SampleTime) -> SampleTime { - match sample_time { - $(crate::pac::adc::vals::SampleTime::$pac_variant => SampleTime::$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) - ) -); - -#[cfg(adc_f3)] -impl_sample_time!( - "1.5", - Cycles1_5, - ( - ("1.5", Cycles1_5, CYCLES1_5), - ("2.5", Cycles2_5, CYCLES2_5), - ("4.5", Cycles4_5, CYCLES4_5), - ("7.5", Cycles7_5, CYCLES7_5), - ("19.5", Cycles19_5, CYCLES19_5), - ("61.5", Cycles61_5, CYCLES61_5), - ("181.5", Cycles181_5, CYCLES181_5), - ("601.5", Cycles601_5, CYCLES601_5) - ) -); - -#[cfg(any(adc_f3_v1_1))] -impl_sample_time!( - "4", - Cycles4, - ( - ("4", Cycles4, CYCLES4), - ("9", Cycles9, CYCLES9), - ("16", Cycles16, CYCLES16), - ("24", Cycles24, CYCLES24), - ("48", Cycles48, CYCLES48), - ("96", Cycles96, CYCLES96), - ("192", Cycles192, CYCLES192), - ("384", Cycles384, CYCLES384) - ) -); diff --git a/embassy-stm32/src/adc/v1.rs b/embassy-stm32/src/adc/v1.rs index 852b027df..a8dc6ce98 100644 --- a/embassy-stm32/src/adc/v1.rs +++ b/embassy-stm32/src/adc/v1.rs @@ -4,6 +4,8 @@ use core::task::Poll; use embassy_hal_internal::into_ref; use embedded_hal_02::blocking::delay::DelayUs; +#[cfg(adc_l0)] +use stm32_metapac::adc::vals::Ckmode; use crate::adc::{Adc, AdcPin, Instance, Resolution, SampleTime}; use crate::interrupt::typelevel::Interrupt; @@ -30,8 +32,13 @@ impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandl } } +#[cfg(not(adc_l0))] pub struct Vbat; + +#[cfg(not(adc_l0))] impl AdcPin<ADC> for Vbat {} + +#[cfg(not(adc_l0))] impl super::sealed::AdcPin<ADC> for Vbat { fn channel(&self) -> u8 { 18 @@ -69,9 +76,18 @@ impl<'d, T: Instance> Adc<'d, T> { // tstab = 14 * 1/fadc delay.delay_us(1); + // set default PCKL/2 on L0s because HSI is disabled in the default clock config + #[cfg(adc_l0)] + T::regs().cfgr2().modify(|reg| reg.set_ckmode(Ckmode::PCLK_DIV2)); + // A.7.1 ADC calibration code example T::regs().cfgr1().modify(|reg| reg.set_dmaen(false)); T::regs().cr().modify(|reg| reg.set_adcal(true)); + + #[cfg(adc_l0)] + while !T::regs().isr().read().eocal() {} + + #[cfg(not(adc_l0))] while T::regs().cr().read().adcal() {} // A.7.2 ADC enable sequence code example @@ -93,10 +109,11 @@ impl<'d, T: Instance> Adc<'d, T> { Self { adc, - sample_time: Default::default(), + sample_time: SampleTime::from_bits(0), } } + #[cfg(not(adc_l0))] pub fn enable_vbat(&self, _delay: &mut impl DelayUs<u32>) -> Vbat { // SMP must be ≥ 56 ADC clock cycles when using HSI14. // @@ -133,6 +150,12 @@ impl<'d, T: Instance> Adc<'d, T> { T::regs().cfgr1().modify(|reg| reg.set_res(resolution.into())); } + #[cfg(adc_l0)] + pub fn set_ckmode(&mut self, ckmode: Ckmode) { + // set ADC clock mode + T::regs().cfgr2().modify(|reg| reg.set_ckmode(ckmode)); + } + pub async fn read(&mut self, pin: &mut impl AdcPin<T>) -> u16 { let channel = pin.channel(); pin.set_as_analog(); diff --git a/embassy-stm32/src/adc/v2.rs b/embassy-stm32/src/adc/v2.rs index 4622b40a9..f6f7dbfcc 100644 --- a/embassy-stm32/src/adc/v2.rs +++ b/embassy-stm32/src/adc/v2.rs @@ -34,7 +34,7 @@ impl AdcPin<ADC1> for Temperature {} impl super::sealed::AdcPin<ADC1> for Temperature { fn channel(&self) -> u8 { cfg_if::cfg_if! { - if #[cfg(any(stm32f40, stm32f41))] { + if #[cfg(any(stm32f2, stm32f40, stm32f41))] { 16 } else { 18 @@ -67,7 +67,11 @@ enum Prescaler { impl Prescaler { fn from_pclk2(freq: Hertz) -> Self { + // Datasheet for F2 specifies min frequency 0.6 MHz, and max 30 MHz (with VDDA 2.4-3.6V). + #[cfg(stm32f2)] + const MAX_FREQUENCY: Hertz = Hertz(30_000_000); // Datasheet for both F4 and F7 specifies min frequency 0.6 MHz, typ freq. 30 MHz and max 36 MHz. + #[cfg(not(stm32f2))] const MAX_FREQUENCY: Hertz = Hertz(36_000_000); let raw_div = freq.0 / MAX_FREQUENCY.0; match raw_div { @@ -107,7 +111,7 @@ where Self { adc, - sample_time: Default::default(), + sample_time: SampleTime::from_bits(0), } } diff --git a/embassy-stm32/src/adc/v3.rs b/embassy-stm32/src/adc/v3.rs index 281a99f72..5f3512cad 100644 --- a/embassy-stm32/src/adc/v3.rs +++ b/embassy-stm32/src/adc/v3.rs @@ -1,3 +1,4 @@ +use cfg_if::cfg_if; use embassy_hal_internal::into_ref; use embedded_hal_02::blocking::delay::DelayUs; @@ -13,10 +14,15 @@ pub struct VrefInt; impl<T: Instance> AdcPin<T> for VrefInt {} impl<T: Instance> super::sealed::AdcPin<T> for VrefInt { fn channel(&self) -> u8 { - #[cfg(not(adc_g0))] - let val = 0; - #[cfg(adc_g0)] - let val = 13; + cfg_if! { + if #[cfg(adc_g0)] { + let val = 13; + } else if #[cfg(adc_h5)] { + let val = 17; + } else { + let val = 0; + } + } val } } @@ -25,10 +31,15 @@ pub struct Temperature; impl<T: Instance> AdcPin<T> for Temperature {} impl<T: Instance> super::sealed::AdcPin<T> for Temperature { fn channel(&self) -> u8 { - #[cfg(not(adc_g0))] - let val = 17; - #[cfg(adc_g0)] - let val = 12; + cfg_if! { + if #[cfg(adc_g0)] { + let val = 12; + } else if #[cfg(adc_h5)] { + let val = 16; + } else { + let val = 17; + } + } val } } @@ -37,14 +48,31 @@ pub struct Vbat; impl<T: Instance> AdcPin<T> for Vbat {} impl<T: Instance> super::sealed::AdcPin<T> for Vbat { fn channel(&self) -> u8 { - #[cfg(not(adc_g0))] - let val = 18; - #[cfg(adc_g0)] - let val = 14; + cfg_if! { + if #[cfg(adc_g0)] { + let val = 14; + } else if #[cfg(adc_h5)] { + let val = 2; + } else { + let val = 18; + } + } val } } +cfg_if! { + if #[cfg(adc_h5)] { + pub struct VddCore; + impl<T: Instance> AdcPin<T> for VddCore {} + impl<T: Instance> super::sealed::AdcPin<T> for VddCore { + fn channel(&self) -> u8 { + 6 + } + } + } +} + impl<'d, T: Instance> Adc<'d, T> { pub fn new(adc: impl Peripheral<P = T> + 'd, delay: &mut impl DelayUs<u32>) -> Self { into_ref!(adc); @@ -74,7 +102,7 @@ impl<'d, T: Instance> Adc<'d, T> { Self { adc, - sample_time: Default::default(), + sample_time: SampleTime::from_bits(0), } } @@ -98,27 +126,41 @@ impl<'d, T: Instance> Adc<'d, T> { } pub fn enable_temperature(&self) -> Temperature { - #[cfg(not(adc_g0))] - T::common_regs().ccr().modify(|reg| { - reg.set_ch17sel(true); - }); - #[cfg(adc_g0)] - T::regs().ccr().modify(|reg| { - reg.set_tsen(true); - }); + cfg_if! { + if #[cfg(adc_g0)] { + T::regs().ccr().modify(|reg| { + reg.set_tsen(true); + }); + } else if #[cfg(adc_h5)] { + T::common_regs().ccr().modify(|reg| { + reg.set_tsen(true); + }); + } else { + T::common_regs().ccr().modify(|reg| { + reg.set_ch17sel(true); + }); + } + } Temperature {} } pub fn enable_vbat(&self) -> Vbat { - #[cfg(not(adc_g0))] - T::common_regs().ccr().modify(|reg| { - reg.set_ch18sel(true); - }); - #[cfg(adc_g0)] - T::regs().ccr().modify(|reg| { - reg.set_vbaten(true); - }); + cfg_if! { + if #[cfg(adc_g0)] { + T::regs().ccr().modify(|reg| { + reg.set_vbaten(true); + }); + } else if #[cfg(adc_h5)] { + T::common_regs().ccr().modify(|reg| { + reg.set_vbaten(true); + }); + } else { + T::common_regs().ccr().modify(|reg| { + reg.set_ch18sel(true); + }); + } + } Vbat {} } @@ -205,16 +247,21 @@ impl<'d, T: Instance> Adc<'d, T> { val } - #[cfg(adc_g0)] fn set_channel_sample_time(_ch: u8, sample_time: SampleTime) { - T::regs().smpr().modify(|reg| reg.set_smp1(sample_time.into())); - } - - #[cfg(not(adc_g0))] - 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)); + cfg_if! { + if #[cfg(adc_g0)] { + T::regs().smpr().modify(|reg| reg.set_smp1(sample_time.into())); + } else if #[cfg(adc_h5)] { + match _ch { + 0..=9 => T::regs().smpr1().modify(|w| w.set_smp(_ch as usize % 10, sample_time.into())), + _ => T::regs().smpr2().modify(|w| w.set_smp(_ch as usize % 10, sample_time.into())), + } + } else { + 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 048e73184..3fd047375 100644 --- a/embassy-stm32/src/adc/v4.rs +++ b/embassy-stm32/src/adc/v4.rs @@ -159,7 +159,7 @@ impl<'d, T: Instance> Adc<'d, T> { } let mut s = Self { adc, - sample_time: Default::default(), + sample_time: SampleTime::from_bits(0), }; s.power_up(delay); s.configure_differential_inputs(); diff --git a/embassy-stm32/src/can/bxcan.rs b/embassy-stm32/src/can/bxcan.rs index cc87b2565..7e00eca6f 100644 --- a/embassy-stm32/src/can/bxcan.rs +++ b/embassy-stm32/src/can/bxcan.rs @@ -13,9 +13,12 @@ use crate::gpio::sealed::AFType; use crate::interrupt::typelevel::Interrupt; use crate::pac::can::vals::{Ide, Lec}; use crate::rcc::RccPeripheral; -use crate::time::Hertz; use crate::{interrupt, peripherals, Peripheral}; +pub mod enums; +use enums::*; +pub mod util; + /// Contains CAN frame and additional metadata. /// /// Timestamp is available if `time` feature is enabled. @@ -93,23 +96,6 @@ pub struct Can<'d, T: Instance> { can: bxcan::Can<BxcanInstance<'d, T>>, } -/// CAN bus error -#[allow(missing_docs)] -#[derive(Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum BusError { - Stuff, - Form, - Acknowledge, - BitRecessive, - BitDominant, - Crc, - Software, - BusOff, - BusPassive, - BusWarning, -} - /// Error returned by `try_read` #[derive(Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -186,8 +172,15 @@ impl<'d, T: Instance> Can<'d, T> { /// Set CAN bit rate. pub fn set_bitrate(&mut self, bitrate: u32) { - let bit_timing = Self::calc_bxcan_timings(T::frequency(), bitrate).unwrap(); - self.can.modify_config().set_bit_timing(bit_timing).leave_disabled(); + let bit_timing = util::calc_can_timings(T::frequency(), bitrate).unwrap(); + let sjw = u8::from(bit_timing.sync_jump_width) as u32; + let seg1 = u8::from(bit_timing.seg1) as u32; + let seg2 = u8::from(bit_timing.seg2) as u32; + let prescaler = u16::from(bit_timing.prescaler) as u32; + self.can + .modify_config() + .set_bit_timing((sjw - 1) << 24 | (seg1 - 1) << 16 | (seg2 - 1) << 20 | (prescaler - 1)) + .leave_disabled(); } /// Enables the peripheral and synchronizes with the bus. @@ -302,97 +295,6 @@ impl<'d, T: Instance> Can<'d, T> { } } - const fn calc_bxcan_timings(periph_clock: Hertz, can_bitrate: u32) -> Option<u32> { - 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 - 1)) - } - /// Split the CAN driver into transmit and receive halves. /// /// Useful for doing separate transmit/receive tasks. diff --git a/embassy-stm32/src/can/enums.rs b/embassy-stm32/src/can/enums.rs new file mode 100644 index 000000000..36139a45c --- /dev/null +++ b/embassy-stm32/src/can/enums.rs @@ -0,0 +1,30 @@ +//! Enums shared between CAN controller types. + +/// Bus error +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum BusError { + /// Bit stuffing error - more than 5 equal bits + Stuff, + /// Form error - A fixed format part of a received message has wrong format + Form, + /// The message transmitted by the FDCAN was not acknowledged by another node. + Acknowledge, + /// Bit0Error: During the transmission of a message the device wanted to send a dominant level + /// but the monitored bus value was recessive. + BitRecessive, + /// Bit1Error: During the transmission of a message the device wanted to send a recessive level + /// but the monitored bus value was dominant. + BitDominant, + /// The CRC check sum of a received message was incorrect. The CRC of an + /// incoming message does not match with the CRC calculated from the received data. + Crc, + /// A software error occured + Software, + /// The FDCAN is in Bus_Off state. + BusOff, + /// The FDCAN is in the Error_Passive state. + BusPassive, + /// At least one of error counter has reached the Error_Warning limit of 96. + BusWarning, +} diff --git a/embassy-stm32/src/can/fd/config.rs b/embassy-stm32/src/can/fd/config.rs new file mode 100644 index 000000000..68161ca50 --- /dev/null +++ b/embassy-stm32/src/can/fd/config.rs @@ -0,0 +1,475 @@ +//! Configuration for FDCAN Module +// Note: This file is copied and modified from fdcan crate by Richard Meadows + +use core::num::{NonZeroU16, NonZeroU8}; + +/// Configures the bit timings. +/// +/// You can use <http://www.bittiming.can-wiki.info/> to calculate the `btr` parameter. Enter +/// parameters as follows: +/// +/// - *Clock Rate*: The input clock speed to the CAN peripheral (*not* the CPU clock speed). +/// This is the clock rate of the peripheral bus the CAN peripheral is attached to (eg. APB1). +/// - *Sample Point*: Should normally be left at the default value of 87.5%. +/// - *SJW*: Should normally be left at the default value of 1. +/// +/// Then copy the `CAN_BUS_TIME` register value from the table and pass it as the `btr` +/// parameter to this method. +#[derive(Clone, Copy, Debug)] +pub struct NominalBitTiming { + /// Value by which the oscillator frequency is divided for generating the bit time quanta. The bit + /// time is built up from a multiple of this quanta. Valid values are 1 to 512. + pub prescaler: NonZeroU16, + /// Valid values are 1 to 128. + pub seg1: NonZeroU8, + /// Valid values are 1 to 255. + pub seg2: NonZeroU8, + /// Valid values are 1 to 128. + pub sync_jump_width: NonZeroU8, +} +impl NominalBitTiming { + #[inline] + pub(crate) fn nbrp(&self) -> u16 { + u16::from(self.prescaler) & 0x1FF + } + #[inline] + pub(crate) fn ntseg1(&self) -> u8 { + u8::from(self.seg1) + } + #[inline] + pub(crate) fn ntseg2(&self) -> u8 { + u8::from(self.seg2) & 0x7F + } + #[inline] + pub(crate) fn nsjw(&self) -> u8 { + u8::from(self.sync_jump_width) & 0x7F + } +} + +impl Default for NominalBitTiming { + #[inline] + fn default() -> Self { + // Kernel Clock 8MHz, Bit rate: 500kbit/s. Corresponds to a NBTP + // register value of 0x0600_0A03 + Self { + prescaler: NonZeroU16::new(1).unwrap(), + seg1: NonZeroU8::new(11).unwrap(), + seg2: NonZeroU8::new(4).unwrap(), + sync_jump_width: NonZeroU8::new(4).unwrap(), + } + } +} + +/// Configures the data bit timings for the FdCan Variable Bitrates. +/// This is not used when frame_transmit is set to anything other than AllowFdCanAndBRS. +#[derive(Clone, Copy, Debug)] +pub struct DataBitTiming { + /// Tranceiver Delay Compensation + pub transceiver_delay_compensation: bool, + /// The value by which the oscillator frequency is divided to generate the bit time quanta. The bit + /// time is built up from a multiple of this quanta. Valid values for the Baud Rate Prescaler are 1 + /// to 31. + pub prescaler: NonZeroU16, + /// Valid values are 1 to 31. + pub seg1: NonZeroU8, + /// Valid values are 1 to 15. + pub seg2: NonZeroU8, + /// Must always be smaller than DTSEG2, valid values are 1 to 15. + pub sync_jump_width: NonZeroU8, +} +impl DataBitTiming { + // #[inline] + // fn tdc(&self) -> u8 { + // let tsd = self.transceiver_delay_compensation as u8; + // //TODO: stm32g4 does not export the TDC field + // todo!() + // } + #[inline] + pub(crate) fn dbrp(&self) -> u8 { + (u16::from(self.prescaler) & 0x001F) as u8 + } + #[inline] + pub(crate) fn dtseg1(&self) -> u8 { + u8::from(self.seg1) & 0x1F + } + #[inline] + pub(crate) fn dtseg2(&self) -> u8 { + u8::from(self.seg2) & 0x0F + } + #[inline] + pub(crate) fn dsjw(&self) -> u8 { + u8::from(self.sync_jump_width) & 0x0F + } +} + +impl Default for DataBitTiming { + #[inline] + fn default() -> Self { + // Kernel Clock 8MHz, Bit rate: 500kbit/s. Corresponds to a DBTP + // register value of 0x0000_0A33 + Self { + transceiver_delay_compensation: false, + prescaler: NonZeroU16::new(1).unwrap(), + seg1: NonZeroU8::new(11).unwrap(), + seg2: NonZeroU8::new(4).unwrap(), + sync_jump_width: NonZeroU8::new(4).unwrap(), + } + } +} + +/// Configures which modes to use +/// Individual headers can contain a desire to be send via FdCan +/// or use Bit rate switching. But if this general setting does not allow +/// that, only classic CAN is used instead. +#[derive(Clone, Copy, Debug)] +pub enum FrameTransmissionConfig { + /// Only allow Classic CAN message Frames + ClassicCanOnly, + /// Allow (non-brs) FdCAN Message Frames + AllowFdCan, + /// Allow FdCAN Message Frames and allow Bit Rate Switching + AllowFdCanAndBRS, +} + +/// +#[derive(Clone, Copy, Debug)] +pub enum ClockDivider { + /// Divide by 1 + _1 = 0b0000, + /// Divide by 2 + _2 = 0b0001, + /// Divide by 4 + _4 = 0b0010, + /// Divide by 6 + _6 = 0b0011, + /// Divide by 8 + _8 = 0b0100, + /// Divide by 10 + _10 = 0b0101, + /// Divide by 12 + _12 = 0b0110, + /// Divide by 14 + _14 = 0b0111, + /// Divide by 16 + _16 = 0b1000, + /// Divide by 18 + _18 = 0b1001, + /// Divide by 20 + _20 = 0b1010, + /// Divide by 22 + _22 = 0b1011, + /// Divide by 24 + _24 = 0b1100, + /// Divide by 26 + _26 = 0b1101, + /// Divide by 28 + _28 = 0b1110, + /// Divide by 30 + _30 = 0b1111, +} + +/// Prescaler of the Timestamp counter +#[derive(Clone, Copy, Debug)] +pub enum TimestampPrescaler { + /// 1 + _1 = 1, + /// 2 + _2 = 2, + /// 3 + _3 = 3, + /// 4 + _4 = 4, + /// 5 + _5 = 5, + /// 6 + _6 = 6, + /// 7 + _7 = 7, + /// 8 + _8 = 8, + /// 9 + _9 = 9, + /// 10 + _10 = 10, + /// 11 + _11 = 11, + /// 12 + _12 = 12, + /// 13 + _13 = 13, + /// 14 + _14 = 14, + /// 15 + _15 = 15, + /// 16 + _16 = 16, +} + +/// Selects the source of the Timestamp counter +#[derive(Clone, Copy, Debug)] +pub enum TimestampSource { + /// The Timestamp counter is disabled + None, + /// Using the FdCan input clock as the Timstamp counter's source, + /// and using a specific prescaler + Prescaler(TimestampPrescaler), + /// Using TIM3 as a source + FromTIM3, +} + +/// How to handle frames in the global filter +#[derive(Clone, Copy, Debug)] +pub enum NonMatchingFilter { + /// Frames will go to Fifo0 when they do no match any specific filter + IntoRxFifo0 = 0b00, + /// Frames will go to Fifo1 when they do no match any specific filter + IntoRxFifo1 = 0b01, + /// Frames will be rejected when they do not match any specific filter + Reject = 0b11, +} + +/// How to handle frames which do not match a specific filter +#[derive(Clone, Copy, Debug)] +pub struct GlobalFilter { + /// How to handle non-matching standard frames + pub handle_standard_frames: NonMatchingFilter, + + /// How to handle non-matching extended frames + pub handle_extended_frames: NonMatchingFilter, + + /// How to handle remote standard frames + pub reject_remote_standard_frames: bool, + + /// How to handle remote extended frames + pub reject_remote_extended_frames: bool, +} +impl GlobalFilter { + /// Reject all non-matching and remote frames + pub const fn reject_all() -> Self { + Self { + handle_standard_frames: NonMatchingFilter::Reject, + handle_extended_frames: NonMatchingFilter::Reject, + reject_remote_standard_frames: true, + reject_remote_extended_frames: true, + } + } + + /// How to handle non-matching standard frames + pub const fn set_handle_standard_frames(mut self, filter: NonMatchingFilter) -> Self { + self.handle_standard_frames = filter; + self + } + /// How to handle non-matching exteded frames + pub const fn set_handle_extended_frames(mut self, filter: NonMatchingFilter) -> Self { + self.handle_extended_frames = filter; + self + } + /// How to handle remote standard frames + pub const fn set_reject_remote_standard_frames(mut self, filter: bool) -> Self { + self.reject_remote_standard_frames = filter; + self + } + /// How to handle remote extended frames + pub const fn set_reject_remote_extended_frames(mut self, filter: bool) -> Self { + self.reject_remote_extended_frames = filter; + self + } +} +impl Default for GlobalFilter { + #[inline] + fn default() -> Self { + Self { + handle_standard_frames: NonMatchingFilter::IntoRxFifo0, + handle_extended_frames: NonMatchingFilter::IntoRxFifo0, + reject_remote_standard_frames: false, + reject_remote_extended_frames: false, + } + } +} + +/// TX buffer operation mode +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum TxBufferMode { + /// TX FIFO operation - In this mode CAN frames are trasmitted strictly in write order. + Fifo, + /// TX priority queue operation - In this mode CAN frames are transmitted according to CAN priority. + Priority, +} + +impl From<TxBufferMode> for crate::pac::can::vals::Tfqm { + fn from(value: TxBufferMode) -> Self { + match value { + TxBufferMode::Priority => Self::QUEUE, + TxBufferMode::Fifo => Self::FIFO, + } + } +} + +impl From<crate::pac::can::vals::Tfqm> for TxBufferMode { + fn from(value: crate::pac::can::vals::Tfqm) -> Self { + match value { + crate::pac::can::vals::Tfqm::QUEUE => Self::Priority, + crate::pac::can::vals::Tfqm::FIFO => Self::Fifo, + } + } +} + +/// FdCan Config Struct +#[derive(Clone, Copy, Debug)] +pub struct FdCanConfig { + /// Nominal Bit Timings + pub nbtr: NominalBitTiming, + /// (Variable) Data Bit Timings + pub dbtr: DataBitTiming, + /// Enables or disables automatic retransmission of messages + /// + /// If this is enabled, the CAN peripheral will automatically try to retransmit each frame + /// util it can be sent. Otherwise, it will try only once to send each frame. + /// + /// Automatic retransmission is enabled by default. + pub automatic_retransmit: bool, + /// Enabled or disables the pausing between transmissions + /// + /// This feature looses up burst transmissions coming from a single node and it protects against + /// "babbling idiot" scenarios where the application program erroneously requests too many + /// transmissions. + pub transmit_pause: bool, + /// Enabled or disables the pausing between transmissions + /// + /// This feature looses up burst transmissions coming from a single node and it protects against + /// "babbling idiot" scenarios where the application program erroneously requests too many + /// transmissions. + pub frame_transmit: FrameTransmissionConfig, + /// Non Isoe Mode + /// If this is set, the FDCAN uses the CAN FD frame format as specified by the Bosch CAN + /// FD Specification V1.0. + pub non_iso_mode: bool, + /// Edge Filtering: Two consecutive dominant tq required to detect an edge for hard synchronization + pub edge_filtering: bool, + /// Enables protocol exception handling + pub protocol_exception_handling: bool, + /// Sets the general clock divider for this FdCAN instance + pub clock_divider: ClockDivider, + /// Sets the timestamp source + pub timestamp_source: TimestampSource, + /// Configures the Global Filter + pub global_filter: GlobalFilter, + /// TX buffer mode (FIFO or priority queue) + pub tx_buffer_mode: TxBufferMode, +} + +impl FdCanConfig { + /// Configures the bit timings. + #[inline] + pub const fn set_nominal_bit_timing(mut self, btr: NominalBitTiming) -> Self { + self.nbtr = btr; + self + } + + /// Configures the bit timings. + #[inline] + pub const fn set_data_bit_timing(mut self, btr: DataBitTiming) -> Self { + self.dbtr = btr; + self + } + + /// Enables or disables automatic retransmission of messages + /// + /// If this is enabled, the CAN peripheral will automatically try to retransmit each frame + /// util it can be sent. Otherwise, it will try only once to send each frame. + /// + /// Automatic retransmission is enabled by default. + #[inline] + pub const fn set_automatic_retransmit(mut self, enabled: bool) -> Self { + self.automatic_retransmit = enabled; + self + } + + /// Enabled or disables the pausing between transmissions + /// + /// This feature looses up burst transmissions coming from a single node and it protects against + /// "babbling idiot" scenarios where the application program erroneously requests too many + /// transmissions. + #[inline] + pub const fn set_transmit_pause(mut self, enabled: bool) -> Self { + self.transmit_pause = enabled; + self + } + + /// If this is set, the FDCAN uses the CAN FD frame format as specified by the Bosch CAN + /// FD Specification V1.0. + #[inline] + pub const fn set_non_iso_mode(mut self, enabled: bool) -> Self { + self.non_iso_mode = enabled; + self + } + + /// Two consecutive dominant tq required to detect an edge for hard synchronization + #[inline] + pub const fn set_edge_filtering(mut self, enabled: bool) -> Self { + self.edge_filtering = enabled; + self + } + + /// Sets the allowed transmission types for messages. + #[inline] + pub const fn set_frame_transmit(mut self, fts: FrameTransmissionConfig) -> Self { + self.frame_transmit = fts; + self + } + + /// Enables protocol exception handling + #[inline] + pub const fn set_protocol_exception_handling(mut self, peh: bool) -> Self { + self.protocol_exception_handling = peh; + self + } + + /// Sets the general clock divider for this FdCAN instance + #[inline] + pub const fn set_clock_divider(mut self, div: ClockDivider) -> Self { + self.clock_divider = div; + self + } + + /// Sets the timestamp source + #[inline] + pub const fn set_timestamp_source(mut self, tss: TimestampSource) -> Self { + self.timestamp_source = tss; + self + } + + /// Sets the global filter settings + #[inline] + pub const fn set_global_filter(mut self, filter: GlobalFilter) -> Self { + self.global_filter = filter; + self + } + + /// Sets the TX buffer mode (FIFO or priority queue) + #[inline] + pub const fn set_tx_buffer_mode(mut self, txbm: TxBufferMode) -> Self { + self.tx_buffer_mode = txbm; + self + } +} + +impl Default for FdCanConfig { + #[inline] + fn default() -> Self { + Self { + nbtr: NominalBitTiming::default(), + dbtr: DataBitTiming::default(), + automatic_retransmit: true, + transmit_pause: false, + frame_transmit: FrameTransmissionConfig::ClassicCanOnly, + non_iso_mode: false, + edge_filtering: false, + protocol_exception_handling: true, + clock_divider: ClockDivider::_1, + timestamp_source: TimestampSource::None, + global_filter: GlobalFilter::default(), + tx_buffer_mode: TxBufferMode::Priority, + } + } +} diff --git a/embassy-stm32/src/can/fd/filter.rs b/embassy-stm32/src/can/fd/filter.rs new file mode 100644 index 000000000..2023a2ef0 --- /dev/null +++ b/embassy-stm32/src/can/fd/filter.rs @@ -0,0 +1,379 @@ +//! Definition of Filter structs for FDCAN Module +// Note: This file is copied and modified from fdcan crate by Richard Meadows + +use embedded_can::{ExtendedId, StandardId}; + +use crate::can::fd::message_ram; +pub use crate::can::fd::message_ram::{EXTENDED_FILTER_MAX, STANDARD_FILTER_MAX}; + +/// A Standard Filter +pub type StandardFilter = Filter<StandardId, u16>; +/// An Extended Filter +pub type ExtendedFilter = Filter<ExtendedId, u32>; + +impl Default for StandardFilter { + fn default() -> Self { + StandardFilter::disable() + } +} +impl Default for ExtendedFilter { + fn default() -> Self { + ExtendedFilter::disable() + } +} + +impl StandardFilter { + /// Accept all messages in FIFO 0 + pub fn accept_all_into_fifo0() -> StandardFilter { + StandardFilter { + filter: FilterType::BitMask { filter: 0x0, mask: 0x0 }, + action: Action::StoreInFifo0, + } + } + + /// Accept all messages in FIFO 1 + pub fn accept_all_into_fifo1() -> StandardFilter { + StandardFilter { + filter: FilterType::BitMask { filter: 0x0, mask: 0x0 }, + action: Action::StoreInFifo1, + } + } + + /// Reject all messages + pub fn reject_all() -> StandardFilter { + StandardFilter { + filter: FilterType::BitMask { filter: 0x0, mask: 0x0 }, + action: Action::Reject, + } + } + + /// Disable the filter + pub fn disable() -> StandardFilter { + StandardFilter { + filter: FilterType::Disabled, + action: Action::Disable, + } + } +} + +impl ExtendedFilter { + /// Accept all messages in FIFO 0 + pub fn accept_all_into_fifo0() -> ExtendedFilter { + ExtendedFilter { + filter: FilterType::BitMask { filter: 0x0, mask: 0x0 }, + action: Action::StoreInFifo0, + } + } + + /// Accept all messages in FIFO 1 + pub fn accept_all_into_fifo1() -> ExtendedFilter { + ExtendedFilter { + filter: FilterType::BitMask { filter: 0x0, mask: 0x0 }, + action: Action::StoreInFifo1, + } + } + + /// Reject all messages + pub fn reject_all() -> ExtendedFilter { + ExtendedFilter { + filter: FilterType::BitMask { filter: 0x0, mask: 0x0 }, + action: Action::Reject, + } + } + + /// Disable the filter + pub fn disable() -> ExtendedFilter { + ExtendedFilter { + filter: FilterType::Disabled, + action: Action::Disable, + } + } +} + +/// Filter Type +#[derive(Clone, Copy, Debug)] +pub enum FilterType<ID, UNIT> +where + ID: Copy + Clone + core::fmt::Debug, + UNIT: Copy + Clone + core::fmt::Debug, +{ + /// Match with a range between two messages + Range { + /// First Id of the range + from: ID, + /// Last Id of the range + to: ID, + }, + /// Match with a bitmask + BitMask { + /// Filter of the bitmask + filter: UNIT, + /// Mask of the bitmask + mask: UNIT, + }, + /// Match with a single ID + DedicatedSingle(ID), + /// Match with one of two ID's + DedicatedDual(ID, ID), + /// Filter is disabled + Disabled, +} +impl<ID, UNIT> From<FilterType<ID, UNIT>> for message_ram::enums::FilterType +where + ID: Copy + Clone + core::fmt::Debug, + UNIT: Copy + Clone + core::fmt::Debug, +{ + fn from(f: FilterType<ID, UNIT>) -> Self { + match f { + FilterType::Range { to: _, from: _ } => Self::RangeFilter, + FilterType::BitMask { filter: _, mask: _ } => Self::ClassicFilter, + FilterType::DedicatedSingle(_) => Self::DualIdFilter, + FilterType::DedicatedDual(_, _) => Self::DualIdFilter, + FilterType::Disabled => Self::FilterDisabled, + } + } +} + +/// Filter Action +#[derive(Clone, Copy, Debug)] +pub enum Action { + /// No Action + Disable = 0b000, + /// Store an matching message in FIFO 0 + StoreInFifo0 = 0b001, + /// Store an matching message in FIFO 1 + StoreInFifo1 = 0b010, + /// Reject an matching message + Reject = 0b011, + /// Flag a matching message (But not store?!?) + FlagHighPrio = 0b100, + /// Flag a matching message as a High Priority message and store it in FIFO 0 + FlagHighPrioAndStoreInFifo0 = 0b101, + /// Flag a matching message as a High Priority message and store it in FIFO 1 + FlagHighPrioAndStoreInFifo1 = 0b110, +} +impl From<Action> for message_ram::enums::FilterElementConfig { + fn from(a: Action) -> Self { + match a { + Action::Disable => Self::DisableFilterElement, + Action::StoreInFifo0 => Self::StoreInFifo0, + Action::StoreInFifo1 => Self::StoreInFifo1, + Action::Reject => Self::Reject, + Action::FlagHighPrio => Self::SetPriority, + Action::FlagHighPrioAndStoreInFifo0 => Self::SetPriorityAndStoreInFifo0, + Action::FlagHighPrioAndStoreInFifo1 => Self::SetPriorityAndStoreInFifo1, + } + } +} + +/// Filter +#[derive(Clone, Copy, Debug)] +pub struct Filter<ID, UNIT> +where + ID: Copy + Clone + core::fmt::Debug, + UNIT: Copy + Clone + core::fmt::Debug, +{ + /// How to match an incoming message + pub filter: FilterType<ID, UNIT>, + /// What to do with a matching message + pub action: Action, +} + +/// Standard Filter Slot +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum StandardFilterSlot { + /// 0 + _0 = 0, + /// 1 + _1 = 1, + /// 2 + _2 = 2, + /// 3 + _3 = 3, + /// 4 + _4 = 4, + /// 5 + _5 = 5, + /// 6 + _6 = 6, + /// 7 + _7 = 7, + /// 8 + _8 = 8, + /// 9 + _9 = 9, + /// 10 + _10 = 10, + /// 11 + _11 = 11, + /// 12 + _12 = 12, + /// 13 + _13 = 13, + /// 14 + _14 = 14, + /// 15 + _15 = 15, + /// 16 + _16 = 16, + /// 17 + _17 = 17, + /// 18 + _18 = 18, + /// 19 + _19 = 19, + /// 20 + _20 = 20, + /// 21 + _21 = 21, + /// 22 + _22 = 22, + /// 23 + _23 = 23, + /// 24 + _24 = 24, + /// 25 + _25 = 25, + /// 26 + _26 = 26, + /// 27 + _27 = 27, +} +impl From<u8> for StandardFilterSlot { + fn from(u: u8) -> Self { + match u { + 0 => StandardFilterSlot::_0, + 1 => StandardFilterSlot::_1, + 2 => StandardFilterSlot::_2, + 3 => StandardFilterSlot::_3, + 4 => StandardFilterSlot::_4, + 5 => StandardFilterSlot::_5, + 6 => StandardFilterSlot::_6, + 7 => StandardFilterSlot::_7, + 8 => StandardFilterSlot::_8, + 9 => StandardFilterSlot::_9, + 10 => StandardFilterSlot::_10, + 11 => StandardFilterSlot::_11, + 12 => StandardFilterSlot::_12, + 13 => StandardFilterSlot::_13, + 14 => StandardFilterSlot::_14, + 15 => StandardFilterSlot::_15, + 16 => StandardFilterSlot::_16, + 17 => StandardFilterSlot::_17, + 18 => StandardFilterSlot::_18, + 19 => StandardFilterSlot::_19, + 20 => StandardFilterSlot::_20, + 21 => StandardFilterSlot::_21, + 22 => StandardFilterSlot::_22, + 23 => StandardFilterSlot::_23, + 24 => StandardFilterSlot::_24, + 25 => StandardFilterSlot::_25, + 26 => StandardFilterSlot::_26, + 27 => StandardFilterSlot::_27, + _ => panic!("Standard Filter Slot Too High!"), + } + } +} + +/// Extended Filter Slot +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum ExtendedFilterSlot { + /// 0 + _0 = 0, + /// 1 + _1 = 1, + /// 2 + _2 = 2, + /// 3 + _3 = 3, + /// 4 + _4 = 4, + /// 5 + _5 = 5, + /// 6 + _6 = 6, + /// 7 + _7 = 7, +} +impl From<u8> for ExtendedFilterSlot { + fn from(u: u8) -> Self { + match u { + 0 => ExtendedFilterSlot::_0, + 1 => ExtendedFilterSlot::_1, + 2 => ExtendedFilterSlot::_2, + 3 => ExtendedFilterSlot::_3, + 4 => ExtendedFilterSlot::_4, + 5 => ExtendedFilterSlot::_5, + 6 => ExtendedFilterSlot::_6, + 7 => ExtendedFilterSlot::_7, + _ => panic!("Extended Filter Slot Too High!"), // Should be unreachable + } + } +} + +/// Enum over both Standard and Extended Filter ID's +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum FilterId { + /// Standard Filter Slots + Standard(StandardFilterSlot), + /// Extended Filter Slots + Extended(ExtendedFilterSlot), +} + +pub(crate) trait ActivateFilter<ID, UNIT> +where + ID: Copy + Clone + core::fmt::Debug, + UNIT: Copy + Clone + core::fmt::Debug, +{ + fn activate(&mut self, f: Filter<ID, UNIT>); + // fn read(&self) -> Filter<ID, UNIT>; +} + +impl ActivateFilter<StandardId, u16> for message_ram::StandardFilter { + fn activate(&mut self, f: Filter<StandardId, u16>) { + let sft = f.filter.into(); + + let (sfid1, sfid2) = match f.filter { + FilterType::Range { to, from } => (to.as_raw(), from.as_raw()), + FilterType::DedicatedSingle(id) => (id.as_raw(), id.as_raw()), + FilterType::DedicatedDual(id1, id2) => (id1.as_raw(), id2.as_raw()), + FilterType::BitMask { filter, mask } => (filter, mask), + FilterType::Disabled => (0x0, 0x0), + }; + let sfec = f.action.into(); + self.write(|w| { + unsafe { w.sfid1().bits(sfid1).sfid2().bits(sfid2) } + .sft() + .set_filter_type(sft) + .sfec() + .set_filter_element_config(sfec) + }); + } + // fn read(&self) -> Filter<StandardId, u16> { + // todo!() + // } +} +impl ActivateFilter<ExtendedId, u32> for message_ram::ExtendedFilter { + fn activate(&mut self, f: Filter<ExtendedId, u32>) { + let eft = f.filter.into(); + + let (efid1, efid2) = match f.filter { + FilterType::Range { to, from } => (to.as_raw(), from.as_raw()), + FilterType::DedicatedSingle(id) => (id.as_raw(), id.as_raw()), + FilterType::DedicatedDual(id1, id2) => (id1.as_raw(), id2.as_raw()), + FilterType::BitMask { filter, mask } => (filter, mask), + FilterType::Disabled => (0x0, 0x0), + }; + let efec = f.action.into(); + self.write(|w| { + unsafe { w.efid1().bits(efid1).efid2().bits(efid2) } + .eft() + .set_filter_type(eft) + .efec() + .set_filter_element_config(efec) + }); + } + // fn read(&self) -> Filter<ExtendedId, u32> { + // todo!() + // } +} diff --git a/embassy-stm32/src/can/fd/message_ram/common.rs b/embassy-stm32/src/can/fd/message_ram/common.rs new file mode 100644 index 000000000..108c1a428 --- /dev/null +++ b/embassy-stm32/src/can/fd/message_ram/common.rs @@ -0,0 +1,134 @@ +// Note: This file is copied and modified from fdcan crate by Richard Meadows +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(unused)] + +use super::enums::{ + BitRateSwitching, ErrorStateIndicator, FilterElementConfig, FilterType, FrameFormat, IdType, + RemoteTransmissionRequest, +}; +use super::generic; + +#[doc = "Reader of field `ID`"] +pub type ID_R = generic::R<u32, u32>; + +#[doc = "Reader of field `RTR`"] +pub type RTR_R = generic::R<bool, RemoteTransmissionRequest>; +impl RTR_R { + pub fn rtr(&self) -> RemoteTransmissionRequest { + match self.bits { + false => RemoteTransmissionRequest::TransmitDataFrame, + true => RemoteTransmissionRequest::TransmitRemoteFrame, + } + } + pub fn is_transmit_remote_frame(&self) -> bool { + *self == RemoteTransmissionRequest::TransmitRemoteFrame + } + pub fn is_transmit_data_frame(&self) -> bool { + *self == RemoteTransmissionRequest::TransmitDataFrame + } +} + +#[doc = "Reader of field `XTD`"] +pub type XTD_R = generic::R<bool, IdType>; +impl XTD_R { + pub fn id_type(&self) -> IdType { + match self.bits() { + false => IdType::StandardId, + true => IdType::ExtendedId, + } + } + pub fn is_standard_id(&self) -> bool { + *self == IdType::StandardId + } + pub fn is_exteded_id(&self) -> bool { + *self == IdType::ExtendedId + } +} + +#[doc = "Reader of field `ESI`"] +pub type ESI_R = generic::R<bool, ErrorStateIndicator>; +impl ESI_R { + pub fn error_state(&self) -> ErrorStateIndicator { + match self.bits() { + false => ErrorStateIndicator::ErrorActive, + true => ErrorStateIndicator::ErrorPassive, + } + } + pub fn is_error_active(&self) -> bool { + *self == ErrorStateIndicator::ErrorActive + } + pub fn is_error_passive(&self) -> bool { + *self == ErrorStateIndicator::ErrorPassive + } +} + +#[doc = "Reader of field `DLC`"] +pub type DLC_R = generic::R<u8, u8>; + +#[doc = "Reader of field `BRS`"] +pub type BRS_R = generic::R<bool, BitRateSwitching>; +impl BRS_R { + pub fn bit_rate_switching(&self) -> BitRateSwitching { + match self.bits() { + true => BitRateSwitching::WithBRS, + false => BitRateSwitching::WithoutBRS, + } + } + pub fn is_with_brs(&self) -> bool { + *self == BitRateSwitching::WithBRS + } + pub fn is_without_brs(&self) -> bool { + *self == BitRateSwitching::WithoutBRS + } +} + +#[doc = "Reader of field `FDF`"] +pub type FDF_R = generic::R<bool, FrameFormat>; +impl FDF_R { + pub fn frame_format(&self) -> FrameFormat { + match self.bits() { + false => FrameFormat::Classic, + true => FrameFormat::Fdcan, + } + } + pub fn is_classic_format(&self) -> bool { + *self == FrameFormat::Classic + } + pub fn is_fdcan_format(&self) -> bool { + *self == FrameFormat::Fdcan + } +} + +#[doc = "Reader of field `(X|S)FT`"] +pub type ESFT_R = generic::R<u8, FilterType>; +impl ESFT_R { + #[doc = r"Gets the Filtertype"] + #[inline(always)] + pub fn to_filter_type(&self) -> FilterType { + match self.bits() { + 0b00 => FilterType::RangeFilter, + 0b01 => FilterType::DualIdFilter, + 0b10 => FilterType::ClassicFilter, + 0b11 => FilterType::FilterDisabled, + _ => unreachable!(), + } + } +} + +#[doc = "Reader of field `(E|S)FEC`"] +pub type ESFEC_R = generic::R<u8, FilterElementConfig>; +impl ESFEC_R { + pub fn to_filter_element_config(&self) -> FilterElementConfig { + match self.bits() { + 0b000 => FilterElementConfig::DisableFilterElement, + 0b001 => FilterElementConfig::StoreInFifo0, + 0b010 => FilterElementConfig::StoreInFifo1, + 0b011 => FilterElementConfig::Reject, + 0b100 => FilterElementConfig::SetPriority, + 0b101 => FilterElementConfig::SetPriorityAndStoreInFifo0, + 0b110 => FilterElementConfig::SetPriorityAndStoreInFifo1, + _ => unimplemented!(), + } + } +} diff --git a/embassy-stm32/src/can/fd/message_ram/enums.rs b/embassy-stm32/src/can/fd/message_ram/enums.rs new file mode 100644 index 000000000..0ec5e0f34 --- /dev/null +++ b/embassy-stm32/src/can/fd/message_ram/enums.rs @@ -0,0 +1,233 @@ +// Note: This file is copied and modified from fdcan crate by Richard Meadows + +/// Datalength is the message length generalised over +/// the Standard (Classic) and FDCAN message types + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum DataLength { + Classic(u8), + Fdcan(u8), +} +impl DataLength { + /// Creates a DataLength type + /// + /// Uses the byte length and Type of frame as input + pub fn new(len: u8, ff: FrameFormat) -> DataLength { + match ff { + FrameFormat::Classic => match len { + 0..=8 => DataLength::Classic(len), + _ => panic!("DataLength > 8"), + }, + FrameFormat::Fdcan => match len { + 0..=64 => DataLength::Fdcan(len), + _ => panic!("DataLength > 64"), + }, + } + } + /// Specialised function to create classic frames + pub fn new_classic(len: u8) -> DataLength { + Self::new(len, FrameFormat::Classic) + } + /// Specialised function to create FDCAN frames + pub fn new_fdcan(len: u8) -> DataLength { + Self::new(len, FrameFormat::Fdcan) + } + + /// returns the length in bytes + pub fn len(&self) -> u8 { + match self { + DataLength::Classic(l) | DataLength::Fdcan(l) => *l, + } + } + + pub(crate) fn dlc(&self) -> u8 { + match self { + DataLength::Classic(l) => *l, + // See RM0433 Rev 7 Table 475. DLC coding + DataLength::Fdcan(l) => match l { + 0..=8 => *l, + 9..=12 => 9, + 13..=16 => 10, + 17..=20 => 11, + 21..=24 => 12, + 25..=32 => 13, + 33..=48 => 14, + 49..=64 => 15, + _ => panic!("DataLength > 64"), + }, + } + } +} +impl From<DataLength> for FrameFormat { + fn from(dl: DataLength) -> FrameFormat { + match dl { + DataLength::Classic(_) => FrameFormat::Classic, + DataLength::Fdcan(_) => FrameFormat::Fdcan, + } + } +} + +/// Wheter or not to generate an Tx Event +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Event { + /// Do not generate an Tx Event + NoEvent, + /// Generate an Tx Event with a specified ID + Event(u8), +} + +impl From<Event> for EventControl { + fn from(e: Event) -> Self { + match e { + Event::NoEvent => EventControl::DoNotStore, + Event::Event(_) => EventControl::Store, + } + } +} + +impl From<Option<u8>> for Event { + fn from(mm: Option<u8>) -> Self { + match mm { + None => Event::NoEvent, + Some(mm) => Event::Event(mm), + } + } +} + +impl From<Event> for Option<u8> { + fn from(e: Event) -> Option<u8> { + match e { + Event::NoEvent => None, + Event::Event(mm) => Some(mm), + } + } +} + +/// TODO +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum ErrorStateIndicator { + /// TODO + ErrorActive = 0, + /// TODO + ErrorPassive = 1, +} +impl From<ErrorStateIndicator> for bool { + #[inline(always)] + fn from(e: ErrorStateIndicator) -> Self { + e as u8 != 0 + } +} + +/// Type of frame, standard (classic) or FdCAN +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum FrameFormat { + Classic = 0, + Fdcan = 1, +} +impl From<FrameFormat> for bool { + #[inline(always)] + fn from(e: FrameFormat) -> Self { + e as u8 != 0 + } +} + +/// Type of Id, Standard or Extended +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum IdType { + /// Standard ID + StandardId = 0, + /// Extended ID + ExtendedId = 1, +} +impl From<IdType> for bool { + #[inline(always)] + fn from(e: IdType) -> Self { + e as u8 != 0 + } +} + +/// Whether the frame contains data or requests data +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum RemoteTransmissionRequest { + /// Frame contains data + TransmitDataFrame = 0, + /// frame does not contain data + TransmitRemoteFrame = 1, +} +impl From<RemoteTransmissionRequest> for bool { + #[inline(always)] + fn from(e: RemoteTransmissionRequest) -> Self { + e as u8 != 0 + } +} + +/// Whether BitRateSwitching should be or was enabled +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum BitRateSwitching { + /// disable bit rate switching + WithoutBRS = 0, + /// enable bit rate switching + WithBRS = 1, +} +impl From<BitRateSwitching> for bool { + #[inline(always)] + fn from(e: BitRateSwitching) -> Self { + e as u8 != 0 + } +} + +/// Whether to store transmit Events +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum EventControl { + /// do not store an tx event + DoNotStore, + /// store transmit events + Store, +} +impl From<EventControl> for bool { + #[inline(always)] + fn from(e: EventControl) -> Self { + e as u8 != 0 + } +} + +/// If an received message matched any filters +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum FilterFrameMatch { + /// This did match filter <id> + DidMatch(u8), + /// This received frame did not match any specific filters + DidNotMatch, +} + +/// Type of filter to be used +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum FilterType { + /// Filter uses the range between two id's + RangeFilter = 0b00, + /// The filter matches on two specific id's (or one ID checked twice) + DualIdFilter = 0b01, + /// Filter is using a bitmask + ClassicFilter = 0b10, + /// Filter is disabled + FilterDisabled = 0b11, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum FilterElementConfig { + /// Filter is disabled + DisableFilterElement = 0b000, + /// Store a matching message in FIFO 0 + StoreInFifo0 = 0b001, + /// Store a matching message in FIFO 1 + StoreInFifo1 = 0b010, + /// Reject a matching message + Reject = 0b011, + /// Flag that a priority message has been received, *But do note store!*?? + SetPriority = 0b100, + /// Flag and store message in FIFO 0 + SetPriorityAndStoreInFifo0 = 0b101, + /// Flag and store message in FIFO 1 + SetPriorityAndStoreInFifo1 = 0b110, + //_Unused = 0b111, +} diff --git a/embassy-stm32/src/can/fd/message_ram/extended_filter.rs b/embassy-stm32/src/can/fd/message_ram/extended_filter.rs new file mode 100644 index 000000000..453e9056e --- /dev/null +++ b/embassy-stm32/src/can/fd/message_ram/extended_filter.rs @@ -0,0 +1,136 @@ +// Note: This file is copied and modified from fdcan crate by Richard Meadows + +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(unused)] + +use super::common::{ESFEC_R, ESFT_R}; +use super::enums::{FilterElementConfig, FilterType}; +use super::generic; + +#[doc = "Reader of register ExtendedFilter"] +pub(crate) type R = generic::R<super::ExtendedFilterType, super::ExtendedFilter>; +#[doc = "Writer for register ExtendedFilter"] +pub(crate) type W = generic::W<super::ExtendedFilterType, super::ExtendedFilter>; +#[doc = "Register ExtendedFilter `reset()`'s"] +impl generic::ResetValue for super::ExtendedFilter { + type Type = super::ExtendedFilterType; + #[inline(always)] + fn reset_value() -> Self::Type { + // Sets filter element to Disabled + [0x0, 0x0] + } +} + +#[doc = "Reader of field `EFID2`"] +pub(crate) type EFID2_R = generic::R<u32, u32>; +#[doc = "Write proxy for field `EFID2`"] +pub(crate) struct EFID2_W<'a> { + w: &'a mut W, +} +impl<'a> EFID2_W<'a> { + #[doc = r"Writes raw bits to the field"] + #[inline(always)] + pub unsafe fn bits(self, value: u32) -> &'a mut W { + self.w.bits[1] = (self.w.bits[1] & !(0x1FFFFFFF)) | ((value as u32) & 0x1FFFFFFF); + self.w + } +} + +#[doc = "Reader of field `EFID1`"] +pub(crate) type EFID1_R = generic::R<u32, u32>; +#[doc = "Write proxy for field `EFID1`"] +pub(crate) struct EFID1_W<'a> { + w: &'a mut W, +} +impl<'a> EFID1_W<'a> { + #[doc = r"Writes raw bits to the field"] + #[inline(always)] + pub unsafe fn bits(self, value: u32) -> &'a mut W { + self.w.bits[0] = (self.w.bits[0] & !(0x1FFFFFFF)) | ((value as u32) & 0x1FFFFFFF); + self.w + } +} + +#[doc = "Write proxy for field `EFEC`"] +pub(crate) struct EFEC_W<'a> { + w: &'a mut W, +} +impl<'a> EFEC_W<'a> { + #[doc = r"Writes raw bits to the field"] + #[inline(always)] + pub unsafe fn bits(self, value: u8) -> &'a mut W { + self.w.bits[0] = (self.w.bits[0] & !(0x07 << 29)) | (((value as u32) & 0x07) << 29); + self.w + } + #[doc = r"Sets the field according to FilterElementConfig"] + #[inline(always)] + pub fn set_filter_element_config(self, fec: FilterElementConfig) -> &'a mut W { + //SAFETY: FilterElementConfig only be valid options + unsafe { self.bits(fec as u8) } + } +} + +#[doc = "Write proxy for field `EFT`"] +pub(crate) struct EFT_W<'a> { + w: &'a mut W, +} +impl<'a> EFT_W<'a> { + #[doc = r"Sets the field according the FilterType"] + #[inline(always)] + pub fn set_filter_type(self, filter: FilterType) -> &'a mut W { + //SAFETY: FilterType only be valid options + unsafe { self.bits(filter as u8) } + } + #[doc = r"Writes raw bits to the field"] + #[inline(always)] + pub unsafe fn bits(self, value: u8) -> &'a mut W { + self.w.bits[1] = (self.w.bits[1] & !(0x03 << 30)) | (((value as u32) & 0x03) << 30); + self.w + } +} + +impl R { + #[doc = "Byte 0 - Bits 0:28 - EFID1"] + #[inline(always)] + pub fn sfid1(&self) -> EFID1_R { + EFID1_R::new(((self.bits[0]) & 0x1FFFFFFF) as u32) + } + #[doc = "Byte 0 - Bits 29:31 - EFEC"] + #[inline(always)] + pub fn efec(&self) -> ESFEC_R { + ESFEC_R::new(((self.bits[0] >> 29) & 0x07) as u8) + } + #[doc = "Byte 1 - Bits 0:28 - EFID2"] + #[inline(always)] + pub fn sfid2(&self) -> EFID2_R { + EFID2_R::new(((self.bits[1]) & 0x1FFFFFFF) as u32) + } + #[doc = "Byte 1 - Bits 30:31 - EFT"] + #[inline(always)] + pub fn eft(&self) -> ESFT_R { + ESFT_R::new(((self.bits[1] >> 30) & 0x03) as u8) + } +} +impl W { + #[doc = "Byte 0 - Bits 0:28 - EFID1"] + #[inline(always)] + pub fn efid1(&mut self) -> EFID1_W { + EFID1_W { w: self } + } + #[doc = "Byte 0 - Bits 29:31 - EFEC"] + #[inline(always)] + pub fn efec(&mut self) -> EFEC_W { + EFEC_W { w: self } + } + #[doc = "Byte 1 - Bits 0:28 - EFID2"] + #[inline(always)] + pub fn efid2(&mut self) -> EFID2_W { + EFID2_W { w: self } + } + #[doc = "Byte 1 - Bits 30:31 - EFT"] + #[inline(always)] + pub fn eft(&mut self) -> EFT_W { + EFT_W { w: self } + } +} diff --git a/embassy-stm32/src/can/fd/message_ram/generic.rs b/embassy-stm32/src/can/fd/message_ram/generic.rs new file mode 100644 index 000000000..1a5e121b4 --- /dev/null +++ b/embassy-stm32/src/can/fd/message_ram/generic.rs @@ -0,0 +1,168 @@ +// Note: This file is copied and modified from fdcan crate by Richard Meadows + +use core::marker; + +///This trait shows that register has `read` method +/// +///Registers marked with `Writable` can be also `modify`'ed +pub trait Readable {} + +///This trait shows that register has `write`, `write_with_zero` and `reset` method +/// +///Registers marked with `Readable` can be also `modify`'ed +pub trait Writable {} + +///Reset value of the register +/// +///This value is initial value for `write` method. +///It can be also directly writed to register by `reset` method. +pub trait ResetValue { + ///Register size + type Type; + ///Reset value of the register + fn reset_value() -> Self::Type; +} + +///This structure provides volatile access to register +pub struct Reg<U, REG> { + register: vcell::VolatileCell<U>, + _marker: marker::PhantomData<REG>, +} + +unsafe impl<U: Send, REG> Send for Reg<U, REG> {} + +impl<U, REG> Reg<U, REG> +where + Self: Readable, + U: Copy, +{ + ///Reads the contents of `Readable` register + /// + ///You can read the contents of a register in such way: + ///```ignore + ///let bits = periph.reg.read().bits(); + ///``` + ///or get the content of a particular field of a register. + ///```ignore + ///let reader = periph.reg.read(); + ///let bits = reader.field1().bits(); + ///let flag = reader.field2().bit_is_set(); + ///``` + #[inline(always)] + pub fn read(&self) -> R<U, Self> { + R { + bits: self.register.get(), + _reg: marker::PhantomData, + } + } +} + +impl<U, REG> Reg<U, REG> +where + Self: ResetValue<Type = U> + Writable, + U: Copy, +{ + ///Writes the reset value to `Writable` register + /// + ///Resets the register to its initial state + #[inline(always)] + pub fn reset(&self) { + self.register.set(Self::reset_value()) + } +} + +impl<U, REG> Reg<U, REG> +where + Self: ResetValue<Type = U> + Writable, + U: Copy, +{ + ///Writes bits to `Writable` register + /// + ///You can write raw bits into a register: + ///```ignore + ///periph.reg.write(|w| unsafe { w.bits(rawbits) }); + ///``` + ///or write only the fields you need: + ///```ignore + ///periph.reg.write(|w| w + /// .field1().bits(newfield1bits) + /// .field2().set_bit() + /// .field3().variant(VARIANT) + ///); + ///``` + ///Other fields will have reset value. + #[inline(always)] + pub fn write<F>(&self, f: F) + where + F: FnOnce(&mut W<U, Self>) -> &mut W<U, Self>, + { + self.register.set( + f(&mut W { + bits: Self::reset_value(), + _reg: marker::PhantomData, + }) + .bits, + ); + } +} + +///Register/field reader +/// +///Result of the [`read`](Reg::read) method of a register. +///Also it can be used in the [`modify`](Reg::read) method +pub struct R<U, T> { + pub(crate) bits: U, + _reg: marker::PhantomData<T>, +} + +impl<U, T> R<U, T> +where + U: Copy, +{ + ///Create new instance of reader + #[inline(always)] + pub(crate) fn new(bits: U) -> Self { + Self { + bits, + _reg: marker::PhantomData, + } + } + ///Read raw bits from register/field + #[inline(always)] + pub fn bits(&self) -> U { + self.bits + } +} + +impl<U, T, FI> PartialEq<FI> for R<U, T> +where + U: PartialEq, + FI: Copy + Into<U>, +{ + #[inline(always)] + fn eq(&self, other: &FI) -> bool { + self.bits.eq(&(*other).into()) + } +} + +impl<FI> R<bool, FI> { + ///Value of the field as raw bits + #[inline(always)] + pub fn bit(&self) -> bool { + self.bits + } + ///Returns `true` if the bit is clear (0) + #[inline(always)] + pub fn bit_is_clear(&self) -> bool { + !self.bit() + } +} + +///Register writer +/// +///Used as an argument to the closures in the [`write`](Reg::write) and [`modify`](Reg::modify) methods of the register +pub struct W<U, REG> { + ///Writable bits + pub(crate) bits: U, + _reg: marker::PhantomData<REG>, +} diff --git a/embassy-stm32/src/can/fd/message_ram/mod.rs b/embassy-stm32/src/can/fd/message_ram/mod.rs new file mode 100644 index 000000000..830edf3bb --- /dev/null +++ b/embassy-stm32/src/can/fd/message_ram/mod.rs @@ -0,0 +1,170 @@ +// Note: This file is copied and modified from fdcan crate by Richard Meadows + +use volatile_register::RW; + +pub(crate) mod common; +pub(crate) mod enums; +pub(crate) mod generic; + +/// Number of Receive Fifos configured by this module +pub const RX_FIFOS_MAX: u8 = 2; +/// Number of Receive Messages per RxFifo configured by this module +pub const RX_FIFO_MAX: u8 = 3; +/// Number of Transmit Messages configured by this module +pub const TX_FIFO_MAX: u8 = 3; +/// Number of Transmit Events configured by this module +pub const TX_EVENT_MAX: u8 = 3; +/// Number of Standard Filters configured by this module +pub const STANDARD_FILTER_MAX: u8 = 28; +/// Number of Extended Filters configured by this module +pub const EXTENDED_FILTER_MAX: u8 = 8; + +/// MessageRam Overlay +#[repr(C)] +pub struct RegisterBlock { + pub(crate) filters: Filters, + pub(crate) receive: [Receive; RX_FIFOS_MAX as usize], + pub(crate) transmit: Transmit, +} +impl RegisterBlock { + pub fn reset(&mut self) { + self.filters.reset(); + self.receive[0].reset(); + self.receive[1].reset(); + self.transmit.reset(); + } +} + +#[repr(C)] +pub(crate) struct Filters { + pub(crate) flssa: [StandardFilter; STANDARD_FILTER_MAX as usize], + pub(crate) flesa: [ExtendedFilter; EXTENDED_FILTER_MAX as usize], +} +impl Filters { + pub fn reset(&mut self) { + for sf in &mut self.flssa { + sf.reset(); + } + for ef in &mut self.flesa { + ef.reset(); + } + } +} + +#[repr(C)] +pub(crate) struct Receive { + pub(crate) fxsa: [RxFifoElement; RX_FIFO_MAX as usize], +} +impl Receive { + pub fn reset(&mut self) { + for fe in &mut self.fxsa { + fe.reset(); + } + } +} + +#[repr(C)] +pub(crate) struct Transmit { + pub(crate) efsa: [TxEventElement; TX_EVENT_MAX as usize], + pub(crate) tbsa: [TxBufferElement; TX_FIFO_MAX as usize], +} +impl Transmit { + pub fn reset(&mut self) { + for ee in &mut self.efsa { + ee.reset(); + } + for be in &mut self.tbsa { + be.reset(); + } + } +} + +pub(crate) mod standard_filter; +pub(crate) type StandardFilterType = u32; +pub(crate) type StandardFilter = generic::Reg<StandardFilterType, _StandardFilter>; +pub(crate) struct _StandardFilter; +impl generic::Readable for StandardFilter {} +impl generic::Writable for StandardFilter {} + +pub(crate) mod extended_filter; +pub(crate) type ExtendedFilterType = [u32; 2]; +pub(crate) type ExtendedFilter = generic::Reg<ExtendedFilterType, _ExtendedFilter>; +pub(crate) struct _ExtendedFilter; +impl generic::Readable for ExtendedFilter {} +impl generic::Writable for ExtendedFilter {} + +pub(crate) mod txevent_element; +pub(crate) type TxEventElementType = [u32; 2]; +pub(crate) type TxEventElement = generic::Reg<TxEventElementType, _TxEventElement>; +pub(crate) struct _TxEventElement; +impl generic::Readable for TxEventElement {} +impl generic::Writable for TxEventElement {} + +pub(crate) mod rxfifo_element; +#[repr(C)] +pub(crate) struct RxFifoElement { + pub(crate) header: RxFifoElementHeader, + pub(crate) data: [RW<u32>; 16], +} +impl RxFifoElement { + pub(crate) fn reset(&mut self) { + self.header.reset(); + for byte in self.data.iter_mut() { + unsafe { byte.write(0) }; + } + } +} +pub(crate) type RxFifoElementHeaderType = [u32; 2]; +pub(crate) type RxFifoElementHeader = generic::Reg<RxFifoElementHeaderType, _RxFifoElement>; +pub(crate) struct _RxFifoElement; +impl generic::Readable for RxFifoElementHeader {} +impl generic::Writable for RxFifoElementHeader {} + +pub(crate) mod txbuffer_element; +#[repr(C)] +pub(crate) struct TxBufferElement { + pub(crate) header: TxBufferElementHeader, + pub(crate) data: [RW<u32>; 16], +} +impl TxBufferElement { + pub(crate) fn reset(&mut self) { + self.header.reset(); + for byte in self.data.iter_mut() { + unsafe { byte.write(0) }; + } + } +} +pub(crate) type TxBufferElementHeader = generic::Reg<TxBufferElementHeaderType, _TxBufferElement>; +pub(crate) type TxBufferElementHeaderType = [u32; 2]; +pub(crate) struct _TxBufferElement; +impl generic::Readable for TxBufferElementHeader {} +impl generic::Writable for TxBufferElementHeader {} + +/// FdCan Message RAM instance. +/// +/// # Safety +/// +/// It is only safe to implement this trait, when: +/// +/// * The implementing type has ownership of the Message RAM, preventing any +/// other accesses to the register block. +/// * `MSG_RAM` is a pointer to the Message RAM block and can be safely accessed +/// for as long as ownership or a borrow of the implementing type is present. +pub unsafe trait Instance { + const MSG_RAM: *mut RegisterBlock; + fn msg_ram(&self) -> &RegisterBlock { + unsafe { &*Self::MSG_RAM } + } + fn msg_ram_mut(&mut self) -> &mut RegisterBlock { + unsafe { &mut *Self::MSG_RAM } + } +} + +// Ensure the RegisterBlock is the same size as on pg 1957 of RM0440. +static_assertions::assert_eq_size!(Filters, [u32; 28 + 16]); +static_assertions::assert_eq_size!(Receive, [u32; 54]); +static_assertions::assert_eq_size!(Transmit, [u32; 6 + 54]); +static_assertions::assert_eq_size!( + RegisterBlock, + [u32; 28 /*Standard Filters*/ +16 /*Extended Filters*/ +54 /*RxFifo0*/ +54 /*RxFifo1*/ +6 /*TxEvent*/ +54 /*TxFifo */] +); diff --git a/embassy-stm32/src/can/fd/message_ram/rxfifo_element.rs b/embassy-stm32/src/can/fd/message_ram/rxfifo_element.rs new file mode 100644 index 000000000..48fc3a091 --- /dev/null +++ b/embassy-stm32/src/can/fd/message_ram/rxfifo_element.rs @@ -0,0 +1,122 @@ +// Note: This file is copied and modified from fdcan crate by Richard Meadows + +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(unused)] + +use super::common::{BRS_R, DLC_R, ESI_R, FDF_R, ID_R, RTR_R, XTD_R}; +use super::enums::{DataLength, FilterFrameMatch, FrameFormat}; +use super::generic; + +#[doc = "Reader of register RxFifoElement"] +pub(crate) type R = generic::R<super::RxFifoElementHeaderType, super::RxFifoElementHeader>; +// #[doc = "Writer for register ExtendedFilter"] +// pub(crate) type W = generic::W<super::RxFifoElementHeaderType, super::RxFifoElementHeader>; +#[doc = "Register ExtendedFilter `reset()`'s"] +impl generic::ResetValue for super::RxFifoElementHeader { + type Type = super::RxFifoElementHeaderType; + #[inline(always)] + fn reset_value() -> Self::Type { + [0x0, 0x0] + } +} + +#[doc = "Reader of field `RXTS`"] +pub(crate) type RXTS_R = generic::R<u16, u16>; + +#[doc = "Reader of field `FIDX`"] +pub(crate) type FIDX_R = generic::R<u8, u8>; + +pub(crate) struct _ANMF; +#[doc = "Reader of field `ANMF`"] +pub(crate) type ANMF_R = generic::R<bool, _ANMF>; +impl ANMF_R { + pub fn is_matching_frame(&self) -> bool { + self.bit_is_clear() + } +} + +impl R { + #[doc = "Byte 0 - Bits 0:28 - ID"] + #[inline(always)] + pub fn id(&self) -> ID_R { + ID_R::new(((self.bits[0]) & 0x1FFFFFFF) as u32) + } + #[doc = "Byte 0 - Bit 29 - RTR"] + #[inline(always)] + pub fn rtr(&self) -> RTR_R { + RTR_R::new(((self.bits[0] >> 29) & 0x01) != 0) + } + #[doc = "Byte 0 - Bit 30 - XTD"] + #[inline(always)] + pub fn xtd(&self) -> XTD_R { + XTD_R::new(((self.bits[0] >> 30) & 0x01) != 0) + } + #[doc = "Byte 0 - Bit 30 - ESI"] + #[inline(always)] + pub fn esi(&self) -> ESI_R { + ESI_R::new(((self.bits[0] >> 31) & 0x01) != 0) + } + #[doc = "Byte 1 - Bits 0:15 - RXTS"] + #[inline(always)] + pub fn txts(&self) -> RXTS_R { + RXTS_R::new(((self.bits[1]) & 0xFFFF) as u16) + } + #[doc = "Byte 1 - Bits 16:19 - DLC"] + #[inline(always)] + pub fn dlc(&self) -> DLC_R { + DLC_R::new(((self.bits[1] >> 16) & 0x0F) as u8) + } + #[doc = "Byte 1 - Bits 20 - BRS"] + #[inline(always)] + pub fn brs(&self) -> BRS_R { + BRS_R::new(((self.bits[1] >> 20) & 0x01) != 0) + } + #[doc = "Byte 1 - Bits 20 - FDF"] + #[inline(always)] + pub fn fdf(&self) -> FDF_R { + FDF_R::new(((self.bits[1] >> 21) & 0x01) != 0) + } + #[doc = "Byte 1 - Bits 24:30 - FIDX"] + #[inline(always)] + pub fn fidx(&self) -> FIDX_R { + FIDX_R::new(((self.bits[1] >> 24) & 0xFF) as u8) + } + #[doc = "Byte 1 - Bits 31 - ANMF"] + #[inline(always)] + pub fn anmf(&self) -> ANMF_R { + ANMF_R::new(((self.bits[1] >> 31) & 0x01) != 0) + } + pub fn to_data_length(&self) -> DataLength { + let dlc = self.dlc().bits(); + let ff = self.fdf().frame_format(); + let len = if ff == FrameFormat::Fdcan { + // See RM0433 Rev 7 Table 475. DLC coding + match dlc { + 0..=8 => dlc, + 9 => 12, + 10 => 16, + 11 => 20, + 12 => 24, + 13 => 32, + 14 => 48, + 15 => 64, + _ => panic!("DLC > 15"), + } + } else { + match dlc { + 0..=8 => dlc, + 9..=15 => 8, + _ => panic!("DLC > 15"), + } + }; + DataLength::new(len, ff) + } + pub fn to_filter_match(&self) -> FilterFrameMatch { + if self.anmf().is_matching_frame() { + FilterFrameMatch::DidMatch(self.fidx().bits()) + } else { + FilterFrameMatch::DidNotMatch + } + } +} diff --git a/embassy-stm32/src/can/fd/message_ram/standard_filter.rs b/embassy-stm32/src/can/fd/message_ram/standard_filter.rs new file mode 100644 index 000000000..3a3bbcf12 --- /dev/null +++ b/embassy-stm32/src/can/fd/message_ram/standard_filter.rs @@ -0,0 +1,136 @@ +// Note: This file is copied and modified from fdcan crate by Richard Meadows + +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(unused)] + +use super::common::{ESFEC_R, ESFT_R}; +use super::enums::{FilterElementConfig, FilterType}; +use super::generic; + +#[doc = "Reader of register StandardFilter"] +pub(crate) type R = generic::R<super::StandardFilterType, super::StandardFilter>; +#[doc = "Writer for register StandardFilter"] +pub(crate) type W = generic::W<super::StandardFilterType, super::StandardFilter>; +#[doc = "Register StandardFilter `reset()`'s with value 0xC0000"] +impl generic::ResetValue for super::StandardFilter { + type Type = super::StandardFilterType; + #[inline(always)] + fn reset_value() -> Self::Type { + // Sets filter element to Disabled + 0xC000 + } +} + +#[doc = "Reader of field `SFID2`"] +pub(crate) type SFID2_R = generic::R<u16, u16>; +#[doc = "Write proxy for field `SFID2`"] +pub(crate) struct SFID2_W<'a> { + w: &'a mut W, +} +impl<'a> SFID2_W<'a> { + #[doc = r"Writes raw bits to the field"] + #[inline(always)] + pub unsafe fn bits(self, value: u16) -> &'a mut W { + self.w.bits = (self.w.bits & !(0x07ff)) | ((value as u32) & 0x07ff); + self.w + } +} + +#[doc = "Reader of field `SFID1`"] +pub(crate) type SFID1_R = generic::R<u16, u16>; +#[doc = "Write proxy for field `SFID1`"] +pub(crate) struct SFID1_W<'a> { + w: &'a mut W, +} +impl<'a> SFID1_W<'a> { + #[doc = r"Writes raw bits to the field"] + #[inline(always)] + pub unsafe fn bits(self, value: u16) -> &'a mut W { + self.w.bits = (self.w.bits & !(0x07ff << 16)) | (((value as u32) & 0x07ff) << 16); + self.w + } +} + +#[doc = "Write proxy for field `SFEC`"] +pub(crate) struct SFEC_W<'a> { + w: &'a mut W, +} +impl<'a> SFEC_W<'a> { + #[doc = r"Writes raw bits to the field"] + #[inline(always)] + pub unsafe fn bits(self, value: u8) -> &'a mut W { + self.w.bits = (self.w.bits & !(0x07 << 27)) | (((value as u32) & 0x07) << 27); + self.w + } + #[doc = r"Sets the field according to FilterElementConfig"] + #[inline(always)] + pub fn set_filter_element_config(self, fec: FilterElementConfig) -> &'a mut W { + //SAFETY: FilterElementConfig only be valid options + unsafe { self.bits(fec as u8) } + } +} + +#[doc = "Write proxy for field `SFT`"] +pub(crate) struct SFT_W<'a> { + w: &'a mut W, +} +impl<'a> SFT_W<'a> { + #[doc = r"Sets the field according the FilterType"] + #[inline(always)] + pub fn set_filter_type(self, filter: FilterType) -> &'a mut W { + //SAFETY: FilterType only be valid options + unsafe { self.bits(filter as u8) } + } + #[doc = r"Writes raw bits to the field"] + #[inline(always)] + pub unsafe fn bits(self, value: u8) -> &'a mut W { + self.w.bits = (self.w.bits & !(0x03 << 30)) | (((value as u32) & 0x03) << 30); + self.w + } +} + +impl R { + #[doc = "Bits 0:10 - SFID2"] + #[inline(always)] + pub fn sfid2(&self) -> SFID2_R { + SFID2_R::new((self.bits & 0x07ff) as u16) + } + #[doc = "Bits 16:26 - SFID1"] + #[inline(always)] + pub fn sfid1(&self) -> SFID1_R { + SFID1_R::new(((self.bits >> 16) & 0x07ff) as u16) + } + #[doc = "Bits 27:29 - SFEC"] + #[inline(always)] + pub fn sfec(&self) -> ESFEC_R { + ESFEC_R::new(((self.bits >> 27) & 0x07) as u8) + } + #[doc = "Bits 30:31 - SFT"] + #[inline(always)] + pub fn sft(&self) -> ESFT_R { + ESFT_R::new(((self.bits >> 30) & 0x03) as u8) + } +} +impl W { + #[doc = "Bits 0:10 - SFID2"] + #[inline(always)] + pub fn sfid2(&mut self) -> SFID2_W { + SFID2_W { w: self } + } + #[doc = "Bits 16:26 - SFID1"] + #[inline(always)] + pub fn sfid1(&mut self) -> SFID1_W { + SFID1_W { w: self } + } + #[doc = "Bits 27:29 - SFEC"] + #[inline(always)] + pub fn sfec(&mut self) -> SFEC_W { + SFEC_W { w: self } + } + #[doc = "Bits 30:31 - SFT"] + #[inline(always)] + pub fn sft(&mut self) -> SFT_W { + SFT_W { w: self } + } +} diff --git a/embassy-stm32/src/can/fd/message_ram/txbuffer_element.rs b/embassy-stm32/src/can/fd/message_ram/txbuffer_element.rs new file mode 100644 index 000000000..455406a1c --- /dev/null +++ b/embassy-stm32/src/can/fd/message_ram/txbuffer_element.rs @@ -0,0 +1,433 @@ +// Note: This file is copied and modified from fdcan crate by Richard Meadows + +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(unused)] + +use super::common::{BRS_R, DLC_R, ESI_R, FDF_R, ID_R, RTR_R, XTD_R}; +use super::enums::{ + BitRateSwitching, DataLength, ErrorStateIndicator, Event, EventControl, FrameFormat, IdType, + RemoteTransmissionRequest, +}; +use super::generic; + +#[doc = "Reader of register TxBufferElement"] +pub(crate) type R = generic::R<super::TxBufferElementHeaderType, super::TxBufferElementHeader>; +#[doc = "Writer for register TxBufferElement"] +pub(crate) type W = generic::W<super::TxBufferElementHeaderType, super::TxBufferElementHeader>; +impl generic::ResetValue for super::TxBufferElementHeader { + type Type = super::TxBufferElementHeaderType; + + #[allow(dead_code)] + #[inline(always)] + fn reset_value() -> Self::Type { + [0; 2] + } +} + +#[doc = "Write proxy for field `ESI`"] +pub(crate) struct ESI_W<'a> { + w: &'a mut W, +} +impl<'a> ESI_W<'a> { + #[doc = r"Writes `variant` to the field"] + #[inline(always)] + #[allow(dead_code)] + pub fn set_error_indicator(self, esi: ErrorStateIndicator) -> &'a mut W { + self.bit(esi as u8 != 0) + } + + #[doc = r"Sets the field bit"] + #[inline(always)] + #[allow(dead_code)] + pub fn set_bit(self) -> &'a mut W { + self.bit(true) + } + #[doc = r"Clears the field bit"] + #[inline(always)] + #[allow(dead_code)] + pub fn clear_bit(self) -> &'a mut W { + self.bit(false) + } + #[doc = r"Writes raw bits to the field"] + #[inline(always)] + #[allow(dead_code)] + pub fn bit(self, value: bool) -> &'a mut W { + self.w.bits[0] = (self.w.bits[0] & !(0x01 << 31)) | (((value as u32) & 0x01) << 31); + self.w + } +} + +#[doc = "Write proxy for field `XTD`"] +pub(crate) struct XTD_W<'a> { + w: &'a mut W, +} +impl<'a> XTD_W<'a> { + #[doc = r"Writes `variant` to the field"] + #[inline(always)] + #[allow(dead_code)] + pub fn set_id_type(self, idt: IdType) -> &'a mut W { + self.bit(idt as u8 != 0) + } + + #[doc = r"Sets the field bit"] + #[inline(always)] + #[allow(dead_code)] + pub fn set_bit(self) -> &'a mut W { + self.bit(true) + } + #[doc = r"Clears the field bit"] + #[inline(always)] + #[allow(dead_code)] + pub fn clear_bit(self) -> &'a mut W { + self.bit(false) + } + #[doc = r"Writes raw bits to the field"] + #[inline(always)] + #[allow(dead_code)] + pub fn bit(self, value: bool) -> &'a mut W { + self.w.bits[0] = (self.w.bits[0] & !(0x01 << 30)) | (((value as u32) & 0x01) << 30); + self.w + } +} + +#[doc = "Write proxy for field `RTR`"] +pub(crate) struct RTR_W<'a> { + w: &'a mut W, +} +impl<'a> RTR_W<'a> { + #[doc = r"Writes `variant` to the field"] + #[inline(always)] + #[allow(dead_code)] + pub fn set_rtr(self, rtr: RemoteTransmissionRequest) -> &'a mut W { + self.bit(rtr as u8 != 0) + } + + #[doc = r"Sets the field bit"] + #[inline(always)] + #[allow(dead_code)] + pub fn set_bit(self) -> &'a mut W { + self.bit(true) + } + #[doc = r"Clears the field bit"] + #[inline(always)] + #[allow(dead_code)] + pub fn clear_bit(self) -> &'a mut W { + self.bit(false) + } + #[doc = r"Writes raw bits to the field"] + #[inline(always)] + #[allow(dead_code)] + pub fn bit(self, value: bool) -> &'a mut W { + self.w.bits[0] = (self.w.bits[0] & !(0x01 << 29)) | (((value as u32) & 0x01) << 29); + self.w + } +} + +#[doc = "Write proxy for field `ID`"] +pub(crate) struct ID_W<'a> { + w: &'a mut W, +} +impl<'a> ID_W<'a> { + #[doc = r"Writes raw bits to the field"] + #[inline(always)] + #[allow(dead_code)] + pub unsafe fn bits(self, value: u32) -> &'a mut W { + self.w.bits[0] = (self.w.bits[0] & !(0x1FFFFFFF)) | ((value as u32) & 0x1FFFFFFF); + self.w + } +} + +#[doc = "Write proxy for field `DLC`"] +pub(crate) struct DLC_W<'a> { + w: &'a mut W, +} +impl<'a> DLC_W<'a> { + #[doc = r"Writes raw bits to the field"] + #[inline(always)] + #[allow(dead_code)] + pub unsafe fn bits(self, value: u8) -> &'a mut W { + self.w.bits[1] = (self.w.bits[1] & !(0x0F << 16)) | (((value as u32) & 0x0F) << 16); + self.w + } +} + +#[doc = "Write proxy for field `BRS`"] +pub(crate) struct BRS_W<'a> { + w: &'a mut W, +} +impl<'a> BRS_W<'a> { + #[doc = r"Writes `variant` to the field"] + #[inline(always)] + #[allow(dead_code)] + pub fn set_brs(self, brs: BitRateSwitching) -> &'a mut W { + self.bit(brs as u8 != 0) + } + + #[doc = r"Sets the field bit"] + #[inline(always)] + #[allow(dead_code)] + pub fn set_bit(self) -> &'a mut W { + self.bit(true) + } + #[doc = r"Clears the field bit"] + #[inline(always)] + #[allow(dead_code)] + pub fn clear_bit(self) -> &'a mut W { + self.bit(false) + } + #[doc = r"Writes raw bits to the field"] + #[inline(always)] + #[allow(dead_code)] + pub fn bit(self, value: bool) -> &'a mut W { + self.w.bits[1] = (self.w.bits[1] & !(0x01 << 20)) | (((value as u32) & 0x01) << 20); + self.w + } +} + +#[doc = "Write proxy for field `FDF`"] +pub(crate) struct FDF_W<'a> { + w: &'a mut W, +} +impl<'a> FDF_W<'a> { + #[doc = r"Writes `variant` to the field"] + #[inline(always)] + #[allow(dead_code)] + pub fn set_format(self, fdf: FrameFormat) -> &'a mut W { + self.bit(fdf as u8 != 0) + } + + #[doc = r"Sets the field bit"] + #[inline(always)] + #[allow(dead_code)] + pub fn set_bit(self) -> &'a mut W { + self.bit(true) + } + #[doc = r"Clears the field bit"] + #[inline(always)] + #[allow(dead_code)] + pub fn clear_bit(self) -> &'a mut W { + self.bit(false) + } + #[doc = r"Writes raw bits to the field"] + #[inline(always)] + #[allow(dead_code)] + pub fn bit(self, value: bool) -> &'a mut W { + self.w.bits[1] = (self.w.bits[1] & !(0x01 << 21)) | (((value as u32) & 0x01) << 21); + self.w + } +} + +#[doc = "Reader of field `EFC`"] +pub(crate) type EFC_R = generic::R<bool, EventControl>; +impl EFC_R { + pub fn to_event_control(&self) -> EventControl { + match self.bit() { + false => EventControl::DoNotStore, + true => EventControl::Store, + } + } +} +#[doc = "Write proxy for field `EFC`"] +pub(crate) struct EFC_W<'a> { + w: &'a mut W, +} +impl<'a> EFC_W<'a> { + #[doc = r"Writes `variant` to the field"] + #[inline(always)] + #[allow(dead_code)] + pub fn set_event_control(self, efc: EventControl) -> &'a mut W { + self.bit(match efc { + EventControl::DoNotStore => false, + EventControl::Store => true, + }) + } + + #[doc = r"Sets the field bit"] + #[inline(always)] + #[allow(dead_code)] + pub fn set_bit(self) -> &'a mut W { + self.bit(true) + } + #[doc = r"Clears the field bit"] + #[inline(always)] + #[allow(dead_code)] + pub fn clear_bit(self) -> &'a mut W { + self.bit(false) + } + #[doc = r"Writes raw bits to the field"] + #[inline(always)] + #[allow(dead_code)] + pub fn bit(self, value: bool) -> &'a mut W { + self.w.bits[1] = (self.w.bits[1] & !(0x01 << 23)) | (((value as u32) & 0x01) << 23); + self.w + } +} + +struct Marker(u8); +impl From<Event> for Marker { + fn from(e: Event) -> Marker { + match e { + Event::NoEvent => Marker(0), + Event::Event(mm) => Marker(mm), + } + } +} + +#[doc = "Reader of field `MM`"] +pub(crate) type MM_R = generic::R<u8, u8>; +#[doc = "Write proxy for field `MM`"] +pub(crate) struct MM_W<'a> { + w: &'a mut W, +} +impl<'a> MM_W<'a> { + #[doc = r"Writes raw bits to the field"] + #[inline(always)] + pub unsafe fn bits(self, value: u8) -> &'a mut W { + self.w.bits[1] = (self.w.bits[1] & !(0x7F << 24)) | (((value as u32) & 0x7F) << 24); + self.w + } + + fn set_message_marker(self, mm: Marker) -> &'a mut W { + unsafe { self.bits(mm.0) } + } +} + +impl R { + #[doc = "Byte 0 - Bits 0:28 - ID"] + #[inline(always)] + pub fn id(&self) -> ID_R { + ID_R::new(((self.bits[0]) & 0x1FFFFFFF) as u32) + } + #[doc = "Byte 0 - Bit 29 - RTR"] + #[inline(always)] + pub fn rtr(&self) -> RTR_R { + RTR_R::new(((self.bits[0] >> 29) & 0x01) != 0) + } + #[doc = "Byte 0 - Bit 30 - XTD"] + #[inline(always)] + pub fn xtd(&self) -> XTD_R { + XTD_R::new(((self.bits[0] >> 30) & 0x01) != 0) + } + #[doc = "Byte 0 - Bit 30 - ESI"] + #[inline(always)] + pub fn esi(&self) -> ESI_R { + ESI_R::new(((self.bits[0] >> 31) & 0x01) != 0) + } + #[doc = "Byte 1 - Bits 16:19 - DLC"] + #[inline(always)] + pub fn dlc(&self) -> DLC_R { + DLC_R::new(((self.bits[1] >> 16) & 0x0F) as u8) + } + #[doc = "Byte 1 - Bits 20 - BRS"] + #[inline(always)] + pub fn brs(&self) -> BRS_R { + BRS_R::new(((self.bits[1] >> 20) & 0x01) != 0) + } + #[doc = "Byte 1 - Bits 20 - FDF"] + #[inline(always)] + pub fn fdf(&self) -> FDF_R { + FDF_R::new(((self.bits[1] >> 21) & 0x01) != 0) + } + #[doc = "Byte 1 - Bits 23 - EFC"] + #[inline(always)] + pub fn efc(&self) -> EFC_R { + EFC_R::new(((self.bits[1] >> 23) & 0x01) != 0) + } + #[doc = "Byte 1 - Bits 24:31 - MM"] + #[inline(always)] + pub fn mm(&self) -> MM_R { + MM_R::new(((self.bits[1] >> 24) & 0xFF) as u8) + } + pub fn to_data_length(&self) -> DataLength { + let dlc = self.dlc().bits(); + let ff = self.fdf().frame_format(); + let len = if ff == FrameFormat::Fdcan { + // See RM0433 Rev 7 Table 475. DLC coding + match dlc { + 0..=8 => dlc, + 9 => 12, + 10 => 16, + 11 => 20, + 12 => 24, + 13 => 32, + 14 => 48, + 15 => 64, + _ => panic!("DLC > 15"), + } + } else { + match dlc { + 0..=8 => dlc, + 9..=15 => 8, + _ => panic!("DLC > 15"), + } + }; + DataLength::new(len, ff) + } + pub fn to_event(&self) -> Event { + let mm = self.mm().bits(); + let efc = self.efc().to_event_control(); + match efc { + EventControl::DoNotStore => Event::NoEvent, + EventControl::Store => Event::Event(mm), + } + } +} +impl W { + #[doc = "Byte 0 - Bits 0:28 - ID"] + #[inline(always)] + pub fn id(&mut self) -> ID_W { + ID_W { w: self } + } + #[doc = "Byte 0 - Bit 29 - RTR"] + #[inline(always)] + pub fn rtr(&mut self) -> RTR_W { + RTR_W { w: self } + } + #[doc = "Byte 0 - Bit 30 - XTD"] + #[inline(always)] + pub fn xtd(&mut self) -> XTD_W { + XTD_W { w: self } + } + #[doc = "Byte 0 - Bit 31 - ESI"] + #[inline(always)] + pub fn esi(&mut self) -> ESI_W { + ESI_W { w: self } + } + #[doc = "Byte 1 - Bit 16:19 - DLC"] + #[inline(always)] + pub fn dlc(&mut self) -> DLC_W { + DLC_W { w: self } + } + #[doc = "Byte 1 - Bit 20 - BRS"] + #[inline(always)] + pub fn brs(&mut self) -> BRS_W { + BRS_W { w: self } + } + #[doc = "Byte 1 - Bit 21 - FDF"] + #[inline(always)] + pub fn fdf(&mut self) -> FDF_W { + FDF_W { w: self } + } + #[doc = "Byte 1 - Bit 23 - EFC"] + #[inline(always)] + pub fn efc(&mut self) -> EFC_W { + EFC_W { w: self } + } + #[doc = "Byte 1 - Bit 24:31 - MM"] + #[inline(always)] + pub fn mm(&mut self) -> MM_W { + MM_W { w: self } + } + #[doc = "Convenience function for setting the data length and frame format"] + #[inline(always)] + pub fn set_len(&mut self, dl: impl Into<DataLength>) -> &mut Self { + let dl: DataLength = dl.into(); + self.fdf().set_format(dl.into()); + unsafe { self.dlc().bits(dl.dlc()) } + } + pub fn set_event(&mut self, event: Event) -> &mut Self { + self.mm().set_message_marker(event.into()); + self.efc().set_event_control(event.into()) + } +} diff --git a/embassy-stm32/src/can/fd/message_ram/txevent_element.rs b/embassy-stm32/src/can/fd/message_ram/txevent_element.rs new file mode 100644 index 000000000..817a4449f --- /dev/null +++ b/embassy-stm32/src/can/fd/message_ram/txevent_element.rs @@ -0,0 +1,138 @@ +// Note: This file is copied and modified from fdcan crate by Richard Meadows + +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(unused)] + +use super::common::{BRS_R, DLC_R, ESI_R, RTR_R, XTD_R}; +use super::generic; + +#[doc = "Reader of register TxEventElement"] +pub(crate) type R = generic::R<super::TxEventElementType, super::TxEventElement>; +// #[doc = "Writer for register TxEventElement"] +// pub(crate) type W = generic::W<super::TxEventElementType, super::TxEventElement>; +#[doc = "Register TxEventElement `reset()`'s"] +impl generic::ResetValue for super::TxEventElement { + type Type = super::TxEventElementType; + #[inline(always)] + fn reset_value() -> Self::Type { + [0, 0] + } +} + +#[doc = "Reader of field `ID`"] +pub(crate) type ID_R = generic::R<u32, u32>; + +#[doc = "Reader of field `TXTS`"] +pub(crate) type TXTS_R = generic::R<u16, u16>; + +#[derive(Clone, Copy, Debug, PartialEq)] +pub(crate) enum DataLengthFormat { + StandardLength = 0, + FDCANLength = 1, +} +impl From<DataLengthFormat> for bool { + #[inline(always)] + fn from(dlf: DataLengthFormat) -> Self { + dlf as u8 != 0 + } +} + +#[doc = "Reader of field `EDL`"] +pub(crate) type EDL_R = generic::R<bool, DataLengthFormat>; +impl EDL_R { + pub fn data_length_format(&self) -> DataLengthFormat { + match self.bits() { + false => DataLengthFormat::StandardLength, + true => DataLengthFormat::FDCANLength, + } + } + pub fn is_standard_length(&self) -> bool { + *self == DataLengthFormat::StandardLength + } + pub fn is_fdcan_length(&self) -> bool { + *self == DataLengthFormat::FDCANLength + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub(crate) enum EventType { + //_Reserved = 0b00, + TxEvent = 0b01, + TxDespiteAbort = 0b10, + //_Reserved = 0b10, +} + +#[doc = "Reader of field `EFC`"] +pub(crate) type EFC_R = generic::R<u8, EventType>; +impl EFC_R { + pub fn event_type(&self) -> EventType { + match self.bits() { + 0b01 => EventType::TxEvent, + 0b10 => EventType::TxDespiteAbort, + _ => unimplemented!(), + } + } + pub fn is_tx_event(&self) -> bool { + self.event_type() == EventType::TxEvent + } + pub fn is_despite_abort(&self) -> bool { + self.event_type() == EventType::TxDespiteAbort + } +} + +#[doc = "Reader of field `MM`"] +pub(crate) type MM_R = generic::R<u8, u8>; + +impl R { + #[doc = "Byte 0 - Bits 0:28 - ID"] + #[inline(always)] + pub fn id(&self) -> ID_R { + ID_R::new(((self.bits[0]) & 0x1FFFFFFF) as u32) + } + #[doc = "Byte 0 - Bit 29 - RTR"] + #[inline(always)] + pub fn rtr(&self) -> RTR_R { + RTR_R::new(((self.bits[0] >> 29) & 0x01) != 0) + } + #[doc = "Byte 0 - Bit 30 - XTD"] + #[inline(always)] + pub fn xtd(&self) -> XTD_R { + XTD_R::new(((self.bits[0] >> 30) & 0x01) != 0) + } + #[doc = "Byte 0 - Bit 30 - ESI"] + #[inline(always)] + pub fn esi(&self) -> ESI_R { + ESI_R::new(((self.bits[0] >> 31) & 0x01) != 0) + } + #[doc = "Byte 1 - Bits 0:15 - TXTS"] + #[inline(always)] + pub fn txts(&self) -> TXTS_R { + TXTS_R::new(((self.bits[1]) & 0xFFFF) as u16) + } + #[doc = "Byte 1 - Bits 16:19 - DLC"] + #[inline(always)] + pub fn dlc(&self) -> DLC_R { + DLC_R::new(((self.bits[1] >> 16) & 0x0F) as u8) + } + #[doc = "Byte 1 - Bits 20 - BRS"] + #[inline(always)] + pub fn brs(&self) -> BRS_R { + BRS_R::new(((self.bits[1] >> 20) & 0x01) != 0) + } + #[doc = "Byte 1 - Bits 21 - EDL"] + #[inline(always)] + pub fn edl(&self) -> EDL_R { + EDL_R::new(((self.bits[1] >> 21) & 0x01) != 0) + } + #[doc = "Byte 1 - Bits 22:23 - EFC"] + #[inline(always)] + pub fn efc(&self) -> EFC_R { + EFC_R::new(((self.bits[1] >> 22) & 0x03) as u8) + } + #[doc = "Byte 1 - Bits 24:31 - MM"] + #[inline(always)] + pub fn mm(&self) -> MM_R { + MM_R::new(((self.bits[1] >> 24) & 0xFF) as u8) + } +} diff --git a/embassy-stm32/src/can/fd/mod.rs b/embassy-stm32/src/can/fd/mod.rs new file mode 100644 index 000000000..271ca0b3c --- /dev/null +++ b/embassy-stm32/src/can/fd/mod.rs @@ -0,0 +1,6 @@ +//! Module containing that which is specific to fdcan hardware variant + +pub mod config; +pub mod filter; +pub(crate) mod message_ram; +pub(crate) mod peripheral; diff --git a/embassy-stm32/src/can/fd/peripheral.rs b/embassy-stm32/src/can/fd/peripheral.rs new file mode 100644 index 000000000..8ec09ac12 --- /dev/null +++ b/embassy-stm32/src/can/fd/peripheral.rs @@ -0,0 +1,788 @@ +// Note: This file is copied and modified from fdcan crate by Richard Meadows + +use core::convert::Infallible; +use core::slice; + +use cfg_if::cfg_if; + +use crate::can::enums::*; +use crate::can::fd::config::*; +use crate::can::fd::message_ram::enums::*; +use crate::can::fd::message_ram::{RegisterBlock, RxFifoElement, TxBufferElement}; +use crate::can::frame::*; + +/// Loopback Mode +#[derive(Clone, Copy, Debug)] +enum LoopbackMode { + None, + Internal, + External, +} + +pub struct Registers { + pub regs: &'static crate::pac::can::Fdcan, + pub msgram: &'static crate::pac::fdcanram::Fdcanram, + pub msg_ram_offset: usize, +} + +impl Registers { + fn tx_buffer_element(&self, bufidx: usize) -> &mut TxBufferElement { + &mut self.msg_ram_mut().transmit.tbsa[bufidx] + } + pub fn msg_ram_mut(&self) -> &mut RegisterBlock { + let ptr = self.msgram.as_ptr() as *mut RegisterBlock; + unsafe { &mut (*ptr) } + } + + fn rx_fifo_element(&self, fifonr: usize, bufnum: usize) -> &mut RxFifoElement { + &mut self.msg_ram_mut().receive[fifonr].fxsa[bufnum] + } + + pub fn read<F: CanHeader>(&self, fifonr: usize) -> Option<(F, u16)> { + // Fill level - do we have a msg? + if self.regs.rxfs(fifonr).read().ffl() < 1 { + return None; + } + + let read_idx = self.regs.rxfs(fifonr).read().fgi(); + let mailbox = self.rx_fifo_element(fifonr, read_idx as usize); + + let mut buffer = [0u8; 64]; + let maybe_header = extract_frame(mailbox, &mut buffer); + + // Clear FIFO, reduces count and increments read buf + self.regs.rxfa(fifonr).modify(|w| w.set_fai(read_idx)); + + match maybe_header { + Some((header, ts)) => { + let data = &buffer[0..header.len() as usize]; + Some((F::from_header(header, data)?, ts)) + } + None => None, + } + } + + pub fn put_tx_frame(&self, bufidx: usize, header: &Header, buffer: &[u8]) { + let mailbox = self.tx_buffer_element(bufidx); + + mailbox.reset(); + put_tx_header(mailbox, header); + put_tx_data(mailbox, &buffer[..header.len() as usize]); + + // Set <idx as Mailbox> as ready to transmit + self.regs.txbar().modify(|w| w.set_ar(bufidx, true)); + } + + fn reg_to_error(value: u8) -> Option<BusError> { + match value { + //0b000 => None, + 0b001 => Some(BusError::Stuff), + 0b010 => Some(BusError::Form), + 0b011 => Some(BusError::Acknowledge), + 0b100 => Some(BusError::BitRecessive), + 0b101 => Some(BusError::BitDominant), + 0b110 => Some(BusError::Crc), + //0b111 => Some(BusError::NoError), + _ => None, + } + } + + pub fn curr_error(&self) -> Option<BusError> { + let err = { self.regs.psr().read() }; + if err.bo() { + return Some(BusError::BusOff); + } else if err.ep() { + return Some(BusError::BusPassive); + } else if err.ew() { + return Some(BusError::BusWarning); + } else { + cfg_if! { + if #[cfg(stm32h7)] { + let lec = err.lec(); + } else { + let lec = err.lec().to_bits(); + } + } + if let Some(err) = Self::reg_to_error(lec) { + return Some(err); + } + } + None + } + /// Returns if the tx queue is able to accept new messages without having to cancel an existing one + #[inline] + pub fn tx_queue_is_full(&self) -> bool { + self.regs.txfqs().read().tfqf() + } + + /// Returns the current TX buffer operation mode (queue or FIFO) + #[inline] + pub fn tx_queue_mode(&self) -> TxBufferMode { + self.regs.txbc().read().tfqm().into() + } + + #[inline] + pub fn has_pending_frame(&self, idx: usize) -> bool { + self.regs.txbrp().read().trp(idx) + } + + /// Returns `Ok` when the mailbox is free or if it contains pending frame with a + /// lower priority (higher ID) than the identifier `id`. + #[inline] + pub fn is_available(&self, bufidx: usize, id: &embedded_can::Id) -> bool { + if self.has_pending_frame(bufidx) { + let mailbox = self.tx_buffer_element(bufidx); + + let header_reg = mailbox.header.read(); + let old_id = make_id(header_reg.id().bits(), header_reg.xtd().bits()); + + *id > old_id + } else { + true + } + } + + /// Attempts to abort the sending of a frame that is pending in a mailbox. + /// + /// If there is no frame in the provided mailbox, or its transmission succeeds before it can be + /// aborted, this function has no effect and returns `false`. + /// + /// If there is a frame in the provided mailbox, and it is canceled successfully, this function + /// returns `true`. + #[inline] + pub fn abort(&self, bufidx: usize) -> bool { + let can = self.regs; + + // Check if there is a request pending to abort + if self.has_pending_frame(bufidx) { + // Abort Request + can.txbcr().write(|w| w.set_cr(bufidx, true)); + + // Wait for the abort request to be finished. + loop { + if can.txbcf().read().cf(bufidx) { + // Return false when a transmission has occured + break can.txbto().read().to(bufidx) == false; + } + } + } else { + false + } + } + + #[inline] + fn abort_pending_mailbox<F: embedded_can::Frame>(&self, bufidx: usize) -> Option<F> { + if self.abort(bufidx) { + let mailbox = self.tx_buffer_element(bufidx); + + let header_reg = mailbox.header.read(); + let id = make_id(header_reg.id().bits(), header_reg.xtd().bits()); + + let len = match header_reg.to_data_length() { + DataLength::Fdcan(len) => len, + DataLength::Classic(len) => len, + }; + if len as usize > ClassicFrame::MAX_DATA_LEN { + return None; + } + + let mut data = [0u8; 64]; + data_from_tx_buffer(&mut data, mailbox, len as usize); + + if header_reg.rtr().bit() { + F::new_remote(id, len as usize) + } else { + F::new(id, &data) + } + } else { + // Abort request failed because the frame was already sent (or being sent) on + // the bus. All mailboxes are now free. This can happen for small prescaler + // values (e.g. 1MBit/s bit timing with a source clock of 8MHz) or when an ISR + // has preempted the execution. + None + } + } + + pub fn write<F: embedded_can::Frame + CanHeader>(&self, frame: &F) -> nb::Result<Option<F>, Infallible> { + let (idx, pending_frame) = if self.tx_queue_is_full() { + if self.tx_queue_mode() == TxBufferMode::Fifo { + // Does not make sense to cancel a pending frame when using FIFO + return Err(nb::Error::WouldBlock); + } + // If the queue is full, + // Discard the first slot with a lower priority message + let id = frame.header().id(); + if self.is_available(0, id) { + (0, self.abort_pending_mailbox(0)) + } else if self.is_available(1, id) { + (1, self.abort_pending_mailbox(1)) + } else if self.is_available(2, id) { + (2, self.abort_pending_mailbox(2)) + } else { + // For now we bail when there is no lower priority slot available + // Can this lead to priority inversion? + return Err(nb::Error::WouldBlock); + } + } else { + // Read the Write Pointer + let idx = self.regs.txfqs().read().tfqpi(); + + (idx, None) + }; + + self.put_tx_frame(idx as usize, frame.header(), frame.data()); + + Ok(pending_frame) + } + + #[inline] + fn reset_msg_ram(&mut self) { + self.msg_ram_mut().reset(); + } + + #[inline] + fn enter_init_mode(&mut self) { + self.regs.cccr().modify(|w| w.set_init(true)); + while false == self.regs.cccr().read().init() {} + self.regs.cccr().modify(|w| w.set_cce(true)); + } + + /// Enables or disables loopback mode: Internally connects the TX and RX + /// signals together. + #[inline] + fn set_loopback_mode(&mut self, mode: LoopbackMode) { + let (test, mon, lbck) = match mode { + LoopbackMode::None => (false, false, false), + LoopbackMode::Internal => (true, true, true), + LoopbackMode::External => (true, false, true), + }; + + self.set_test_mode(test); + self.set_bus_monitoring_mode(mon); + + self.regs.test().modify(|w| w.set_lbck(lbck)); + } + + /// Enables or disables silent mode: Disconnects the TX signal from the pin. + #[inline] + fn set_bus_monitoring_mode(&mut self, enabled: bool) { + self.regs.cccr().modify(|w| w.set_mon(enabled)); + } + + #[inline] + fn set_restricted_operations(&mut self, enabled: bool) { + self.regs.cccr().modify(|w| w.set_asm(enabled)); + } + + #[inline] + fn set_normal_operations(&mut self, _enabled: bool) { + self.set_loopback_mode(LoopbackMode::None); + } + + #[inline] + fn set_test_mode(&mut self, enabled: bool) { + self.regs.cccr().modify(|w| w.set_test(enabled)); + } + + #[inline] + fn set_power_down_mode(&mut self, enabled: bool) { + self.regs.cccr().modify(|w| w.set_csr(enabled)); + while self.regs.cccr().read().csa() != enabled {} + } + + /// Moves out of PoweredDownMode and into ConfigMode + #[inline] + pub fn into_config_mode(mut self, _config: FdCanConfig) { + self.set_power_down_mode(false); + self.enter_init_mode(); + self.reset_msg_ram(); + + // check the FDCAN core matches our expections + assert!( + self.regs.crel().read().rel() == 3, + "Expected FDCAN core major release 3" + ); + assert!( + self.regs.endn().read().etv() == 0x87654321_u32, + "Error reading endianness test value from FDCAN core" + ); + + /* + for fid in 0..crate::can::message_ram::STANDARD_FILTER_MAX { + self.set_standard_filter((fid as u8).into(), StandardFilter::disable()); + } + for fid in 0..Ecrate::can::message_ram::XTENDED_FILTER_MAX { + self.set_extended_filter(fid.into(), ExtendedFilter::disable()); + } + */ + } + + /// Disables the CAN interface and returns back the raw peripheral it was created from. + #[inline] + pub fn free(mut self) { + //self.disable_interrupts(Interrupts::all()); + + //TODO check this! + self.enter_init_mode(); + self.set_power_down_mode(true); + //self.control.instance + } + + /// Applies the settings of a new FdCanConfig See [`FdCanConfig`] + #[inline] + pub fn apply_config(&mut self, config: FdCanConfig) { + self.set_tx_buffer_mode(config.tx_buffer_mode); + + // set standard filters list size to 28 + // set extended filters list size to 8 + // REQUIRED: we use the memory map as if these settings are set + // instead of re-calculating them. + #[cfg(not(stm32h7))] + { + self.regs.rxgfc().modify(|w| { + w.set_lss(crate::can::fd::message_ram::STANDARD_FILTER_MAX); + w.set_lse(crate::can::fd::message_ram::EXTENDED_FILTER_MAX); + }); + } + #[cfg(stm32h7)] + { + self.regs + .sidfc() + .modify(|w| w.set_lss(crate::can::fd::message_ram::STANDARD_FILTER_MAX)); + self.regs + .xidfc() + .modify(|w| w.set_lse(crate::can::fd::message_ram::EXTENDED_FILTER_MAX)); + } + + self.configure_msg_ram(); + + // Enable timestamping + #[cfg(not(stm32h7))] + self.regs + .tscc() + .write(|w| w.set_tss(stm32_metapac::can::vals::Tss::INCREMENT)); + #[cfg(stm32h7)] + self.regs.tscc().write(|w| w.set_tss(0x01)); + + // this isn't really documented in the reference manual + // but corresponding txbtie bit has to be set for the TC (TxComplete) interrupt to fire + self.regs.txbtie().write(|w| w.0 = 0xffff_ffff); + self.regs.ie().modify(|w| { + w.set_rfne(0, true); // Rx Fifo 0 New Msg + w.set_rfne(1, true); // Rx Fifo 1 New Msg + w.set_tce(true); // Tx Complete + }); + self.regs.ile().modify(|w| { + w.set_eint0(true); // Interrupt Line 0 + w.set_eint1(true); // Interrupt Line 1 + }); + + self.set_data_bit_timing(config.dbtr); + self.set_nominal_bit_timing(config.nbtr); + self.set_automatic_retransmit(config.automatic_retransmit); + self.set_transmit_pause(config.transmit_pause); + self.set_frame_transmit(config.frame_transmit); + //self.set_interrupt_line_config(config.interrupt_line_config); + self.set_non_iso_mode(config.non_iso_mode); + self.set_edge_filtering(config.edge_filtering); + self.set_protocol_exception_handling(config.protocol_exception_handling); + self.set_global_filter(config.global_filter); + } + + #[inline] + fn leave_init_mode(&mut self, config: FdCanConfig) { + self.apply_config(config); + + self.regs.cccr().modify(|w| w.set_cce(false)); + self.regs.cccr().modify(|w| w.set_init(false)); + while self.regs.cccr().read().init() == true {} + } + + /// Moves out of ConfigMode and into specified mode + #[inline] + pub fn into_mode(mut self, config: FdCanConfig, mode: crate::can::_version::FdcanOperatingMode) { + match mode { + crate::can::FdcanOperatingMode::InternalLoopbackMode => self.set_loopback_mode(LoopbackMode::Internal), + crate::can::FdcanOperatingMode::ExternalLoopbackMode => self.set_loopback_mode(LoopbackMode::External), + crate::can::FdcanOperatingMode::NormalOperationMode => self.set_normal_operations(true), + crate::can::FdcanOperatingMode::RestrictedOperationMode => self.set_restricted_operations(true), + crate::can::FdcanOperatingMode::BusMonitoringMode => self.set_bus_monitoring_mode(true), + } + self.leave_init_mode(config); + } + + /// Moves out of ConfigMode and into InternalLoopbackMode + #[inline] + pub fn into_internal_loopback(mut self, config: FdCanConfig) { + self.set_loopback_mode(LoopbackMode::Internal); + self.leave_init_mode(config); + } + + /// Moves out of ConfigMode and into ExternalLoopbackMode + #[inline] + pub fn into_external_loopback(mut self, config: FdCanConfig) { + self.set_loopback_mode(LoopbackMode::External); + self.leave_init_mode(config); + } + + /// Moves out of ConfigMode and into RestrictedOperationMode + #[inline] + pub fn into_restricted(mut self, config: FdCanConfig) { + self.set_restricted_operations(true); + self.leave_init_mode(config); + } + + /// Moves out of ConfigMode and into NormalOperationMode + #[inline] + pub fn into_normal(mut self, config: FdCanConfig) { + self.set_normal_operations(true); + self.leave_init_mode(config); + } + + /// Moves out of ConfigMode and into BusMonitoringMode + #[inline] + pub fn into_bus_monitoring(mut self, config: FdCanConfig) { + self.set_bus_monitoring_mode(true); + self.leave_init_mode(config); + } + + /// Moves out of ConfigMode and into Testmode + #[inline] + pub fn into_test_mode(mut self, config: FdCanConfig) { + self.set_test_mode(true); + self.leave_init_mode(config); + } + + /// Moves out of ConfigMode and into PoweredDownmode + #[inline] + pub fn into_powered_down(mut self, config: FdCanConfig) { + self.set_power_down_mode(true); + self.leave_init_mode(config); + } + + /// Configures the bit timings. + /// + /// You can use <http://www.bittiming.can-wiki.info/> to calculate the `btr` parameter. Enter + /// parameters as follows: + /// + /// - *Clock Rate*: The input clock speed to the CAN peripheral (*not* the CPU clock speed). + /// This is the clock rate of the peripheral bus the CAN peripheral is attached to (eg. APB1). + /// - *Sample Point*: Should normally be left at the default value of 87.5%. + /// - *SJW*: Should normally be left at the default value of 1. + /// + /// Then copy the `CAN_BUS_TIME` register value from the table and pass it as the `btr` + /// parameter to this method. + #[inline] + pub fn set_nominal_bit_timing(&mut self, btr: NominalBitTiming) { + self.regs.nbtp().write(|w| { + w.set_nbrp(btr.nbrp() - 1); + w.set_ntseg1(btr.ntseg1() - 1); + w.set_ntseg2(btr.ntseg2() - 1); + w.set_nsjw(btr.nsjw() - 1); + }); + } + + /// Configures the data bit timings for the FdCan Variable Bitrates. + /// This is not used when frame_transmit is set to anything other than AllowFdCanAndBRS. + #[inline] + pub fn set_data_bit_timing(&mut self, btr: DataBitTiming) { + self.regs.dbtp().write(|w| { + w.set_dbrp(btr.dbrp() - 1); + w.set_dtseg1(btr.dtseg1() - 1); + w.set_dtseg2(btr.dtseg2() - 1); + w.set_dsjw(btr.dsjw() - 1); + }); + } + + /// Enables or disables automatic retransmission of messages + /// + /// If this is enabled, the CAN peripheral will automatically try to retransmit each frame + /// util it can be sent. Otherwise, it will try only once to send each frame. + /// + /// Automatic retransmission is enabled by default. + #[inline] + pub fn set_automatic_retransmit(&mut self, enabled: bool) { + self.regs.cccr().modify(|w| w.set_dar(!enabled)); + } + + /// Configures the transmit pause feature. See + /// [`FdCanConfig::set_transmit_pause`] + #[inline] + pub fn set_transmit_pause(&mut self, enabled: bool) { + self.regs.cccr().modify(|w| w.set_txp(!enabled)); + } + + /// Configures non-iso mode. See [`FdCanConfig::set_non_iso_mode`] + #[inline] + pub fn set_non_iso_mode(&mut self, enabled: bool) { + self.regs.cccr().modify(|w| w.set_niso(enabled)); + } + + /// Configures edge filtering. See [`FdCanConfig::set_edge_filtering`] + #[inline] + pub fn set_edge_filtering(&mut self, enabled: bool) { + self.regs.cccr().modify(|w| w.set_efbi(enabled)); + } + + /// Configures TX Buffer Mode + #[inline] + pub fn set_tx_buffer_mode(&mut self, tbm: TxBufferMode) { + self.regs.txbc().write(|w| w.set_tfqm(tbm.into())); + } + + /// Configures frame transmission mode. See + /// [`FdCanConfig::set_frame_transmit`] + #[inline] + pub fn set_frame_transmit(&mut self, fts: FrameTransmissionConfig) { + let (fdoe, brse) = match fts { + FrameTransmissionConfig::ClassicCanOnly => (false, false), + FrameTransmissionConfig::AllowFdCan => (true, false), + FrameTransmissionConfig::AllowFdCanAndBRS => (true, true), + }; + + self.regs.cccr().modify(|w| { + w.set_fdoe(fdoe); + #[cfg(stm32h7)] + w.set_bse(brse); + #[cfg(not(stm32h7))] + w.set_brse(brse); + }); + } + + /// Sets the protocol exception handling on/off + #[inline] + pub fn set_protocol_exception_handling(&mut self, enabled: bool) { + self.regs.cccr().modify(|w| w.set_pxhd(!enabled)); + } + + /// Configures and resets the timestamp counter + #[inline] + pub fn set_timestamp_counter_source(&mut self, select: TimestampSource) { + #[cfg(stm32h7)] + let (tcp, tss) = match select { + TimestampSource::None => (0, 0), + TimestampSource::Prescaler(p) => (p as u8, 1), + TimestampSource::FromTIM3 => (0, 2), + }; + + #[cfg(not(stm32h7))] + let (tcp, tss) = match select { + TimestampSource::None => (0, stm32_metapac::can::vals::Tss::ZERO), + TimestampSource::Prescaler(p) => (p as u8, stm32_metapac::can::vals::Tss::INCREMENT), + TimestampSource::FromTIM3 => (0, stm32_metapac::can::vals::Tss::EXTERNAL), + }; + + self.regs.tscc().write(|w| { + w.set_tcp(tcp); + w.set_tss(tss); + }); + } + + #[cfg(not(stm32h7))] + /// Configures the global filter settings + #[inline] + pub fn set_global_filter(&mut self, filter: GlobalFilter) { + let anfs = match filter.handle_standard_frames { + crate::can::fd::config::NonMatchingFilter::IntoRxFifo0 => stm32_metapac::can::vals::Anfs::ACCEPT_FIFO_0, + crate::can::fd::config::NonMatchingFilter::IntoRxFifo1 => stm32_metapac::can::vals::Anfs::ACCEPT_FIFO_1, + crate::can::fd::config::NonMatchingFilter::Reject => stm32_metapac::can::vals::Anfs::REJECT, + }; + let anfe = match filter.handle_extended_frames { + crate::can::fd::config::NonMatchingFilter::IntoRxFifo0 => stm32_metapac::can::vals::Anfe::ACCEPT_FIFO_0, + crate::can::fd::config::NonMatchingFilter::IntoRxFifo1 => stm32_metapac::can::vals::Anfe::ACCEPT_FIFO_1, + crate::can::fd::config::NonMatchingFilter::Reject => stm32_metapac::can::vals::Anfe::REJECT, + }; + + self.regs.rxgfc().modify(|w| { + w.set_anfs(anfs); + w.set_anfe(anfe); + w.set_rrfs(filter.reject_remote_standard_frames); + w.set_rrfe(filter.reject_remote_extended_frames); + }); + } + + #[cfg(stm32h7)] + /// Configures the global filter settings + #[inline] + pub fn set_global_filter(&mut self, filter: GlobalFilter) { + let anfs = match filter.handle_standard_frames { + crate::can::fd::config::NonMatchingFilter::IntoRxFifo0 => 0, + crate::can::fd::config::NonMatchingFilter::IntoRxFifo1 => 1, + crate::can::fd::config::NonMatchingFilter::Reject => 2, + }; + + let anfe = match filter.handle_extended_frames { + crate::can::fd::config::NonMatchingFilter::IntoRxFifo0 => 0, + crate::can::fd::config::NonMatchingFilter::IntoRxFifo1 => 1, + crate::can::fd::config::NonMatchingFilter::Reject => 2, + }; + + self.regs.gfc().modify(|w| { + w.set_anfs(anfs); + w.set_anfe(anfe); + w.set_rrfs(filter.reject_remote_standard_frames); + w.set_rrfe(filter.reject_remote_extended_frames); + }); + } + + #[cfg(not(stm32h7))] + fn configure_msg_ram(&mut self) {} + + #[cfg(stm32h7)] + fn configure_msg_ram(&mut self) { + let r = self.regs; + + use crate::can::fd::message_ram::*; + //use fdcan::message_ram::*; + let mut offset_words = self.msg_ram_offset as u16; + + // 11-bit filter + r.sidfc().modify(|w| w.set_flssa(offset_words)); + offset_words += STANDARD_FILTER_MAX as u16; + + // 29-bit filter + r.xidfc().modify(|w| w.set_flesa(offset_words)); + offset_words += 2 * EXTENDED_FILTER_MAX as u16; + + // Rx FIFO 0 and 1 + for i in 0..=1 { + r.rxfc(i).modify(|w| { + w.set_fsa(offset_words); + w.set_fs(RX_FIFO_MAX); + w.set_fwm(RX_FIFO_MAX); + }); + offset_words += 18 * RX_FIFO_MAX as u16; + } + + // Rx buffer - see below + // Tx event FIFO + r.txefc().modify(|w| { + w.set_efsa(offset_words); + w.set_efs(TX_EVENT_MAX); + w.set_efwm(TX_EVENT_MAX); + }); + offset_words += 2 * TX_EVENT_MAX as u16; + + // Tx buffers + r.txbc().modify(|w| { + w.set_tbsa(offset_words); + w.set_tfqs(TX_FIFO_MAX); + }); + offset_words += 18 * TX_FIFO_MAX as u16; + + // Rx Buffer - not used + r.rxbc().modify(|w| { + w.set_rbsa(offset_words); + }); + + // TX event FIFO? + // Trigger memory? + + // Set the element sizes to 16 bytes + r.rxesc().modify(|w| { + w.set_rbds(0b111); + for i in 0..=1 { + w.set_fds(i, 0b111); + } + }); + r.txesc().modify(|w| { + w.set_tbds(0b111); + }) + } +} + +fn make_id(id: u32, extended: bool) -> embedded_can::Id { + if extended { + embedded_can::Id::from(unsafe { embedded_can::ExtendedId::new_unchecked(id & 0x1FFFFFFF) }) + } else { + // A standard identifier is stored into ID[28:18]. + embedded_can::Id::from(unsafe { embedded_can::StandardId::new_unchecked(((id >> 18) & 0x000007FF) as u16) }) + } +} + +fn put_tx_header(mailbox: &mut TxBufferElement, header: &Header) { + let (id, id_type) = match header.id() { + // A standard identifier has to be written to ID[28:18]. + embedded_can::Id::Standard(id) => ((id.as_raw() as u32) << 18, IdType::StandardId), + embedded_can::Id::Extended(id) => (id.as_raw() as u32, IdType::ExtendedId), + }; + + // Use FDCAN only for DLC > 8. FDCAN users can revise this if required. + let frame_format = if header.len() > 8 || header.fdcan() { + FrameFormat::Fdcan + } else { + FrameFormat::Classic + }; + let brs = (frame_format == FrameFormat::Fdcan) && header.bit_rate_switching(); + + mailbox.header.write(|w| { + unsafe { w.id().bits(id) } + .rtr() + .bit(header.len() == 0 && header.rtr()) + .xtd() + .set_id_type(id_type) + .set_len(DataLength::new(header.len(), frame_format)) + .set_event(Event::NoEvent) + .fdf() + .set_format(frame_format) + .brs() + .bit(brs) + //esi.set_error_indicator(//TODO//) + }); +} + +fn put_tx_data(mailbox: &mut TxBufferElement, buffer: &[u8]) { + let mut lbuffer = [0_u32; 16]; + let len = buffer.len(); + let data = unsafe { slice::from_raw_parts_mut(lbuffer.as_mut_ptr() as *mut u8, len) }; + data[..len].copy_from_slice(&buffer[..len]); + let data_len = ((len) + 3) / 4; + for (register, byte) in mailbox.data.iter_mut().zip(lbuffer[..data_len].iter()) { + unsafe { register.write(*byte) }; + } +} + +fn data_from_fifo(buffer: &mut [u8], mailbox: &RxFifoElement, len: usize) { + for (i, register) in mailbox.data.iter().enumerate() { + let register_value = register.read(); + let register_bytes = unsafe { slice::from_raw_parts(®ister_value as *const u32 as *const u8, 4) }; + let num_bytes = (len) - i * 4; + if num_bytes <= 4 { + buffer[i * 4..i * 4 + num_bytes].copy_from_slice(®ister_bytes[..num_bytes]); + break; + } + buffer[i * 4..(i + 1) * 4].copy_from_slice(register_bytes); + } +} + +fn data_from_tx_buffer(buffer: &mut [u8], mailbox: &TxBufferElement, len: usize) { + for (i, register) in mailbox.data.iter().enumerate() { + let register_value = register.read(); + let register_bytes = unsafe { slice::from_raw_parts(®ister_value as *const u32 as *const u8, 4) }; + let num_bytes = (len) - i * 4; + if num_bytes <= 4 { + buffer[i * 4..i * 4 + num_bytes].copy_from_slice(®ister_bytes[..num_bytes]); + break; + } + buffer[i * 4..(i + 1) * 4].copy_from_slice(register_bytes); + } +} + +fn extract_frame(mailbox: &RxFifoElement, buffer: &mut [u8]) -> Option<(Header, u16)> { + let header_reg = mailbox.header.read(); + + let id = make_id(header_reg.id().bits(), header_reg.xtd().bits()); + let dlc = header_reg.to_data_length().len(); + let len = dlc as usize; + let timestamp = header_reg.txts().bits; + if len > buffer.len() { + return None; + } + data_from_fifo(buffer, mailbox, len); + let header = if header_reg.fdf().bits { + Header::new_fd(id, dlc, header_reg.rtr().bits(), header_reg.brs().bits()) + } else { + Header::new(id, dlc, header_reg.rtr().bits()) + }; + Some((header, timestamp)) +} diff --git a/embassy-stm32/src/can/fdcan.rs b/embassy-stm32/src/can/fdcan.rs index 0cc2559cf..fe8969a5a 100644 --- a/embassy-stm32/src/can/fdcan.rs +++ b/embassy-stm32/src/can/fdcan.rs @@ -1,54 +1,951 @@ -use crate::peripherals; +#[allow(unused_variables)] +use core::future::poll_fn; +use core::marker::PhantomData; +use core::task::Poll; + +use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::channel::Channel; + +use crate::can::fd::peripheral::Registers; +use crate::gpio::sealed::AFType; +use crate::interrupt::typelevel::Interrupt; +use crate::rcc::RccPeripheral; +use crate::{interrupt, peripherals, Peripheral}; + +pub mod enums; +pub(crate) mod fd; +pub mod frame; +mod util; + +use enums::*; +use fd::config::*; +use fd::filter::*; +pub use fd::{config, filter}; +use frame::*; + +/// Timestamp for incoming packets. Use Embassy time when enabled. +#[cfg(feature = "time")] +pub type Timestamp = embassy_time::Instant; + +/// Timestamp for incoming packets. +#[cfg(not(feature = "time"))] +pub type Timestamp = u16; + +/// Interrupt handler channel 0. +pub struct IT0InterruptHandler<T: Instance> { + _phantom: PhantomData<T>, +} + +// We use IT0 for everything currently +impl<T: Instance> interrupt::typelevel::Handler<T::IT0Interrupt> for IT0InterruptHandler<T> { + unsafe fn on_interrupt() { + let regs = T::regs(); + + let ir = regs.ir().read(); + + { + if ir.tc() { + regs.ir().write(|w| w.set_tc(true)); + } + if ir.tefn() { + regs.ir().write(|w| w.set_tefn(true)); + } + + match &T::state().tx_mode { + sealed::TxMode::NonBuffered(waker) => waker.wake(), + sealed::TxMode::ClassicBuffered(buf) => { + if !T::registers().tx_queue_is_full() { + match buf.tx_receiver.try_receive() { + Ok(frame) => { + _ = T::registers().write(&frame); + } + Err(_) => {} + } + } + } + sealed::TxMode::FdBuffered(buf) => { + if !T::registers().tx_queue_is_full() { + match buf.tx_receiver.try_receive() { + Ok(frame) => { + _ = T::registers().write(&frame); + } + Err(_) => {} + } + } + } + } + } + + if ir.ped() || ir.pea() { + regs.ir().write(|w| { + w.set_ped(true); + w.set_pea(true); + }); + } + + if ir.rfn(0) { + T::state().rx_mode.on_interrupt::<T>(0); + } + + if ir.rfn(1) { + T::state().rx_mode.on_interrupt::<T>(1); + } + } +} + +/// Interrupt handler channel 1. +pub struct IT1InterruptHandler<T: Instance> { + _phantom: PhantomData<T>, +} + +impl<T: Instance> interrupt::typelevel::Handler<T::IT1Interrupt> for IT1InterruptHandler<T> { + unsafe fn on_interrupt() {} +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Different operating modes +pub enum FdcanOperatingMode { + //PoweredDownMode, + //ConfigMode, + /// This mode can be used for a “Hot Selftest”, meaning the FDCAN can be tested without + /// affecting a running CAN system connected to the FDCAN_TX and FDCAN_RX pins. In this + /// mode, FDCAN_RX pin is disconnected from the FDCAN and FDCAN_TX pin is held + /// recessive. + InternalLoopbackMode, + /// This mode is provided for hardware self-test. To be independent from external stimulation, + /// the FDCAN ignores acknowledge errors (recessive bit sampled in the acknowledge slot of a + /// data / remote frame) in Loop Back mode. In this mode the FDCAN performs an internal + /// feedback from its transmit output to its receive input. The actual value of the FDCAN_RX + /// input pin is disregarded by the FDCAN. The transmitted messages can be monitored at the + /// FDCAN_TX transmit pin. + ExternalLoopbackMode, + /// The normal use of the Fdcan instance after configurations + NormalOperationMode, + /// In Restricted operation mode the node is able to receive data and remote frames and to give + /// acknowledge to valid frames, but it does not send data frames, remote frames, active error + /// frames, or overload frames. In case of an error condition or overload condition, it does not + /// send dominant bits, instead it waits for the occurrence of bus idle condition to resynchronize + /// itself to the CAN communication. The error counters for transmit and receive are frozen while + /// error logging (can_errors) is active. TODO: automatically enter in this mode? + RestrictedOperationMode, + /// In Bus monitoring mode (for more details refer to ISO11898-1, 10.12 Bus monitoring), + /// the FDCAN is able to receive valid data frames and valid remote frames, but cannot start a + /// transmission. In this mode, it sends only recessive bits on the CAN bus. If the FDCAN is + /// required to send a dominant bit (ACK bit, overload flag, active error flag), the bit is + /// rerouted internally so that the FDCAN can monitor it, even if the CAN bus remains in recessive + /// state. In Bus monitoring mode the TXBRP register is held in reset state. The Bus monitoring + /// mode can be used to analyze the traffic on a CAN bus without affecting it by the transmission + /// of dominant bits. + BusMonitoringMode, + //TestMode, +} + +/// FDCAN Configuration instance instance +/// Create instance of this first +pub struct FdcanConfigurator<'d, T: Instance> { + config: crate::can::fd::config::FdCanConfig, + /// Reference to internals. + instance: FdcanInstance<'d, T>, +} + +fn calc_ns_per_timer_tick<T: Instance>(mode: crate::can::fd::config::FrameTransmissionConfig) -> u64 { + match mode { + // Use timestamp from Rx FIFO to adjust timestamp reported to user + crate::can::fd::config::FrameTransmissionConfig::ClassicCanOnly => { + let freq = T::frequency(); + let prescale: u64 = + ({ T::regs().nbtp().read().nbrp() } + 1) as u64 * ({ T::regs().tscc().read().tcp() } + 1) as u64; + 1_000_000_000 as u64 / (freq.0 as u64 * prescale) + } + // For VBR this is too hard because the FDCAN timer switches clock rate you need to configure to use + // timer3 instead which is too hard to do from this module. + _ => 0, + } +} + +impl<'d, T: Instance> FdcanConfigurator<'d, T> { + /// Creates a new Fdcan instance, keeping the peripheral in sleep mode. + /// You must call [Fdcan::enable_non_blocking] to use the peripheral. + pub fn new( + peri: impl Peripheral<P = T> + 'd, + rx: impl Peripheral<P = impl RxPin<T>> + 'd, + tx: impl Peripheral<P = impl TxPin<T>> + 'd, + _irqs: impl interrupt::typelevel::Binding<T::IT0Interrupt, IT0InterruptHandler<T>> + + interrupt::typelevel::Binding<T::IT1Interrupt, IT1InterruptHandler<T>> + + 'd, + ) -> FdcanConfigurator<'d, T> { + into_ref!(peri, rx, tx); + + rx.set_as_af(rx.af_num(), AFType::Input); + tx.set_as_af(tx.af_num(), AFType::OutputPushPull); + + T::enable_and_reset(); + + let mut config = crate::can::fd::config::FdCanConfig::default(); + config.timestamp_source = TimestampSource::Prescaler(TimestampPrescaler::_1); + T::registers().into_config_mode(config); + + rx.set_as_af(rx.af_num(), AFType::Input); + tx.set_as_af(tx.af_num(), AFType::OutputPushPull); + + unsafe { + T::IT0Interrupt::unpend(); // Not unsafe + T::IT0Interrupt::enable(); + + T::IT1Interrupt::unpend(); // Not unsafe + T::IT1Interrupt::enable(); + } + + Self { + config, + instance: FdcanInstance(peri), + } + } + + /// Get configuration + pub fn config(&self) -> crate::can::fd::config::FdCanConfig { + return self.config; + } + + /// Set configuration + pub fn set_config(&mut self, config: crate::can::fd::config::FdCanConfig) { + self.config = config; + } + + /// Configures the bit timings calculated from supplied bitrate. + pub fn set_bitrate(&mut self, bitrate: u32) { + let bit_timing = util::calc_can_timings(T::frequency(), bitrate).unwrap(); + + let nbtr = crate::can::fd::config::NominalBitTiming { + sync_jump_width: bit_timing.sync_jump_width, + prescaler: bit_timing.prescaler, + seg1: bit_timing.seg1, + seg2: bit_timing.seg2, + }; + self.config = self.config.set_nominal_bit_timing(nbtr); + } + + /// Configures the bit timings for VBR data calculated from supplied bitrate. This also sets confit to allow can FD and VBR + pub fn set_fd_data_bitrate(&mut self, bitrate: u32, transceiver_delay_compensation: bool) { + let bit_timing = util::calc_can_timings(T::frequency(), bitrate).unwrap(); + // Note, used existing calcluation for normal(non-VBR) bitrate, appears to work for 250k/1M + let nbtr = crate::can::fd::config::DataBitTiming { + transceiver_delay_compensation, + sync_jump_width: bit_timing.sync_jump_width, + prescaler: bit_timing.prescaler, + seg1: bit_timing.seg1, + seg2: bit_timing.seg2, + }; + self.config.frame_transmit = FrameTransmissionConfig::AllowFdCanAndBRS; + self.config = self.config.set_data_bit_timing(nbtr); + } + + /// Set an Standard Address CAN filter into slot 'id' + #[inline] + pub fn set_standard_filter(&mut self, slot: StandardFilterSlot, filter: StandardFilter) { + T::registers().msg_ram_mut().filters.flssa[slot as usize].activate(filter); + } + + /// Set an array of Standard Address CAN filters and overwrite the current set + pub fn set_standard_filters(&mut self, filters: &[StandardFilter; STANDARD_FILTER_MAX as usize]) { + for (i, f) in filters.iter().enumerate() { + T::registers().msg_ram_mut().filters.flssa[i].activate(*f); + } + } + + /// Set an Extended Address CAN filter into slot 'id' + #[inline] + pub fn set_extended_filter(&mut self, slot: ExtendedFilterSlot, filter: ExtendedFilter) { + T::registers().msg_ram_mut().filters.flesa[slot as usize].activate(filter); + } + + /// Set an array of Extended Address CAN filters and overwrite the current set + pub fn set_extended_filters(&mut self, filters: &[ExtendedFilter; EXTENDED_FILTER_MAX as usize]) { + for (i, f) in filters.iter().enumerate() { + T::registers().msg_ram_mut().filters.flesa[i].activate(*f); + } + } + + /// Start in mode. + pub fn start(self, mode: FdcanOperatingMode) -> Fdcan<'d, T> { + let ns_per_timer_tick = calc_ns_per_timer_tick::<T>(self.config.frame_transmit); + critical_section::with(|_| unsafe { + T::mut_state().ns_per_timer_tick = ns_per_timer_tick; + }); + T::registers().into_mode(self.config, mode); + let ret = Fdcan { + config: self.config, + instance: self.instance, + _mode: mode, + }; + ret + } + + /// Start, entering mode. Does same as start(mode) + pub fn into_normal_mode(self) -> Fdcan<'d, T> { + self.start(FdcanOperatingMode::NormalOperationMode) + } + + /// Start, entering mode. Does same as start(mode) + pub fn into_internal_loopback_mode(self) -> Fdcan<'d, T> { + self.start(FdcanOperatingMode::InternalLoopbackMode) + } + + /// Start, entering mode. Does same as start(mode) + pub fn into_external_loopback_mode(self) -> Fdcan<'d, T> { + self.start(FdcanOperatingMode::ExternalLoopbackMode) + } +} + +/// FDCAN Instance +pub struct Fdcan<'d, T: Instance> { + config: crate::can::fd::config::FdCanConfig, + /// Reference to internals. + instance: FdcanInstance<'d, T>, + _mode: FdcanOperatingMode, +} + +impl<'d, T: Instance> Fdcan<'d, T> { + /// Flush one of the TX mailboxes. + pub async fn flush(&self, idx: usize) { + poll_fn(|cx| { + T::state().tx_mode.register(cx.waker()); + + if idx > 3 { + panic!("Bad mailbox"); + } + let idx = 1 << idx; + if !T::regs().txbrp().read().trp(idx) { + return Poll::Ready(()); + } + + Poll::Pending + }) + .await; + } + + /// Queues the message to be sent but exerts backpressure. If a lower-priority + /// frame is dropped from the mailbox, it is returned. If no lower-priority frames + /// can be replaced, this call asynchronously waits for a frame to be successfully + /// transmitted, then tries again. + pub async fn write(&mut self, frame: &ClassicFrame) -> Option<ClassicFrame> { + T::state().tx_mode.write::<T>(frame).await + } + + /// Returns the next received message frame + pub async fn read(&mut self) -> Result<(ClassicFrame, Timestamp), BusError> { + T::state().rx_mode.read_classic::<T>().await + } + + /// Queues the message to be sent but exerts backpressure. If a lower-priority + /// frame is dropped from the mailbox, it is returned. If no lower-priority frames + /// can be replaced, this call asynchronously waits for a frame to be successfully + /// transmitted, then tries again. + pub async fn write_fd(&mut self, frame: &FdFrame) -> Option<FdFrame> { + T::state().tx_mode.write_fd::<T>(frame).await + } + + /// Returns the next received message frame + pub async fn read_fd(&mut self) -> Result<(FdFrame, Timestamp), BusError> { + T::state().rx_mode.read_fd::<T>().await + } + + /// Split instance into separate Tx(write) and Rx(read) portions + pub fn split(self) -> (FdcanTx<'d, T>, FdcanRx<'d, T>) { + ( + FdcanTx { + config: self.config, + _instance: self.instance, + _mode: self._mode, + }, + FdcanRx { + _instance1: PhantomData::<T>, + _instance2: T::regs(), + _mode: self._mode, + }, + ) + } + + /// Join split rx and tx portions back together + pub fn join(tx: FdcanTx<'d, T>, rx: FdcanRx<'d, T>) -> Self { + Fdcan { + config: tx.config, + //_instance2: T::regs(), + instance: tx._instance, + _mode: rx._mode, + } + } + + /// Return a buffered instance of driver without CAN FD support. User must supply Buffers + pub fn buffered<const TX_BUF_SIZE: usize, const RX_BUF_SIZE: usize>( + &self, + tx_buf: &'static mut TxBuf<TX_BUF_SIZE>, + rxb: &'static mut RxBuf<RX_BUF_SIZE>, + ) -> BufferedCan<'d, T, TX_BUF_SIZE, RX_BUF_SIZE> { + BufferedCan::new(PhantomData::<T>, T::regs(), self._mode, tx_buf, rxb) + } + + /// Return a buffered instance of driver with CAN FD support. User must supply Buffers + pub fn buffered_fd<const TX_BUF_SIZE: usize, const RX_BUF_SIZE: usize>( + &self, + tx_buf: &'static mut TxFdBuf<TX_BUF_SIZE>, + rxb: &'static mut RxFdBuf<RX_BUF_SIZE>, + ) -> BufferedCanFd<'d, T, TX_BUF_SIZE, RX_BUF_SIZE> { + BufferedCanFd::new(PhantomData::<T>, T::regs(), self._mode, tx_buf, rxb) + } +} + +/// User supplied buffer for RX Buffering +pub type RxBuf<const BUF_SIZE: usize> = + Channel<CriticalSectionRawMutex, Result<(ClassicFrame, Timestamp), BusError>, BUF_SIZE>; + +/// User supplied buffer for TX buffering +pub type TxBuf<const BUF_SIZE: usize> = Channel<CriticalSectionRawMutex, ClassicFrame, BUF_SIZE>; + +/// Buffered FDCAN Instance +pub struct BufferedCan<'d, T: Instance, const TX_BUF_SIZE: usize, const RX_BUF_SIZE: usize> { + _instance1: PhantomData<T>, + _instance2: &'d crate::pac::can::Fdcan, + _mode: FdcanOperatingMode, + tx_buf: &'static TxBuf<TX_BUF_SIZE>, + rx_buf: &'static RxBuf<RX_BUF_SIZE>, +} + +/// Sender that can be used for sending CAN frames. +#[derive(Copy, Clone)] +pub struct BufferedCanSender { + tx_buf: embassy_sync::channel::DynamicSender<'static, ClassicFrame>, + waker: fn(), +} + +impl BufferedCanSender { + /// Async write frame to TX buffer. + pub fn try_write(&mut self, frame: ClassicFrame) -> Result<(), embassy_sync::channel::TrySendError<ClassicFrame>> { + self.tx_buf.try_send(frame)?; + (self.waker)(); + Ok(()) + } + + /// Async write frame to TX buffer. + pub async fn write(&mut self, frame: ClassicFrame) { + self.tx_buf.send(frame).await; + (self.waker)(); + } + + /// Allows a poll_fn to poll until the channel is ready to write + pub fn poll_ready_to_send(&self, cx: &mut core::task::Context<'_>) -> core::task::Poll<()> { + self.tx_buf.poll_ready_to_send(cx) + } +} + +/// Receiver that can be used for receiving CAN frames. Note, each CAN frame will only be received by one receiver. +pub type BufferedCanReceiver = + embassy_sync::channel::DynamicReceiver<'static, Result<(ClassicFrame, Timestamp), BusError>>; + +impl<'c, 'd, T: Instance, const TX_BUF_SIZE: usize, const RX_BUF_SIZE: usize> + BufferedCan<'d, T, TX_BUF_SIZE, RX_BUF_SIZE> +{ + fn new( + _instance1: PhantomData<T>, + _instance2: &'d crate::pac::can::Fdcan, + _mode: FdcanOperatingMode, + tx_buf: &'static TxBuf<TX_BUF_SIZE>, + rx_buf: &'static RxBuf<RX_BUF_SIZE>, + ) -> Self { + BufferedCan { + _instance1, + _instance2, + _mode, + tx_buf, + rx_buf, + } + .setup() + } + + fn setup(self) -> Self { + // We don't want interrupts being processed while we change modes. + critical_section::with(|_| unsafe { + let rx_inner = sealed::ClassicBufferedRxInner { + rx_sender: self.rx_buf.sender().into(), + }; + let tx_inner = sealed::ClassicBufferedTxInner { + tx_receiver: self.tx_buf.receiver().into(), + }; + T::mut_state().rx_mode = sealed::RxMode::ClassicBuffered(rx_inner); + T::mut_state().tx_mode = sealed::TxMode::ClassicBuffered(tx_inner); + }); + self + } + + /// Async write frame to TX buffer. + pub async fn write(&mut self, frame: ClassicFrame) { + self.tx_buf.send(frame).await; + T::IT0Interrupt::pend(); // Wake for Tx + } + + /// Async read frame from RX buffer. + pub async fn read(&mut self) -> Result<(ClassicFrame, Timestamp), BusError> { + self.rx_buf.receive().await + } + + /// Returns a sender that can be used for sending CAN frames. + pub fn writer(&self) -> BufferedCanSender { + BufferedCanSender { + tx_buf: self.tx_buf.sender().into(), + waker: T::IT0Interrupt::pend, + } + } + + /// Returns a receiver that can be used for receiving CAN frames. Note, each CAN frame will only be received by one receiver. + pub fn reader(&self) -> BufferedCanReceiver { + self.rx_buf.receiver().into() + } +} + +impl<'c, 'd, T: Instance, const TX_BUF_SIZE: usize, const RX_BUF_SIZE: usize> Drop + for BufferedCan<'d, T, TX_BUF_SIZE, RX_BUF_SIZE> +{ + fn drop(&mut self) { + critical_section::with(|_| unsafe { + T::mut_state().rx_mode = sealed::RxMode::NonBuffered(embassy_sync::waitqueue::AtomicWaker::new()); + T::mut_state().tx_mode = sealed::TxMode::NonBuffered(embassy_sync::waitqueue::AtomicWaker::new()); + }); + } +} + +/// User supplied buffer for RX Buffering +pub type RxFdBuf<const BUF_SIZE: usize> = + Channel<CriticalSectionRawMutex, Result<(FdFrame, Timestamp), BusError>, BUF_SIZE>; + +/// User supplied buffer for TX buffering +pub type TxFdBuf<const BUF_SIZE: usize> = Channel<CriticalSectionRawMutex, FdFrame, BUF_SIZE>; + +/// Buffered FDCAN Instance +pub struct BufferedCanFd<'d, T: Instance, const TX_BUF_SIZE: usize, const RX_BUF_SIZE: usize> { + _instance1: PhantomData<T>, + _instance2: &'d crate::pac::can::Fdcan, + _mode: FdcanOperatingMode, + tx_buf: &'static TxFdBuf<TX_BUF_SIZE>, + rx_buf: &'static RxFdBuf<RX_BUF_SIZE>, +} + +/// Sender that can be used for sending CAN frames. +#[derive(Copy, Clone)] +pub struct BufferedFdCanSender { + tx_buf: embassy_sync::channel::DynamicSender<'static, FdFrame>, + waker: fn(), +} + +impl BufferedFdCanSender { + /// Async write frame to TX buffer. + pub fn try_write(&mut self, frame: FdFrame) -> Result<(), embassy_sync::channel::TrySendError<FdFrame>> { + self.tx_buf.try_send(frame)?; + (self.waker)(); + Ok(()) + } + + /// Async write frame to TX buffer. + pub async fn write(&mut self, frame: FdFrame) { + self.tx_buf.send(frame).await; + (self.waker)(); + } + + /// Allows a poll_fn to poll until the channel is ready to write + pub fn poll_ready_to_send(&self, cx: &mut core::task::Context<'_>) -> core::task::Poll<()> { + self.tx_buf.poll_ready_to_send(cx) + } +} + +/// Receiver that can be used for receiving CAN frames. Note, each CAN frame will only be received by one receiver. +pub type BufferedFdCanReceiver = + embassy_sync::channel::DynamicReceiver<'static, Result<(FdFrame, Timestamp), BusError>>; + +impl<'c, 'd, T: Instance, const TX_BUF_SIZE: usize, const RX_BUF_SIZE: usize> + BufferedCanFd<'d, T, TX_BUF_SIZE, RX_BUF_SIZE> +{ + fn new( + _instance1: PhantomData<T>, + _instance2: &'d crate::pac::can::Fdcan, + _mode: FdcanOperatingMode, + tx_buf: &'static TxFdBuf<TX_BUF_SIZE>, + rx_buf: &'static RxFdBuf<RX_BUF_SIZE>, + ) -> Self { + BufferedCanFd { + _instance1, + _instance2, + _mode, + tx_buf, + rx_buf, + } + .setup() + } + + fn setup(self) -> Self { + // We don't want interrupts being processed while we change modes. + critical_section::with(|_| unsafe { + let rx_inner = sealed::FdBufferedRxInner { + rx_sender: self.rx_buf.sender().into(), + }; + let tx_inner = sealed::FdBufferedTxInner { + tx_receiver: self.tx_buf.receiver().into(), + }; + T::mut_state().rx_mode = sealed::RxMode::FdBuffered(rx_inner); + T::mut_state().tx_mode = sealed::TxMode::FdBuffered(tx_inner); + }); + self + } + + /// Async write frame to TX buffer. + pub async fn write(&mut self, frame: FdFrame) { + self.tx_buf.send(frame).await; + T::IT0Interrupt::pend(); // Wake for Tx + } + + /// Async read frame from RX buffer. + pub async fn read(&mut self) -> Result<(FdFrame, Timestamp), BusError> { + self.rx_buf.receive().await + } + + /// Returns a sender that can be used for sending CAN frames. + pub fn writer(&self) -> BufferedFdCanSender { + BufferedFdCanSender { + tx_buf: self.tx_buf.sender().into(), + waker: T::IT0Interrupt::pend, + } + } + + /// Returns a receiver that can be used for receiving CAN frames. Note, each CAN frame will only be received by one receiver. + pub fn reader(&self) -> BufferedFdCanReceiver { + self.rx_buf.receiver().into() + } +} + +impl<'c, 'd, T: Instance, const TX_BUF_SIZE: usize, const RX_BUF_SIZE: usize> Drop + for BufferedCanFd<'d, T, TX_BUF_SIZE, RX_BUF_SIZE> +{ + fn drop(&mut self) { + critical_section::with(|_| unsafe { + T::mut_state().rx_mode = sealed::RxMode::NonBuffered(embassy_sync::waitqueue::AtomicWaker::new()); + T::mut_state().tx_mode = sealed::TxMode::NonBuffered(embassy_sync::waitqueue::AtomicWaker::new()); + }); + } +} + +/// FDCAN Rx only Instance +pub struct FdcanRx<'d, T: Instance> { + _instance1: PhantomData<T>, + _instance2: &'d crate::pac::can::Fdcan, + _mode: FdcanOperatingMode, +} + +/// FDCAN Tx only Instance +pub struct FdcanTx<'d, T: Instance> { + config: crate::can::fd::config::FdCanConfig, + _instance: FdcanInstance<'d, T>, //(PeripheralRef<'a, T>); + _mode: FdcanOperatingMode, +} + +impl<'c, 'd, T: Instance> FdcanTx<'d, T> { + /// Queues the message to be sent but exerts backpressure. If a lower-priority + /// frame is dropped from the mailbox, it is returned. If no lower-priority frames + /// can be replaced, this call asynchronously waits for a frame to be successfully + /// transmitted, then tries again. + pub async fn write(&mut self, frame: &ClassicFrame) -> Option<ClassicFrame> { + T::state().tx_mode.write::<T>(frame).await + } + + /// Queues the message to be sent but exerts backpressure. If a lower-priority + /// frame is dropped from the mailbox, it is returned. If no lower-priority frames + /// can be replaced, this call asynchronously waits for a frame to be successfully + /// transmitted, then tries again. + pub async fn write_fd(&mut self, frame: &FdFrame) -> Option<FdFrame> { + T::state().tx_mode.write_fd::<T>(frame).await + } +} + +impl<'c, 'd, T: Instance> FdcanRx<'d, T> { + /// Returns the next received message frame + pub async fn read(&mut self) -> Result<(ClassicFrame, Timestamp), BusError> { + T::state().rx_mode.read_classic::<T>().await + } + + /// Returns the next received message frame + pub async fn read_fd(&mut self) -> Result<(FdFrame, Timestamp), BusError> { + T::state().rx_mode.read_fd::<T>().await + } +} pub(crate) mod sealed { - use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; - use embassy_sync::channel::Channel; + use core::future::poll_fn; + use core::task::Poll; + + use embassy_sync::channel::{DynamicReceiver, DynamicSender}; use embassy_sync::waitqueue::AtomicWaker; + use super::CanHeader; + use crate::can::_version::{BusError, Timestamp}; + use crate::can::frame::{ClassicFrame, FdFrame}; + + pub struct ClassicBufferedRxInner { + pub rx_sender: DynamicSender<'static, Result<(ClassicFrame, Timestamp), BusError>>, + } + pub struct ClassicBufferedTxInner { + pub tx_receiver: DynamicReceiver<'static, ClassicFrame>, + } + + pub struct FdBufferedRxInner { + pub rx_sender: DynamicSender<'static, Result<(FdFrame, Timestamp), BusError>>, + } + pub struct FdBufferedTxInner { + pub tx_receiver: DynamicReceiver<'static, FdFrame>, + } + + pub enum RxMode { + NonBuffered(AtomicWaker), + ClassicBuffered(ClassicBufferedRxInner), + FdBuffered(FdBufferedRxInner), + } + + impl RxMode { + pub fn register(&self, arg: &core::task::Waker) { + match self { + RxMode::NonBuffered(waker) => waker.register(arg), + _ => { + panic!("Bad Mode") + } + } + } + + pub fn on_interrupt<T: Instance>(&self, fifonr: usize) { + T::regs().ir().write(|w| w.set_rfn(fifonr, true)); + match self { + RxMode::NonBuffered(waker) => { + waker.wake(); + } + RxMode::ClassicBuffered(buf) => { + if let Some(result) = self.read::<T, _>() { + let _ = buf.rx_sender.try_send(result); + } + } + RxMode::FdBuffered(buf) => { + if let Some(result) = self.read::<T, _>() { + let _ = buf.rx_sender.try_send(result); + } + } + } + } + + fn read<T: Instance, F: CanHeader>(&self) -> Option<Result<(F, Timestamp), BusError>> { + if let Some((msg, ts)) = T::registers().read(0) { + let ts = T::calc_timestamp(T::state().ns_per_timer_tick, ts); + Some(Ok((msg, ts))) + } else if let Some((msg, ts)) = T::registers().read(1) { + let ts = T::calc_timestamp(T::state().ns_per_timer_tick, ts); + Some(Ok((msg, ts))) + } else if let Some(err) = T::registers().curr_error() { + // TODO: this is probably wrong + Some(Err(err)) + } else { + None + } + } + + async fn read_async<T: Instance, F: CanHeader>(&self) -> Result<(F, Timestamp), BusError> { + poll_fn(|cx| { + T::state().err_waker.register(cx.waker()); + self.register(cx.waker()); + match self.read::<T, _>() { + Some(result) => Poll::Ready(result), + None => Poll::Pending, + } + }) + .await + } + + pub async fn read_classic<T: Instance>(&self) -> Result<(ClassicFrame, Timestamp), BusError> { + self.read_async::<T, _>().await + } + + pub async fn read_fd<T: Instance>(&self) -> Result<(FdFrame, Timestamp), BusError> { + self.read_async::<T, _>().await + } + } + + pub enum TxMode { + NonBuffered(AtomicWaker), + ClassicBuffered(ClassicBufferedTxInner), + FdBuffered(FdBufferedTxInner), + } + + impl TxMode { + pub fn register(&self, arg: &core::task::Waker) { + match self { + TxMode::NonBuffered(waker) => { + waker.register(arg); + } + _ => { + panic!("Bad mode"); + } + } + } + + /// Queues the message to be sent but exerts backpressure. If a lower-priority + /// frame is dropped from the mailbox, it is returned. If no lower-priority frames + /// can be replaced, this call asynchronously waits for a frame to be successfully + /// transmitted, then tries again. + async fn write_generic<T: Instance, F: embedded_can::Frame + CanHeader>(&self, frame: &F) -> Option<F> { + poll_fn(|cx| { + self.register(cx.waker()); + + if let Ok(dropped) = T::registers().write(frame) { + return Poll::Ready(dropped); + } + + // Couldn't replace any lower priority frames. Need to wait for some mailboxes + // to clear. + Poll::Pending + }) + .await + } + + /// Queues the message to be sent but exerts backpressure. If a lower-priority + /// frame is dropped from the mailbox, it is returned. If no lower-priority frames + /// can be replaced, this call asynchronously waits for a frame to be successfully + /// transmitted, then tries again. + pub async fn write<T: Instance>(&self, frame: &ClassicFrame) -> Option<ClassicFrame> { + self.write_generic::<T, _>(frame).await + } + + /// Queues the message to be sent but exerts backpressure. If a lower-priority + /// frame is dropped from the mailbox, it is returned. If no lower-priority frames + /// can be replaced, this call asynchronously waits for a frame to be successfully + /// transmitted, then tries again. + pub async fn write_fd<T: Instance>(&self, frame: &FdFrame) -> Option<FdFrame> { + self.write_generic::<T, _>(frame).await + } + } + pub struct State { - pub tx_waker: AtomicWaker, + pub rx_mode: RxMode, + pub tx_mode: TxMode, + pub ns_per_timer_tick: u64, + pub err_waker: AtomicWaker, - pub rx_queue: Channel<CriticalSectionRawMutex, (u16, bxcan::Frame), 32>, } impl State { pub const fn new() -> Self { Self { - tx_waker: AtomicWaker::new(), + rx_mode: RxMode::NonBuffered(AtomicWaker::new()), + tx_mode: TxMode::NonBuffered(AtomicWaker::new()), + ns_per_timer_tick: 0, err_waker: AtomicWaker::new(), - rx_queue: Channel::new(), } } } pub trait Instance { + const MSG_RAM_OFFSET: usize; + fn regs() -> &'static crate::pac::can::Fdcan; + fn registers() -> crate::can::fd::peripheral::Registers; + fn ram() -> &'static crate::pac::fdcanram::Fdcanram; fn state() -> &'static State; + unsafe fn mut_state() -> &'static mut State; + fn calc_timestamp(ns_per_timer_tick: u64, ts_val: u16) -> Timestamp; } } -/// Interruptable FDCAN instance. -pub trait InterruptableInstance {} -/// FDCAN instance. -pub trait Instance: sealed::Instance + InterruptableInstance + 'static {} +/// Instance trait +pub trait Instance: sealed::Instance + RccPeripheral + 'static { + /// Interrupt 0 + type IT0Interrupt: crate::interrupt::typelevel::Interrupt; + /// Interrupt 0 + type IT1Interrupt: crate::interrupt::typelevel::Interrupt; +} -foreach_peripheral!( - (can, $inst:ident) => { +/// Fdcan Instance struct +pub struct FdcanInstance<'a, T>(PeripheralRef<'a, T>); + +macro_rules! impl_fdcan { + ($inst:ident, $msg_ram_inst:ident, $msg_ram_offset:literal) => { impl sealed::Instance for peripherals::$inst { + const MSG_RAM_OFFSET: usize = $msg_ram_offset; + fn regs() -> &'static crate::pac::can::Fdcan { &crate::pac::$inst } - - fn state() -> &'static sealed::State { - static STATE: sealed::State = sealed::State::new(); - &STATE + fn registers() -> Registers { + Registers{regs: &crate::pac::$inst, msgram: &crate::pac::$msg_ram_inst, msg_ram_offset: Self::MSG_RAM_OFFSET} } + fn ram() -> &'static crate::pac::fdcanram::Fdcanram { + &crate::pac::$msg_ram_inst + } + unsafe fn mut_state() -> & 'static mut sealed::State { + static mut STATE: sealed::State = sealed::State::new(); + & mut STATE + } + fn state() -> &'static sealed::State { + unsafe { peripherals::$inst::mut_state() } + } + + #[cfg(feature = "time")] + fn calc_timestamp(ns_per_timer_tick: u64, ts_val: u16) -> Timestamp { + let now_embassy = embassy_time::Instant::now(); + if ns_per_timer_tick == 0 { + return now_embassy; + } + let cantime = { Self::regs().tscv().read().tsc() }; + let delta = cantime.overflowing_sub(ts_val).0 as u64; + let ns = ns_per_timer_tick * delta as u64; + now_embassy - embassy_time::Duration::from_nanos(ns) + } + + #[cfg(not(feature = "time"))] + fn calc_timestamp(_ns_per_timer_tick: u64, ts_val: u16) -> Timestamp { + ts_val + } + } - impl Instance for peripherals::$inst {} + #[allow(non_snake_case)] + pub(crate) mod $inst { - impl InterruptableInstance for peripherals::$inst {} + foreach_interrupt!( + ($inst,can,FDCAN,IT0,$irq:ident) => { + pub type Interrupt0 = crate::interrupt::typelevel::$irq; + }; + ($inst,can,FDCAN,IT1,$irq:ident) => { + pub type Interrupt1 = crate::interrupt::typelevel::$irq; + }; + ); + } + impl Instance for peripherals::$inst { + type IT0Interrupt = $inst::Interrupt0; + type IT1Interrupt = $inst::Interrupt1; + } }; + + ($inst:ident, $msg_ram_inst:ident) => { + impl_fdcan!($inst, $msg_ram_inst, 0); + }; +} + +#[cfg(not(stm32h7))] +foreach_peripheral!( + (can, FDCAN) => { impl_fdcan!(FDCAN, FDCANRAM); }; + (can, FDCAN1) => { impl_fdcan!(FDCAN1, FDCANRAM1); }; + (can, FDCAN2) => { impl_fdcan!(FDCAN2, FDCANRAM2); }; + (can, FDCAN3) => { impl_fdcan!(FDCAN3, FDCANRAM3); }; +); + +#[cfg(stm32h7)] +foreach_peripheral!( + (can, FDCAN1) => { impl_fdcan!(FDCAN1, FDCANRAM, 0x0000); }; + (can, FDCAN2) => { impl_fdcan!(FDCAN2, FDCANRAM, 0x0C00); }; + (can, FDCAN3) => { impl_fdcan!(FDCAN3, FDCANRAM, 0x1800); }; ); pin_trait!(RxPin, Instance); diff --git a/embassy-stm32/src/can/frame.rs b/embassy-stm32/src/can/frame.rs new file mode 100644 index 000000000..9c293035d --- /dev/null +++ b/embassy-stm32/src/can/frame.rs @@ -0,0 +1,398 @@ +//! Definition for CAN Frames +use bit_field::BitField; + +/// CAN Header, without meta data +#[derive(Debug, Copy, Clone)] +pub struct Header { + id: embedded_can::Id, + len: u8, + flags: u8, +} + +impl Header { + const FLAG_RTR: usize = 0; // Remote + const FLAG_FDCAN: usize = 1; // FDCan vs Classic CAN + const FLAG_BRS: usize = 2; // Bit-rate switching, ignored for Classic CAN + + /// Create new CAN Header + pub fn new(id: embedded_can::Id, len: u8, rtr: bool) -> Header { + let mut flags = 0u8; + flags.set_bit(Self::FLAG_RTR, rtr); + Header { id, len, flags } + } + + /// Create new CAN FD Header + pub fn new_fd(id: embedded_can::Id, len: u8, rtr: bool, brs: bool) -> Header { + let mut flags = 0u8; + flags.set_bit(Self::FLAG_RTR, rtr); + flags.set_bit(Self::FLAG_FDCAN, true); + flags.set_bit(Self::FLAG_BRS, brs); + Header { id, len, flags } + } + + /// Return ID + pub fn id(&self) -> &embedded_can::Id { + &self.id + } + + /// Return length as u8 + pub fn len(&self) -> u8 { + self.len + } + + /// Is remote frame + pub fn rtr(&self) -> bool { + self.flags.get_bit(Self::FLAG_RTR) + } + + /// Request/is FDCAN frame + pub fn fdcan(&self) -> bool { + self.flags.get_bit(Self::FLAG_FDCAN) + } + + /// Request/is Flexible Data Rate + pub fn bit_rate_switching(&self) -> bool { + self.flags.get_bit(Self::FLAG_BRS) + } +} + +/// Trait for FDCAN frame types, providing ability to construct from a Header +/// and to retrieve the Header from a frame +pub trait CanHeader: Sized { + /// Construct frame from header and payload + fn from_header(header: Header, data: &[u8]) -> Option<Self>; + + /// Get this frame's header struct + fn header(&self) -> &Header; +} + +/// Payload of a classic CAN data frame. +/// +/// Contains 0 to 8 Bytes of data. +#[derive(Debug, Copy, Clone)] +pub struct ClassicData { + pub(crate) bytes: [u8; 8], +} + +impl ClassicData { + /// Creates a data payload from a raw byte slice. + /// + /// Returns `None` if `data` is more than 64 bytes (which is the maximum) or + /// cannot be represented with an FDCAN DLC. + pub fn new(data: &[u8]) -> Option<Self> { + if !FdData::is_valid_len(data.len()) { + return None; + } + + let mut bytes = [0; 8]; + bytes[..data.len()].copy_from_slice(data); + + Some(Self { bytes }) + } + + /// Raw read access to data. + pub fn raw(&self) -> &[u8] { + &self.bytes + } + + /// Checks if the length can be encoded in FDCAN DLC field. + pub const fn is_valid_len(len: usize) -> bool { + match len { + 0..=8 => true, + _ => false, + } + } + + /// Creates an empty data payload containing 0 bytes. + #[inline] + pub const fn empty() -> Self { + Self { bytes: [0; 8] } + } +} + +/// Frame with up to 8 bytes of data payload as per Classic CAN +#[derive(Debug, Copy, Clone)] +pub struct ClassicFrame { + can_header: Header, + data: ClassicData, +} + +impl ClassicFrame { + pub(crate) const MAX_DATA_LEN: usize = 8; + + /// Create a new CAN classic Frame + pub fn new(can_header: Header, data: ClassicData) -> ClassicFrame { + ClassicFrame { can_header, data } + } + + /// Create new extended frame + pub fn new_extended(raw_id: u32, raw_data: &[u8]) -> Option<Self> { + if let Some(id) = embedded_can::ExtendedId::new(raw_id) { + match ClassicData::new(raw_data) { + Some(data) => Some(ClassicFrame::new( + Header::new(id.into(), raw_data.len() as u8, false), + data, + )), + None => None, + } + } else { + None + } + } + + /// Create new standard frame + pub fn new_standard(raw_id: u16, raw_data: &[u8]) -> Option<Self> { + if let Some(id) = embedded_can::StandardId::new(raw_id) { + match ClassicData::new(raw_data) { + Some(data) => Some(ClassicFrame::new( + Header::new(id.into(), raw_data.len() as u8, false), + data, + )), + None => None, + } + } else { + None + } + } + + /// Create new remote frame + pub fn new_remote(id: impl Into<embedded_can::Id>, len: usize) -> Option<Self> { + if len <= 8usize { + Some(ClassicFrame::new( + Header::new(id.into(), len as u8, true), + ClassicData::empty(), + )) + } else { + None + } + } + + /// Get reference to data + pub fn header(&self) -> &Header { + &self.can_header + } + + /// Return ID + pub fn id(&self) -> &embedded_can::Id { + &self.can_header.id + } + + /// Get reference to data + pub fn data(&self) -> &[u8] { + &self.data.raw() + } +} + +impl embedded_can::Frame for ClassicFrame { + fn new(id: impl Into<embedded_can::Id>, raw_data: &[u8]) -> Option<Self> { + match ClassicData::new(raw_data) { + Some(data) => Some(ClassicFrame::new( + Header::new(id.into(), raw_data.len() as u8, false), + data, + )), + None => None, + } + } + fn new_remote(id: impl Into<embedded_can::Id>, len: usize) -> Option<Self> { + if len <= 8 { + Some(ClassicFrame::new( + Header::new(id.into(), len as u8, true), + ClassicData::empty(), + )) + } else { + None + } + } + fn is_extended(&self) -> bool { + match self.can_header.id { + embedded_can::Id::Extended(_) => true, + embedded_can::Id::Standard(_) => false, + } + } + fn is_remote_frame(&self) -> bool { + self.can_header.rtr() + } + fn id(&self) -> embedded_can::Id { + self.can_header.id + } + fn dlc(&self) -> usize { + self.can_header.len as usize + } + fn data(&self) -> &[u8] { + &self.data.raw() + } +} + +impl CanHeader for ClassicFrame { + fn from_header(header: Header, data: &[u8]) -> Option<Self> { + Some(Self::new(header, ClassicData::new(data)?)) + } + + fn header(&self) -> &Header { + self.header() + } +} + +/// Payload of a (FD)CAN data frame. +/// +/// Contains 0 to 64 Bytes of data. +#[derive(Debug, Copy, Clone)] +pub struct FdData { + pub(crate) bytes: [u8; 64], +} + +impl FdData { + /// Creates a data payload from a raw byte slice. + /// + /// Returns `None` if `data` is more than 64 bytes (which is the maximum) or + /// cannot be represented with an FDCAN DLC. + pub fn new(data: &[u8]) -> Option<Self> { + if !FdData::is_valid_len(data.len()) { + return None; + } + + let mut bytes = [0; 64]; + bytes[..data.len()].copy_from_slice(data); + + Some(Self { bytes }) + } + + /// Raw read access to data. + pub fn raw(&self) -> &[u8] { + &self.bytes + } + + /// Checks if the length can be encoded in FDCAN DLC field. + pub const fn is_valid_len(len: usize) -> bool { + match len { + 0..=8 => true, + 12 => true, + 16 => true, + 20 => true, + 24 => true, + 32 => true, + 48 => true, + 64 => true, + _ => false, + } + } + + /// Creates an empty data payload containing 0 bytes. + #[inline] + pub const fn empty() -> Self { + Self { bytes: [0; 64] } + } +} + +/// Frame with up to 8 bytes of data payload as per Fd CAN +#[derive(Debug, Copy, Clone)] +pub struct FdFrame { + can_header: Header, + data: FdData, +} + +impl FdFrame { + /// Create a new CAN classic Frame + pub fn new(can_header: Header, data: FdData) -> FdFrame { + FdFrame { can_header, data } + } + + /// Create new extended frame + pub fn new_extended(raw_id: u32, raw_data: &[u8]) -> Option<Self> { + if let Some(id) = embedded_can::ExtendedId::new(raw_id) { + match FdData::new(raw_data) { + Some(data) => Some(FdFrame::new(Header::new(id.into(), raw_data.len() as u8, false), data)), + None => None, + } + } else { + None + } + } + + /// Create new standard frame + pub fn new_standard(raw_id: u16, raw_data: &[u8]) -> Option<Self> { + if let Some(id) = embedded_can::StandardId::new(raw_id) { + match FdData::new(raw_data) { + Some(data) => Some(FdFrame::new(Header::new(id.into(), raw_data.len() as u8, false), data)), + None => None, + } + } else { + None + } + } + + /// Create new remote frame + pub fn new_remote(id: impl Into<embedded_can::Id>, len: usize) -> Option<Self> { + if len <= 8 { + Some(FdFrame::new(Header::new(id.into(), len as u8, true), FdData::empty())) + } else { + None + } + } + + /// Get reference to data + pub fn header(&self) -> &Header { + &self.can_header + } + + /// Return ID + pub fn id(&self) -> &embedded_can::Id { + &self.can_header.id + } + + /// Get reference to data + pub fn data(&self) -> &[u8] { + &self.data.raw() + } +} + +impl embedded_can::Frame for FdFrame { + fn new(id: impl Into<embedded_can::Id>, raw_data: &[u8]) -> Option<Self> { + match FdData::new(raw_data) { + Some(data) => Some(FdFrame::new( + Header::new_fd(id.into(), raw_data.len() as u8, false, true), + data, + )), + None => None, + } + } + fn new_remote(id: impl Into<embedded_can::Id>, len: usize) -> Option<Self> { + if len <= 8 { + Some(FdFrame::new( + Header::new_fd(id.into(), len as u8, true, true), + FdData::empty(), + )) + } else { + None + } + } + fn is_extended(&self) -> bool { + match self.can_header.id { + embedded_can::Id::Extended(_) => true, + embedded_can::Id::Standard(_) => false, + } + } + fn is_remote_frame(&self) -> bool { + self.can_header.rtr() + } + fn id(&self) -> embedded_can::Id { + self.can_header.id + } + // Returns length in bytes even for CANFD packets which embedded-can does not really mention. + fn dlc(&self) -> usize { + self.can_header.len as usize + } + fn data(&self) -> &[u8] { + &self.data.raw() + } +} + +impl CanHeader for FdFrame { + fn from_header(header: Header, data: &[u8]) -> Option<Self> { + Some(Self::new(header, FdData::new(data)?)) + } + + fn header(&self) -> &Header { + self.header() + } +} diff --git a/embassy-stm32/src/can/util.rs b/embassy-stm32/src/can/util.rs new file mode 100644 index 000000000..fcdbbad62 --- /dev/null +++ b/embassy-stm32/src/can/util.rs @@ -0,0 +1,117 @@ +//! Utility functions shared between CAN controller types. + +use core::num::{NonZeroU16, NonZeroU8}; + +/// Shared struct to represent bit timings used by calc_can_timings. +#[derive(Clone, Copy, Debug)] +pub struct NominalBitTiming { + /// Value by which the oscillator frequency is divided for generating the bit time quanta. The bit + /// time is built up from a multiple of this quanta. Valid values are 1 to 512. + pub prescaler: NonZeroU16, + /// Valid values are 1 to 128. + pub seg1: NonZeroU8, + /// Valid values are 1 to 255. + pub seg2: NonZeroU8, + /// Valid values are 1 to 128. + pub sync_jump_width: NonZeroU8, +} + +/// Calculate nominal CAN bit timing based on CAN bitrate and periphial clock frequency +pub fn calc_can_timings(periph_clock: crate::time::Hertz, can_bitrate: u32) -> Option<NominalBitTiming> { + 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 sync_jump_width = core::num::NonZeroU8::new(1)?; + + let seg1 = core::num::NonZeroU8::new(bs1)?; + let seg2 = core::num::NonZeroU8::new(bs2)?; + let nz_prescaler = core::num::NonZeroU16::new(prescaler as u16)?; + + Some(NominalBitTiming { + sync_jump_width, + prescaler: nz_prescaler, + seg1, + seg2, + }) +} diff --git a/embassy-stm32/src/cryp/mod.rs b/embassy-stm32/src/cryp/mod.rs new file mode 100644 index 000000000..8f259520a --- /dev/null +++ b/embassy-stm32/src/cryp/mod.rs @@ -0,0 +1,1356 @@ +//! Crypto Accelerator (CRYP) +#[cfg(any(cryp_v2, cryp_v3))] +use core::cmp::min; +use core::marker::PhantomData; + +use embassy_hal_internal::{into_ref, PeripheralRef}; + +use crate::{interrupt, pac, peripherals, Peripheral}; + +const DES_BLOCK_SIZE: usize = 8; // 64 bits +const AES_BLOCK_SIZE: usize = 16; // 128 bits + +/// This trait encapsulates all cipher-specific behavior/ +pub trait Cipher<'c> { + /// Processing block size. Determined by the processor and the algorithm. + const BLOCK_SIZE: usize; + + /// Indicates whether the cipher requires the application to provide padding. + /// If `true`, no partial blocks will be accepted (a panic will occur). + const REQUIRES_PADDING: bool = false; + + /// Returns the symmetric key. + fn key(&self) -> &[u8]; + + /// Returns the initialization vector. + fn iv(&self) -> &[u8]; + + /// Sets the processor algorithm mode according to the associated cipher. + fn set_algomode(&self, p: &pac::cryp::Cryp); + + /// Performs any key preparation within the processor, if necessary. + fn prepare_key(&self, _p: &pac::cryp::Cryp) {} + + /// Performs any cipher-specific initialization. + fn init_phase(&self, _p: &pac::cryp::Cryp) {} + + /// Called prior to processing the last data block for cipher-specific operations. + fn pre_final_block(&self, _p: &pac::cryp::Cryp, _dir: Direction, _padding_len: usize) -> [u32; 4] { + return [0; 4]; + } + + /// Called after processing the last data block for cipher-specific operations. + fn post_final_block( + &self, + _p: &pac::cryp::Cryp, + _dir: Direction, + _int_data: &mut [u8; AES_BLOCK_SIZE], + _temp1: [u32; 4], + _padding_mask: [u8; 16], + ) { + } + + /// Called prior to processing the first associated data block for cipher-specific operations. + fn get_header_block(&self) -> &[u8] { + return [0; 0].as_slice(); + } +} + +/// This trait enables restriction of ciphers to specific key sizes. +pub trait CipherSized {} + +/// This trait enables restriction of initialization vectors to sizes compatibile with a cipher mode. +pub trait IVSized {} + +/// This trait enables restriction of a header phase to authenticated ciphers only. +pub trait CipherAuthenticated<const TAG_SIZE: usize> { + /// Defines the authentication tag size. + const TAG_SIZE: usize = TAG_SIZE; +} + +/// TDES-ECB Cipher Mode +pub struct TdesEcb<'c, const KEY_SIZE: usize> { + iv: &'c [u8; 0], + key: &'c [u8; KEY_SIZE], +} + +impl<'c, const KEY_SIZE: usize> TdesEcb<'c, KEY_SIZE> { + /// Constructs a new AES-ECB cipher for a cryptographic operation. + pub fn new(key: &'c [u8; KEY_SIZE]) -> Self { + return Self { key: key, iv: &[0; 0] }; + } +} + +impl<'c, const KEY_SIZE: usize> Cipher<'c> for TdesEcb<'c, KEY_SIZE> { + const BLOCK_SIZE: usize = DES_BLOCK_SIZE; + const REQUIRES_PADDING: bool = true; + + fn key(&self) -> &'c [u8] { + self.key + } + + fn iv(&self) -> &'c [u8] { + self.iv + } + + fn set_algomode(&self, p: &pac::cryp::Cryp) { + #[cfg(cryp_v1)] + { + p.cr().modify(|w| w.set_algomode(0)); + } + #[cfg(any(cryp_v2, cryp_v3))] + { + p.cr().modify(|w| w.set_algomode0(0)); + p.cr().modify(|w| w.set_algomode3(false)); + } + } +} + +impl<'c> CipherSized for TdesEcb<'c, { 112 / 8 }> {} +impl<'c> CipherSized for TdesEcb<'c, { 168 / 8 }> {} +impl<'c, const KEY_SIZE: usize> IVSized for TdesEcb<'c, KEY_SIZE> {} + +/// TDES-CBC Cipher Mode +pub struct TdesCbc<'c, const KEY_SIZE: usize> { + iv: &'c [u8; 8], + key: &'c [u8; KEY_SIZE], +} + +impl<'c, const KEY_SIZE: usize> TdesCbc<'c, KEY_SIZE> { + /// Constructs a new TDES-CBC cipher for a cryptographic operation. + pub fn new(key: &'c [u8; KEY_SIZE], iv: &'c [u8; 8]) -> Self { + return Self { key: key, iv: iv }; + } +} + +impl<'c, const KEY_SIZE: usize> Cipher<'c> for TdesCbc<'c, KEY_SIZE> { + const BLOCK_SIZE: usize = DES_BLOCK_SIZE; + const REQUIRES_PADDING: bool = true; + + fn key(&self) -> &'c [u8] { + self.key + } + + fn iv(&self) -> &'c [u8] { + self.iv + } + + fn set_algomode(&self, p: &pac::cryp::Cryp) { + #[cfg(cryp_v1)] + { + p.cr().modify(|w| w.set_algomode(1)); + } + #[cfg(any(cryp_v2, cryp_v3))] + { + p.cr().modify(|w| w.set_algomode0(1)); + p.cr().modify(|w| w.set_algomode3(false)); + } + } +} + +impl<'c> CipherSized for TdesCbc<'c, { 112 / 8 }> {} +impl<'c> CipherSized for TdesCbc<'c, { 168 / 8 }> {} +impl<'c, const KEY_SIZE: usize> IVSized for TdesCbc<'c, KEY_SIZE> {} + +/// DES-ECB Cipher Mode +pub struct DesEcb<'c, const KEY_SIZE: usize> { + iv: &'c [u8; 0], + key: &'c [u8; KEY_SIZE], +} + +impl<'c, const KEY_SIZE: usize> DesEcb<'c, KEY_SIZE> { + /// Constructs a new AES-ECB cipher for a cryptographic operation. + pub fn new(key: &'c [u8; KEY_SIZE]) -> Self { + return Self { key: key, iv: &[0; 0] }; + } +} + +impl<'c, const KEY_SIZE: usize> Cipher<'c> for DesEcb<'c, KEY_SIZE> { + const BLOCK_SIZE: usize = DES_BLOCK_SIZE; + const REQUIRES_PADDING: bool = true; + + fn key(&self) -> &'c [u8] { + self.key + } + + fn iv(&self) -> &'c [u8] { + self.iv + } + + fn set_algomode(&self, p: &pac::cryp::Cryp) { + #[cfg(cryp_v1)] + { + p.cr().modify(|w| w.set_algomode(2)); + } + #[cfg(any(cryp_v2, cryp_v3))] + { + p.cr().modify(|w| w.set_algomode0(2)); + p.cr().modify(|w| w.set_algomode3(false)); + } + } +} + +impl<'c> CipherSized for DesEcb<'c, { 56 / 8 }> {} +impl<'c, const KEY_SIZE: usize> IVSized for DesEcb<'c, KEY_SIZE> {} + +/// DES-CBC Cipher Mode +pub struct DesCbc<'c, const KEY_SIZE: usize> { + iv: &'c [u8; 8], + key: &'c [u8; KEY_SIZE], +} + +impl<'c, const KEY_SIZE: usize> DesCbc<'c, KEY_SIZE> { + /// Constructs a new AES-CBC cipher for a cryptographic operation. + pub fn new(key: &'c [u8; KEY_SIZE], iv: &'c [u8; 8]) -> Self { + return Self { key: key, iv: iv }; + } +} + +impl<'c, const KEY_SIZE: usize> Cipher<'c> for DesCbc<'c, KEY_SIZE> { + const BLOCK_SIZE: usize = DES_BLOCK_SIZE; + const REQUIRES_PADDING: bool = true; + + fn key(&self) -> &'c [u8] { + self.key + } + + fn iv(&self) -> &'c [u8] { + self.iv + } + + fn set_algomode(&self, p: &pac::cryp::Cryp) { + #[cfg(cryp_v1)] + { + p.cr().modify(|w| w.set_algomode(3)); + } + #[cfg(any(cryp_v2, cryp_v3))] + { + p.cr().modify(|w| w.set_algomode0(3)); + p.cr().modify(|w| w.set_algomode3(false)); + } + } +} + +impl<'c> CipherSized for DesCbc<'c, { 56 / 8 }> {} +impl<'c, const KEY_SIZE: usize> IVSized for DesCbc<'c, KEY_SIZE> {} + +/// AES-ECB Cipher Mode +pub struct AesEcb<'c, const KEY_SIZE: usize> { + iv: &'c [u8; 0], + key: &'c [u8; KEY_SIZE], +} + +impl<'c, const KEY_SIZE: usize> AesEcb<'c, KEY_SIZE> { + /// Constructs a new AES-ECB cipher for a cryptographic operation. + pub fn new(key: &'c [u8; KEY_SIZE]) -> Self { + return Self { key: key, iv: &[0; 0] }; + } +} + +impl<'c, const KEY_SIZE: usize> Cipher<'c> for AesEcb<'c, KEY_SIZE> { + const BLOCK_SIZE: usize = AES_BLOCK_SIZE; + const REQUIRES_PADDING: bool = true; + + fn key(&self) -> &'c [u8] { + self.key + } + + fn iv(&self) -> &'c [u8] { + self.iv + } + + fn prepare_key(&self, p: &pac::cryp::Cryp) { + #[cfg(cryp_v1)] + { + p.cr().modify(|w| w.set_algomode(7)); + } + #[cfg(any(cryp_v2, cryp_v3))] + { + p.cr().modify(|w| w.set_algomode0(7)); + p.cr().modify(|w| w.set_algomode3(false)); + } + p.cr().modify(|w| w.set_crypen(true)); + while p.sr().read().busy() {} + } + + fn set_algomode(&self, p: &pac::cryp::Cryp) { + #[cfg(cryp_v1)] + { + p.cr().modify(|w| w.set_algomode(2)); + } + #[cfg(any(cryp_v2, cryp_v3))] + { + p.cr().modify(|w| w.set_algomode0(2)); + p.cr().modify(|w| w.set_algomode3(false)); + } + } +} + +impl<'c> CipherSized for AesEcb<'c, { 128 / 8 }> {} +impl<'c> CipherSized for AesEcb<'c, { 192 / 8 }> {} +impl<'c> CipherSized for AesEcb<'c, { 256 / 8 }> {} +impl<'c, const KEY_SIZE: usize> IVSized for AesEcb<'c, KEY_SIZE> {} + +/// AES-CBC Cipher Mode +pub struct AesCbc<'c, const KEY_SIZE: usize> { + iv: &'c [u8; 16], + key: &'c [u8; KEY_SIZE], +} + +impl<'c, const KEY_SIZE: usize> AesCbc<'c, KEY_SIZE> { + /// Constructs a new AES-CBC cipher for a cryptographic operation. + pub fn new(key: &'c [u8; KEY_SIZE], iv: &'c [u8; 16]) -> Self { + return Self { key: key, iv: iv }; + } +} + +impl<'c, const KEY_SIZE: usize> Cipher<'c> for AesCbc<'c, KEY_SIZE> { + const BLOCK_SIZE: usize = AES_BLOCK_SIZE; + const REQUIRES_PADDING: bool = true; + + fn key(&self) -> &'c [u8] { + self.key + } + + fn iv(&self) -> &'c [u8] { + self.iv + } + + fn prepare_key(&self, p: &pac::cryp::Cryp) { + #[cfg(cryp_v1)] + { + p.cr().modify(|w| w.set_algomode(7)); + } + #[cfg(any(cryp_v2, cryp_v3))] + { + p.cr().modify(|w| w.set_algomode0(7)); + p.cr().modify(|w| w.set_algomode3(false)); + } + p.cr().modify(|w| w.set_crypen(true)); + while p.sr().read().busy() {} + } + + fn set_algomode(&self, p: &pac::cryp::Cryp) { + #[cfg(cryp_v1)] + { + p.cr().modify(|w| w.set_algomode(5)); + } + #[cfg(any(cryp_v2, cryp_v3))] + { + p.cr().modify(|w| w.set_algomode0(5)); + p.cr().modify(|w| w.set_algomode3(false)); + } + } +} + +impl<'c> CipherSized for AesCbc<'c, { 128 / 8 }> {} +impl<'c> CipherSized for AesCbc<'c, { 192 / 8 }> {} +impl<'c> CipherSized for AesCbc<'c, { 256 / 8 }> {} +impl<'c, const KEY_SIZE: usize> IVSized for AesCbc<'c, KEY_SIZE> {} + +/// AES-CTR Cipher Mode +pub struct AesCtr<'c, const KEY_SIZE: usize> { + iv: &'c [u8; 16], + key: &'c [u8; KEY_SIZE], +} + +impl<'c, const KEY_SIZE: usize> AesCtr<'c, KEY_SIZE> { + /// Constructs a new AES-CTR cipher for a cryptographic operation. + pub fn new(key: &'c [u8; KEY_SIZE], iv: &'c [u8; 16]) -> Self { + return Self { key: key, iv: iv }; + } +} + +impl<'c, const KEY_SIZE: usize> Cipher<'c> for AesCtr<'c, KEY_SIZE> { + const BLOCK_SIZE: usize = AES_BLOCK_SIZE; + + fn key(&self) -> &'c [u8] { + self.key + } + + fn iv(&self) -> &'c [u8] { + self.iv + } + + fn set_algomode(&self, p: &pac::cryp::Cryp) { + #[cfg(cryp_v1)] + { + p.cr().modify(|w| w.set_algomode(6)); + } + #[cfg(any(cryp_v2, cryp_v3))] + { + p.cr().modify(|w| w.set_algomode0(6)); + p.cr().modify(|w| w.set_algomode3(false)); + } + } +} + +impl<'c> CipherSized for AesCtr<'c, { 128 / 8 }> {} +impl<'c> CipherSized for AesCtr<'c, { 192 / 8 }> {} +impl<'c> CipherSized for AesCtr<'c, { 256 / 8 }> {} +impl<'c, const KEY_SIZE: usize> IVSized for AesCtr<'c, KEY_SIZE> {} + +#[cfg(any(cryp_v2, cryp_v3))] +///AES-GCM Cipher Mode +pub struct AesGcm<'c, const KEY_SIZE: usize> { + iv: [u8; 16], + key: &'c [u8; KEY_SIZE], +} + +#[cfg(any(cryp_v2, cryp_v3))] +impl<'c, const KEY_SIZE: usize> AesGcm<'c, KEY_SIZE> { + /// Constucts a new AES-GCM cipher for a cryptographic operation. + pub fn new(key: &'c [u8; KEY_SIZE], iv: &'c [u8; 12]) -> Self { + let mut new_gcm = Self { key: key, iv: [0; 16] }; + new_gcm.iv[..12].copy_from_slice(iv); + new_gcm.iv[15] = 2; + new_gcm + } +} + +#[cfg(any(cryp_v2, cryp_v3))] +impl<'c, const KEY_SIZE: usize> Cipher<'c> for AesGcm<'c, KEY_SIZE> { + const BLOCK_SIZE: usize = AES_BLOCK_SIZE; + + fn key(&self) -> &'c [u8] { + self.key + } + + fn iv(&self) -> &[u8] { + self.iv.as_slice() + } + + fn set_algomode(&self, p: &pac::cryp::Cryp) { + p.cr().modify(|w| w.set_algomode0(0)); + p.cr().modify(|w| w.set_algomode3(true)); + } + + fn init_phase(&self, p: &pac::cryp::Cryp) { + p.cr().modify(|w| w.set_gcm_ccmph(0)); + p.cr().modify(|w| w.set_crypen(true)); + while p.cr().read().crypen() {} + } + + #[cfg(cryp_v2)] + fn pre_final_block(&self, p: &pac::cryp::Cryp, dir: Direction, _padding_len: usize) -> [u32; 4] { + //Handle special GCM partial block process. + if dir == Direction::Encrypt { + p.cr().modify(|w| w.set_crypen(false)); + p.cr().modify(|w| w.set_algomode3(false)); + p.cr().modify(|w| w.set_algomode0(6)); + let iv1r = p.csgcmccmr(7).read() - 1; + p.init(1).ivrr().write_value(iv1r); + p.cr().modify(|w| w.set_crypen(true)); + } + [0; 4] + } + + #[cfg(cryp_v3)] + fn pre_final_block(&self, p: &pac::cryp::Cryp, _dir: Direction, padding_len: usize) -> [u32; 4] { + //Handle special GCM partial block process. + p.cr().modify(|w| w.set_npblb(padding_len as u8)); + [0; 4] + } + + #[cfg(cryp_v2)] + fn post_final_block( + &self, + p: &pac::cryp::Cryp, + dir: Direction, + int_data: &mut [u8; AES_BLOCK_SIZE], + _temp1: [u32; 4], + padding_mask: [u8; AES_BLOCK_SIZE], + ) { + if dir == Direction::Encrypt { + //Handle special GCM partial block process. + p.cr().modify(|w| w.set_crypen(false)); + p.cr().modify(|w| w.set_algomode3(true)); + p.cr().modify(|w| w.set_algomode0(0)); + for i in 0..AES_BLOCK_SIZE { + int_data[i] = int_data[i] & padding_mask[i]; + } + p.cr().modify(|w| w.set_crypen(true)); + p.cr().modify(|w| w.set_gcm_ccmph(3)); + let mut index = 0; + let end_index = Self::BLOCK_SIZE; + while index < end_index { + let mut in_word: [u8; 4] = [0; 4]; + in_word.copy_from_slice(&int_data[index..index + 4]); + p.din().write_value(u32::from_ne_bytes(in_word)); + index += 4; + } + for _ in 0..4 { + p.dout().read(); + } + } + } +} + +#[cfg(any(cryp_v2, cryp_v3))] +impl<'c> CipherSized for AesGcm<'c, { 128 / 8 }> {} +#[cfg(any(cryp_v2, cryp_v3))] +impl<'c> CipherSized for AesGcm<'c, { 192 / 8 }> {} +#[cfg(any(cryp_v2, cryp_v3))] +impl<'c> CipherSized for AesGcm<'c, { 256 / 8 }> {} +#[cfg(any(cryp_v2, cryp_v3))] +impl<'c, const KEY_SIZE: usize> CipherAuthenticated<16> for AesGcm<'c, KEY_SIZE> {} +#[cfg(any(cryp_v2, cryp_v3))] +impl<'c, const KEY_SIZE: usize> IVSized for AesGcm<'c, KEY_SIZE> {} + +#[cfg(any(cryp_v2, cryp_v3))] +/// AES-GMAC Cipher Mode +pub struct AesGmac<'c, const KEY_SIZE: usize> { + iv: [u8; 16], + key: &'c [u8; KEY_SIZE], +} + +#[cfg(any(cryp_v2, cryp_v3))] +impl<'c, const KEY_SIZE: usize> AesGmac<'c, KEY_SIZE> { + /// Constructs a new AES-GMAC cipher for a cryptographic operation. + pub fn new(key: &'c [u8; KEY_SIZE], iv: &'c [u8; 12]) -> Self { + let mut new_gmac = Self { key: key, iv: [0; 16] }; + new_gmac.iv[..12].copy_from_slice(iv); + new_gmac.iv[15] = 2; + new_gmac + } +} + +#[cfg(any(cryp_v2, cryp_v3))] +impl<'c, const KEY_SIZE: usize> Cipher<'c> for AesGmac<'c, KEY_SIZE> { + const BLOCK_SIZE: usize = AES_BLOCK_SIZE; + + fn key(&self) -> &'c [u8] { + self.key + } + + fn iv(&self) -> &[u8] { + self.iv.as_slice() + } + + fn set_algomode(&self, p: &pac::cryp::Cryp) { + p.cr().modify(|w| w.set_algomode0(0)); + p.cr().modify(|w| w.set_algomode3(true)); + } + + fn init_phase(&self, p: &pac::cryp::Cryp) { + p.cr().modify(|w| w.set_gcm_ccmph(0)); + p.cr().modify(|w| w.set_crypen(true)); + while p.cr().read().crypen() {} + } + + #[cfg(cryp_v2)] + fn pre_final_block(&self, p: &pac::cryp::Cryp, dir: Direction, _padding_len: usize) -> [u32; 4] { + //Handle special GCM partial block process. + if dir == Direction::Encrypt { + p.cr().modify(|w| w.set_crypen(false)); + p.cr().modify(|w| w.set_algomode3(false)); + p.cr().modify(|w| w.set_algomode0(6)); + let iv1r = p.csgcmccmr(7).read() - 1; + p.init(1).ivrr().write_value(iv1r); + p.cr().modify(|w| w.set_crypen(true)); + } + [0; 4] + } + + #[cfg(cryp_v3)] + fn pre_final_block(&self, p: &pac::cryp::Cryp, _dir: Direction, padding_len: usize) -> [u32; 4] { + //Handle special GCM partial block process. + p.cr().modify(|w| w.set_npblb(padding_len as u8)); + [0; 4] + } + + #[cfg(cryp_v2)] + fn post_final_block( + &self, + p: &pac::cryp::Cryp, + dir: Direction, + int_data: &mut [u8; AES_BLOCK_SIZE], + _temp1: [u32; 4], + padding_mask: [u8; AES_BLOCK_SIZE], + ) { + if dir == Direction::Encrypt { + //Handle special GCM partial block process. + p.cr().modify(|w| w.set_crypen(false)); + p.cr().modify(|w| w.set_algomode3(true)); + p.cr().modify(|w| w.set_algomode0(0)); + for i in 0..AES_BLOCK_SIZE { + int_data[i] = int_data[i] & padding_mask[i]; + } + p.cr().modify(|w| w.set_crypen(true)); + p.cr().modify(|w| w.set_gcm_ccmph(3)); + let mut index = 0; + let end_index = Self::BLOCK_SIZE; + while index < end_index { + let mut in_word: [u8; 4] = [0; 4]; + in_word.copy_from_slice(&int_data[index..index + 4]); + p.din().write_value(u32::from_ne_bytes(in_word)); + index += 4; + } + for _ in 0..4 { + p.dout().read(); + } + } + } +} + +#[cfg(any(cryp_v2, cryp_v3))] +impl<'c> CipherSized for AesGmac<'c, { 128 / 8 }> {} +#[cfg(any(cryp_v2, cryp_v3))] +impl<'c> CipherSized for AesGmac<'c, { 192 / 8 }> {} +#[cfg(any(cryp_v2, cryp_v3))] +impl<'c> CipherSized for AesGmac<'c, { 256 / 8 }> {} +#[cfg(any(cryp_v2, cryp_v3))] +impl<'c, const KEY_SIZE: usize> CipherAuthenticated<16> for AesGmac<'c, KEY_SIZE> {} +#[cfg(any(cryp_v2, cryp_v3))] +impl<'c, const KEY_SIZE: usize> IVSized for AesGmac<'c, KEY_SIZE> {} + +#[cfg(any(cryp_v2, cryp_v3))] +/// AES-CCM Cipher Mode +pub struct AesCcm<'c, const KEY_SIZE: usize, const TAG_SIZE: usize, const IV_SIZE: usize> { + key: &'c [u8; KEY_SIZE], + aad_header: [u8; 6], + aad_header_len: usize, + block0: [u8; 16], + ctr: [u8; 16], +} + +#[cfg(any(cryp_v2, cryp_v3))] +impl<'c, const KEY_SIZE: usize, const TAG_SIZE: usize, const IV_SIZE: usize> AesCcm<'c, KEY_SIZE, TAG_SIZE, IV_SIZE> { + /// Constructs a new AES-CCM cipher for a cryptographic operation. + pub fn new(key: &'c [u8; KEY_SIZE], iv: &'c [u8; IV_SIZE], aad_len: usize, payload_len: usize) -> Self { + let mut aad_header: [u8; 6] = [0; 6]; + let mut aad_header_len = 0; + let mut block0: [u8; 16] = [0; 16]; + if aad_len != 0 { + if aad_len < 65280 { + aad_header[0] = (aad_len >> 8) as u8 & 0xFF; + aad_header[1] = aad_len as u8 & 0xFF; + aad_header_len = 2; + } else { + aad_header[0] = 0xFF; + aad_header[1] = 0xFE; + let aad_len_bytes: [u8; 4] = aad_len.to_be_bytes(); + aad_header[2] = aad_len_bytes[0]; + aad_header[3] = aad_len_bytes[1]; + aad_header[4] = aad_len_bytes[2]; + aad_header[5] = aad_len_bytes[3]; + aad_header_len = 6; + } + } + let total_aad_len = aad_header_len + aad_len; + let mut aad_padding_len = 16 - (total_aad_len % 16); + if aad_padding_len == 16 { + aad_padding_len = 0; + } + aad_header_len += aad_padding_len; + let total_aad_len_padded = aad_header_len + aad_len; + if total_aad_len_padded > 0 { + block0[0] = 0x40; + } + block0[0] |= ((((TAG_SIZE as u8) - 2) >> 1) & 0x07) << 3; + block0[0] |= ((15 - (iv.len() as u8)) - 1) & 0x07; + block0[1..1 + iv.len()].copy_from_slice(iv); + let payload_len_bytes: [u8; 4] = payload_len.to_be_bytes(); + if iv.len() <= 11 { + block0[12] = payload_len_bytes[0]; + } else if payload_len_bytes[0] > 0 { + panic!("Message is too large for given IV size."); + } + if iv.len() <= 12 { + block0[13] = payload_len_bytes[1]; + } else if payload_len_bytes[1] > 0 { + panic!("Message is too large for given IV size."); + } + block0[14] = payload_len_bytes[2]; + block0[15] = payload_len_bytes[3]; + let mut ctr: [u8; 16] = [0; 16]; + ctr[0] = block0[0] & 0x07; + ctr[1..1 + iv.len()].copy_from_slice(&block0[1..1 + iv.len()]); + ctr[15] = 0x01; + + return Self { + key: key, + aad_header: aad_header, + aad_header_len: aad_header_len, + block0: block0, + ctr: ctr, + }; + } +} + +#[cfg(any(cryp_v2, cryp_v3))] +impl<'c, const KEY_SIZE: usize, const TAG_SIZE: usize, const IV_SIZE: usize> Cipher<'c> + for AesCcm<'c, KEY_SIZE, TAG_SIZE, IV_SIZE> +{ + const BLOCK_SIZE: usize = AES_BLOCK_SIZE; + + fn key(&self) -> &'c [u8] { + self.key + } + + fn iv(&self) -> &[u8] { + self.ctr.as_slice() + } + + fn set_algomode(&self, p: &pac::cryp::Cryp) { + p.cr().modify(|w| w.set_algomode0(1)); + p.cr().modify(|w| w.set_algomode3(true)); + } + + fn init_phase(&self, p: &pac::cryp::Cryp) { + p.cr().modify(|w| w.set_gcm_ccmph(0)); + + let mut index = 0; + let end_index = index + Self::BLOCK_SIZE; + // Write block in + while index < end_index { + let mut in_word: [u8; 4] = [0; 4]; + in_word.copy_from_slice(&self.block0[index..index + 4]); + p.din().write_value(u32::from_ne_bytes(in_word)); + index += 4; + } + p.cr().modify(|w| w.set_crypen(true)); + while p.cr().read().crypen() {} + } + + fn get_header_block(&self) -> &[u8] { + return &self.aad_header[0..self.aad_header_len]; + } + + #[cfg(cryp_v2)] + fn pre_final_block(&self, p: &pac::cryp::Cryp, dir: Direction, _padding_len: usize) -> [u32; 4] { + //Handle special CCM partial block process. + let mut temp1 = [0; 4]; + if dir == Direction::Decrypt { + p.cr().modify(|w| w.set_crypen(false)); + let iv1temp = p.init(1).ivrr().read(); + temp1[0] = p.csgcmccmr(0).read().swap_bytes(); + temp1[1] = p.csgcmccmr(1).read().swap_bytes(); + temp1[2] = p.csgcmccmr(2).read().swap_bytes(); + temp1[3] = p.csgcmccmr(3).read().swap_bytes(); + p.init(1).ivrr().write_value(iv1temp); + p.cr().modify(|w| w.set_algomode3(false)); + p.cr().modify(|w| w.set_algomode0(6)); + p.cr().modify(|w| w.set_crypen(true)); + } + return temp1; + } + + #[cfg(cryp_v3)] + fn pre_final_block(&self, p: &pac::cryp::Cryp, _dir: Direction, padding_len: usize) -> [u32; 4] { + //Handle special GCM partial block process. + p.cr().modify(|w| w.set_npblb(padding_len as u8)); + [0; 4] + } + + #[cfg(cryp_v2)] + fn post_final_block( + &self, + p: &pac::cryp::Cryp, + dir: Direction, + int_data: &mut [u8; AES_BLOCK_SIZE], + temp1: [u32; 4], + padding_mask: [u8; 16], + ) { + if dir == Direction::Decrypt { + //Handle special CCM partial block process. + let mut temp2 = [0; 4]; + temp2[0] = p.csgcmccmr(0).read().swap_bytes(); + temp2[1] = p.csgcmccmr(1).read().swap_bytes(); + temp2[2] = p.csgcmccmr(2).read().swap_bytes(); + temp2[3] = p.csgcmccmr(3).read().swap_bytes(); + p.cr().modify(|w| w.set_algomode3(true)); + p.cr().modify(|w| w.set_algomode0(1)); + p.cr().modify(|w| w.set_gcm_ccmph(3)); + // Header phase + p.cr().modify(|w| w.set_gcm_ccmph(1)); + for i in 0..AES_BLOCK_SIZE { + int_data[i] = int_data[i] & padding_mask[i]; + } + let mut in_data: [u32; 4] = [0; 4]; + for i in 0..in_data.len() { + let mut int_bytes: [u8; 4] = [0; 4]; + int_bytes.copy_from_slice(&int_data[(i * 4)..(i * 4) + 4]); + let int_word = u32::from_le_bytes(int_bytes); + in_data[i] = int_word; + in_data[i] = in_data[i] ^ temp1[i] ^ temp2[i]; + p.din().write_value(in_data[i]); + } + } + } +} + +#[cfg(any(cryp_v2, cryp_v3))] +impl<'c, const TAG_SIZE: usize, const IV_SIZE: usize> CipherSized for AesCcm<'c, { 128 / 8 }, TAG_SIZE, IV_SIZE> {} +#[cfg(any(cryp_v2, cryp_v3))] +impl<'c, const TAG_SIZE: usize, const IV_SIZE: usize> CipherSized for AesCcm<'c, { 192 / 8 }, TAG_SIZE, IV_SIZE> {} +#[cfg(any(cryp_v2, cryp_v3))] +impl<'c, const TAG_SIZE: usize, const IV_SIZE: usize> CipherSized for AesCcm<'c, { 256 / 8 }, TAG_SIZE, IV_SIZE> {} +#[cfg(any(cryp_v2, cryp_v3))] +impl<'c, const KEY_SIZE: usize, const IV_SIZE: usize> CipherAuthenticated<4> for AesCcm<'c, KEY_SIZE, 4, IV_SIZE> {} +#[cfg(any(cryp_v2, cryp_v3))] +impl<'c, const KEY_SIZE: usize, const IV_SIZE: usize> CipherAuthenticated<6> for AesCcm<'c, KEY_SIZE, 6, IV_SIZE> {} +#[cfg(any(cryp_v2, cryp_v3))] +impl<'c, const KEY_SIZE: usize, const IV_SIZE: usize> CipherAuthenticated<8> for AesCcm<'c, KEY_SIZE, 8, IV_SIZE> {} +#[cfg(any(cryp_v2, cryp_v3))] +impl<'c, const KEY_SIZE: usize, const IV_SIZE: usize> CipherAuthenticated<10> for AesCcm<'c, KEY_SIZE, 10, IV_SIZE> {} +#[cfg(any(cryp_v2, cryp_v3))] +impl<'c, const KEY_SIZE: usize, const IV_SIZE: usize> CipherAuthenticated<12> for AesCcm<'c, KEY_SIZE, 12, IV_SIZE> {} +#[cfg(any(cryp_v2, cryp_v3))] +impl<'c, const KEY_SIZE: usize, const IV_SIZE: usize> CipherAuthenticated<14> for AesCcm<'c, KEY_SIZE, 14, IV_SIZE> {} +#[cfg(any(cryp_v2, cryp_v3))] +impl<'c, const KEY_SIZE: usize, const IV_SIZE: usize> CipherAuthenticated<16> for AesCcm<'c, KEY_SIZE, 16, IV_SIZE> {} +#[cfg(any(cryp_v2, cryp_v3))] +impl<'c, const KEY_SIZE: usize, const TAG_SIZE: usize> IVSized for AesCcm<'c, KEY_SIZE, TAG_SIZE, 7> {} +#[cfg(any(cryp_v2, cryp_v3))] +impl<'c, const KEY_SIZE: usize, const TAG_SIZE: usize> IVSized for AesCcm<'c, KEY_SIZE, TAG_SIZE, 8> {} +#[cfg(any(cryp_v2, cryp_v3))] +impl<'c, const KEY_SIZE: usize, const TAG_SIZE: usize> IVSized for AesCcm<'c, KEY_SIZE, TAG_SIZE, 9> {} +#[cfg(any(cryp_v2, cryp_v3))] +impl<'c, const KEY_SIZE: usize, const TAG_SIZE: usize> IVSized for AesCcm<'c, KEY_SIZE, TAG_SIZE, 10> {} +#[cfg(any(cryp_v2, cryp_v3))] +impl<'c, const KEY_SIZE: usize, const TAG_SIZE: usize> IVSized for AesCcm<'c, KEY_SIZE, TAG_SIZE, 11> {} +#[cfg(any(cryp_v2, cryp_v3))] +impl<'c, const KEY_SIZE: usize, const TAG_SIZE: usize> IVSized for AesCcm<'c, KEY_SIZE, TAG_SIZE, 12> {} +#[cfg(any(cryp_v2, cryp_v3))] +impl<'c, const KEY_SIZE: usize, const TAG_SIZE: usize> IVSized for AesCcm<'c, KEY_SIZE, TAG_SIZE, 13> {} + +#[allow(dead_code)] +/// Holds the state information for a cipher operation. +/// Allows suspending/resuming of cipher operations. +pub struct Context<'c, C: Cipher<'c> + CipherSized> { + phantom_data: PhantomData<&'c C>, + cipher: &'c C, + dir: Direction, + last_block_processed: bool, + header_processed: bool, + aad_complete: bool, + cr: u32, + iv: [u32; 4], + csgcmccm: [u32; 8], + csgcm: [u32; 8], + header_len: u64, + payload_len: u64, + aad_buffer: [u8; 16], + aad_buffer_len: usize, +} + +/// Selects whether the crypto processor operates in encryption or decryption mode. +#[derive(PartialEq, Clone, Copy)] +pub enum Direction { + /// Encryption mode + Encrypt, + /// Decryption mode + Decrypt, +} + +/// Crypto Accelerator Driver +pub struct Cryp<'d, T: Instance> { + _peripheral: PeripheralRef<'d, T>, +} + +impl<'d, T: Instance> Cryp<'d, T> { + /// Create a new CRYP driver. + pub fn new(peri: impl Peripheral<P = T> + 'd) -> Self { + T::enable_and_reset(); + into_ref!(peri); + let instance = Self { _peripheral: peri }; + instance + } + + /// Start a new cipher operation. + /// Key size must be 128, 192, or 256 bits. + /// Initialization vector must only be supplied if necessary. + /// Panics if there is any mismatch in parameters, such as an incorrect IV length or invalid mode. + pub fn start<'c, C: Cipher<'c> + CipherSized + IVSized>(&self, cipher: &'c C, dir: Direction) -> Context<'c, C> { + let mut ctx: Context<'c, C> = Context { + dir, + last_block_processed: false, + cr: 0, + iv: [0; 4], + csgcmccm: [0; 8], + csgcm: [0; 8], + aad_complete: false, + header_len: 0, + payload_len: 0, + cipher: cipher, + phantom_data: PhantomData, + header_processed: false, + aad_buffer: [0; 16], + aad_buffer_len: 0, + }; + + T::regs().cr().modify(|w| w.set_crypen(false)); + + let key = ctx.cipher.key(); + + if key.len() == (128 / 8) { + T::regs().cr().modify(|w| w.set_keysize(0)); + } else if key.len() == (192 / 8) { + T::regs().cr().modify(|w| w.set_keysize(1)); + } else if key.len() == (256 / 8) { + T::regs().cr().modify(|w| w.set_keysize(2)); + } + + self.load_key(key); + + // Set data type to 8-bit. This will match software implementations. + T::regs().cr().modify(|w| w.set_datatype(2)); + + ctx.cipher.prepare_key(&T::regs()); + + ctx.cipher.set_algomode(&T::regs()); + + // Set encrypt/decrypt + if dir == Direction::Encrypt { + T::regs().cr().modify(|w| w.set_algodir(false)); + } else { + T::regs().cr().modify(|w| w.set_algodir(true)); + } + + // Load the IV into the registers. + let iv = ctx.cipher.iv(); + let mut full_iv: [u8; 16] = [0; 16]; + full_iv[0..iv.len()].copy_from_slice(iv); + let mut iv_idx = 0; + let mut iv_word: [u8; 4] = [0; 4]; + iv_word.copy_from_slice(&full_iv[iv_idx..iv_idx + 4]); + iv_idx += 4; + T::regs().init(0).ivlr().write_value(u32::from_be_bytes(iv_word)); + iv_word.copy_from_slice(&full_iv[iv_idx..iv_idx + 4]); + iv_idx += 4; + T::regs().init(0).ivrr().write_value(u32::from_be_bytes(iv_word)); + iv_word.copy_from_slice(&full_iv[iv_idx..iv_idx + 4]); + iv_idx += 4; + T::regs().init(1).ivlr().write_value(u32::from_be_bytes(iv_word)); + iv_word.copy_from_slice(&full_iv[iv_idx..iv_idx + 4]); + T::regs().init(1).ivrr().write_value(u32::from_be_bytes(iv_word)); + + // Flush in/out FIFOs + T::regs().cr().modify(|w| w.fflush()); + + ctx.cipher.init_phase(&T::regs()); + + self.store_context(&mut ctx); + + ctx + } + + #[cfg(any(cryp_v2, cryp_v3))] + /// Controls the header phase of cipher processing. + /// This function is only valid for GCM, CCM, and GMAC modes. + /// It only needs to be called if using one of these modes and there is associated data. + /// All AAD must be supplied to this function prior to starting the payload phase with `payload_blocking`. + /// The AAD must be supplied in multiples of the block size (128 bits), except when supplying the last block. + /// When supplying the last block of AAD, `last_aad_block` must be `true`. + pub fn aad_blocking< + 'c, + const TAG_SIZE: usize, + C: Cipher<'c> + CipherSized + IVSized + CipherAuthenticated<TAG_SIZE>, + >( + &self, + ctx: &mut Context<'c, C>, + aad: &[u8], + last_aad_block: bool, + ) { + self.load_context(ctx); + + // Perform checks for correctness. + if ctx.aad_complete { + panic!("Cannot update AAD after starting payload!") + } + + ctx.header_len += aad.len() as u64; + + // Header phase + T::regs().cr().modify(|w| w.set_crypen(false)); + T::regs().cr().modify(|w| w.set_gcm_ccmph(1)); + T::regs().cr().modify(|w| w.set_crypen(true)); + + // First write the header B1 block if not yet written. + if !ctx.header_processed { + ctx.header_processed = true; + let header = ctx.cipher.get_header_block(); + ctx.aad_buffer[0..header.len()].copy_from_slice(header); + ctx.aad_buffer_len += header.len(); + } + + // Fill the header block to make a full block. + let len_to_copy = min(aad.len(), C::BLOCK_SIZE - ctx.aad_buffer_len); + ctx.aad_buffer[ctx.aad_buffer_len..ctx.aad_buffer_len + len_to_copy].copy_from_slice(&aad[..len_to_copy]); + ctx.aad_buffer_len += len_to_copy; + ctx.aad_buffer[ctx.aad_buffer_len..].fill(0); + let mut aad_len_remaining = aad.len() - len_to_copy; + + if ctx.aad_buffer_len < C::BLOCK_SIZE { + // The buffer isn't full and this is the last buffer, so process it as is (already padded). + if last_aad_block { + let mut index = 0; + let end_index = C::BLOCK_SIZE; + // Write block in + while index < end_index { + let mut in_word: [u8; 4] = [0; 4]; + in_word.copy_from_slice(&ctx.aad_buffer[index..index + 4]); + T::regs().din().write_value(u32::from_ne_bytes(in_word)); + index += 4; + } + // Block until input FIFO is empty. + while !T::regs().sr().read().ifem() {} + + // Switch to payload phase. + ctx.aad_complete = true; + T::regs().cr().modify(|w| w.set_crypen(false)); + T::regs().cr().modify(|w| w.set_gcm_ccmph(2)); + T::regs().cr().modify(|w| w.fflush()); + } else { + // Just return because we don't yet have a full block to process. + return; + } + } else { + // Load the full block from the buffer. + let mut index = 0; + let end_index = C::BLOCK_SIZE; + // Write block in + while index < end_index { + let mut in_word: [u8; 4] = [0; 4]; + in_word.copy_from_slice(&ctx.aad_buffer[index..index + 4]); + T::regs().din().write_value(u32::from_ne_bytes(in_word)); + index += 4; + } + // Block until input FIFO is empty. + while !T::regs().sr().read().ifem() {} + } + + // Handle a partial block that is passed in. + ctx.aad_buffer_len = 0; + let leftovers = aad_len_remaining % C::BLOCK_SIZE; + ctx.aad_buffer[..leftovers].copy_from_slice(&aad[aad.len() - leftovers..aad.len()]); + ctx.aad_buffer_len += leftovers; + ctx.aad_buffer[ctx.aad_buffer_len..].fill(0); + aad_len_remaining -= leftovers; + assert_eq!(aad_len_remaining % C::BLOCK_SIZE, 0); + + // Load full data blocks into core. + let num_full_blocks = aad_len_remaining / C::BLOCK_SIZE; + for block in 0..num_full_blocks { + let mut index = len_to_copy + (block * C::BLOCK_SIZE); + let end_index = index + C::BLOCK_SIZE; + // Write block in + while index < end_index { + let mut in_word: [u8; 4] = [0; 4]; + in_word.copy_from_slice(&aad[index..index + 4]); + T::regs().din().write_value(u32::from_ne_bytes(in_word)); + index += 4; + } + // Block until input FIFO is empty. + while !T::regs().sr().read().ifem() {} + } + + if last_aad_block { + if leftovers > 0 { + let mut index = 0; + let end_index = C::BLOCK_SIZE; + // Write block in + while index < end_index { + let mut in_word: [u8; 4] = [0; 4]; + in_word.copy_from_slice(&ctx.aad_buffer[index..index + 4]); + T::regs().din().write_value(u32::from_ne_bytes(in_word)); + index += 4; + } + // Block until input FIFO is empty. + while !T::regs().sr().read().ifem() {} + } + // Switch to payload phase. + ctx.aad_complete = true; + T::regs().cr().modify(|w| w.set_crypen(false)); + T::regs().cr().modify(|w| w.set_gcm_ccmph(2)); + T::regs().cr().modify(|w| w.fflush()); + } + + self.store_context(ctx); + } + + /// Performs encryption/decryption on the provided context. + /// The context determines algorithm, mode, and state of the crypto accelerator. + /// When the last piece of data is supplied, `last_block` should be `true`. + /// This function panics under various mismatches of parameters. + /// Input and output buffer lengths must match. + /// Data must be a multiple of block size (128-bits for AES, 64-bits for DES) for CBC and ECB modes. + /// Padding or ciphertext stealing must be managed by the application for these modes. + /// Data must also be a multiple of block size unless `last_block` is `true`. + pub fn payload_blocking<'c, C: Cipher<'c> + CipherSized + IVSized>( + &self, + ctx: &mut Context<'c, C>, + input: &[u8], + output: &mut [u8], + last_block: bool, + ) { + self.load_context(ctx); + + let last_block_remainder = input.len() % C::BLOCK_SIZE; + + // Perform checks for correctness. + if !ctx.aad_complete && ctx.header_len > 0 { + panic!("Additional associated data must be processed first!"); + } else if !ctx.aad_complete { + #[cfg(any(cryp_v2, cryp_v3))] + { + ctx.aad_complete = true; + T::regs().cr().modify(|w| w.set_crypen(false)); + T::regs().cr().modify(|w| w.set_gcm_ccmph(2)); + T::regs().cr().modify(|w| w.fflush()); + T::regs().cr().modify(|w| w.set_crypen(true)); + } + } + if ctx.last_block_processed { + panic!("The last block has already been processed!"); + } + if input.len() > output.len() { + panic!("Output buffer length must match input length."); + } + if !last_block { + if last_block_remainder != 0 { + panic!("Input length must be a multiple of {} bytes.", C::BLOCK_SIZE); + } + } + if C::REQUIRES_PADDING { + if last_block_remainder != 0 { + panic!("Input must be a multiple of {} bytes in ECB and CBC modes. Consider padding or ciphertext stealing.", C::BLOCK_SIZE); + } + } + if last_block { + ctx.last_block_processed = true; + } + + // Load data into core, block by block. + let num_full_blocks = input.len() / C::BLOCK_SIZE; + for block in 0..num_full_blocks { + let mut index = block * C::BLOCK_SIZE; + let end_index = index + C::BLOCK_SIZE; + // Write block in + while index < end_index { + let mut in_word: [u8; 4] = [0; 4]; + in_word.copy_from_slice(&input[index..index + 4]); + T::regs().din().write_value(u32::from_ne_bytes(in_word)); + index += 4; + } + let mut index = block * C::BLOCK_SIZE; + let end_index = index + C::BLOCK_SIZE; + // Block until there is output to read. + while !T::regs().sr().read().ofne() {} + // Read block out + while index < end_index { + let out_word: u32 = T::regs().dout().read(); + output[index..index + 4].copy_from_slice(u32::to_ne_bytes(out_word).as_slice()); + index += 4; + } + } + + // Handle the final block, which is incomplete. + if last_block_remainder > 0 { + let padding_len = C::BLOCK_SIZE - last_block_remainder; + let temp1 = ctx.cipher.pre_final_block(&T::regs(), ctx.dir, padding_len); + + let mut intermediate_data: [u8; AES_BLOCK_SIZE] = [0; AES_BLOCK_SIZE]; + let mut last_block: [u8; AES_BLOCK_SIZE] = [0; AES_BLOCK_SIZE]; + last_block[..last_block_remainder].copy_from_slice(&input[input.len() - last_block_remainder..input.len()]); + let mut index = 0; + let end_index = C::BLOCK_SIZE; + // Write block in + while index < end_index { + let mut in_word: [u8; 4] = [0; 4]; + in_word.copy_from_slice(&last_block[index..index + 4]); + T::regs().din().write_value(u32::from_ne_bytes(in_word)); + index += 4; + } + let mut index = 0; + let end_index = C::BLOCK_SIZE; + // Block until there is output to read. + while !T::regs().sr().read().ofne() {} + // Read block out + while index < end_index { + let out_word: u32 = T::regs().dout().read(); + intermediate_data[index..index + 4].copy_from_slice(u32::to_ne_bytes(out_word).as_slice()); + index += 4; + } + + // Handle the last block depending on mode. + let output_len = output.len(); + output[output_len - last_block_remainder..output_len] + .copy_from_slice(&intermediate_data[0..last_block_remainder]); + + let mut mask: [u8; 16] = [0; 16]; + mask[..last_block_remainder].fill(0xFF); + ctx.cipher + .post_final_block(&T::regs(), ctx.dir, &mut intermediate_data, temp1, mask); + } + + ctx.payload_len += input.len() as u64; + + self.store_context(ctx); + } + + #[cfg(any(cryp_v2, cryp_v3))] + /// This function only needs to be called for GCM, CCM, and GMAC modes to + /// generate an authentication tag. + pub fn finish_blocking< + 'c, + const TAG_SIZE: usize, + C: Cipher<'c> + CipherSized + IVSized + CipherAuthenticated<TAG_SIZE>, + >( + &self, + mut ctx: Context<'c, C>, + ) -> [u8; TAG_SIZE] { + self.load_context(&mut ctx); + + T::regs().cr().modify(|w| w.set_crypen(false)); + T::regs().cr().modify(|w| w.set_gcm_ccmph(3)); + T::regs().cr().modify(|w| w.set_crypen(true)); + + let headerlen1: u32 = ((ctx.header_len * 8) >> 32) as u32; + let headerlen2: u32 = (ctx.header_len * 8) as u32; + let payloadlen1: u32 = ((ctx.payload_len * 8) >> 32) as u32; + let payloadlen2: u32 = (ctx.payload_len * 8) as u32; + + #[cfg(cryp_v2)] + { + T::regs().din().write_value(headerlen1.swap_bytes()); + T::regs().din().write_value(headerlen2.swap_bytes()); + T::regs().din().write_value(payloadlen1.swap_bytes()); + T::regs().din().write_value(payloadlen2.swap_bytes()); + } + + #[cfg(cryp_v3)] + { + T::regs().din().write_value(headerlen1); + T::regs().din().write_value(headerlen2); + T::regs().din().write_value(payloadlen1); + T::regs().din().write_value(payloadlen2); + } + + while !T::regs().sr().read().ofne() {} + + let mut full_tag: [u8; 16] = [0; 16]; + full_tag[0..4].copy_from_slice(T::regs().dout().read().to_ne_bytes().as_slice()); + full_tag[4..8].copy_from_slice(T::regs().dout().read().to_ne_bytes().as_slice()); + full_tag[8..12].copy_from_slice(T::regs().dout().read().to_ne_bytes().as_slice()); + full_tag[12..16].copy_from_slice(T::regs().dout().read().to_ne_bytes().as_slice()); + let mut tag: [u8; TAG_SIZE] = [0; TAG_SIZE]; + tag.copy_from_slice(&full_tag[0..TAG_SIZE]); + + T::regs().cr().modify(|w| w.set_crypen(false)); + + tag + } + + fn load_key(&self, key: &[u8]) { + // Load the key into the registers. + let mut keyidx = 0; + let mut keyword: [u8; 4] = [0; 4]; + let keylen = key.len() * 8; + if keylen > 192 { + keyword.copy_from_slice(&key[keyidx..keyidx + 4]); + keyidx += 4; + T::regs().key(0).klr().write_value(u32::from_be_bytes(keyword)); + keyword.copy_from_slice(&key[keyidx..keyidx + 4]); + keyidx += 4; + T::regs().key(0).krr().write_value(u32::from_be_bytes(keyword)); + } + if keylen > 128 { + keyword.copy_from_slice(&key[keyidx..keyidx + 4]); + keyidx += 4; + T::regs().key(1).klr().write_value(u32::from_be_bytes(keyword)); + keyword.copy_from_slice(&key[keyidx..keyidx + 4]); + keyidx += 4; + T::regs().key(1).krr().write_value(u32::from_be_bytes(keyword)); + } + if keylen > 64 { + keyword.copy_from_slice(&key[keyidx..keyidx + 4]); + keyidx += 4; + T::regs().key(2).klr().write_value(u32::from_be_bytes(keyword)); + keyword.copy_from_slice(&key[keyidx..keyidx + 4]); + keyidx += 4; + T::regs().key(2).krr().write_value(u32::from_be_bytes(keyword)); + } + keyword.copy_from_slice(&key[keyidx..keyidx + 4]); + keyidx += 4; + T::regs().key(3).klr().write_value(u32::from_be_bytes(keyword)); + keyword = [0; 4]; + keyword[0..key.len() - keyidx].copy_from_slice(&key[keyidx..key.len()]); + T::regs().key(3).krr().write_value(u32::from_be_bytes(keyword)); + } + + fn store_context<'c, C: Cipher<'c> + CipherSized>(&self, ctx: &mut Context<'c, C>) { + // Wait for data block processing to finish. + while !T::regs().sr().read().ifem() {} + while T::regs().sr().read().ofne() {} + while T::regs().sr().read().busy() {} + + // Disable crypto processor. + T::regs().cr().modify(|w| w.set_crypen(false)); + + // Save the peripheral state. + ctx.cr = T::regs().cr().read().0; + ctx.iv[0] = T::regs().init(0).ivlr().read(); + ctx.iv[1] = T::regs().init(0).ivrr().read(); + ctx.iv[2] = T::regs().init(1).ivlr().read(); + ctx.iv[3] = T::regs().init(1).ivrr().read(); + + #[cfg(any(cryp_v2, cryp_v3))] + for i in 0..8 { + ctx.csgcmccm[i] = T::regs().csgcmccmr(i).read(); + ctx.csgcm[i] = T::regs().csgcmr(i).read(); + } + } + + fn load_context<'c, C: Cipher<'c> + CipherSized>(&self, ctx: &Context<'c, C>) { + // Reload state registers. + T::regs().cr().write(|w| w.0 = ctx.cr); + T::regs().init(0).ivlr().write_value(ctx.iv[0]); + T::regs().init(0).ivrr().write_value(ctx.iv[1]); + T::regs().init(1).ivlr().write_value(ctx.iv[2]); + T::regs().init(1).ivrr().write_value(ctx.iv[3]); + + #[cfg(any(cryp_v2, cryp_v3))] + for i in 0..8 { + T::regs().csgcmccmr(i).write_value(ctx.csgcmccm[i]); + T::regs().csgcmr(i).write_value(ctx.csgcm[i]); + } + self.load_key(ctx.cipher.key()); + + // Prepare key if applicable. + ctx.cipher.prepare_key(&T::regs()); + T::regs().cr().write(|w| w.0 = ctx.cr); + + // Enable crypto processor. + T::regs().cr().modify(|w| w.set_crypen(true)); + } +} + +pub(crate) mod sealed { + use super::*; + + pub trait Instance { + fn regs() -> pac::cryp::Cryp; + } +} + +/// CRYP instance trait. +pub trait Instance: sealed::Instance + Peripheral<P = Self> + crate::rcc::RccPeripheral + 'static + Send { + /// Interrupt for this CRYP instance. + type Interrupt: interrupt::typelevel::Interrupt; +} + +foreach_interrupt!( + ($inst:ident, cryp, CRYP, GLOBAL, $irq:ident) => { + impl Instance for peripherals::$inst { + type Interrupt = crate::interrupt::typelevel::$irq; + } + + impl sealed::Instance for peripherals::$inst { + fn regs() -> crate::pac::cryp::Cryp { + crate::pac::$inst + } + } + }; +); diff --git a/embassy-stm32/src/dac/mod.rs b/embassy-stm32/src/dac/mod.rs index 31dedf06e..60f9404c2 100644 --- a/embassy-stm32/src/dac/mod.rs +++ b/embassy-stm32/src/dac/mod.rs @@ -504,29 +504,6 @@ pub trait DacPin<T: Instance, const C: u8>: 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(any(rcc_h7, rcc_h7rm0433))] - impl crate::rcc::sealed::RccPeripheral for peripherals::$inst { - fn frequency() -> crate::time::Hertz { - critical_section::with(|_| unsafe { crate::rcc::get_freqs().pclk1 }) - } - - fn enable_and_reset_with_cs(_cs: critical_section::CriticalSection) { - // TODO: Increment refcount? - crate::pac::RCC.apb1lrstr().modify(|w| w.set_dac12rst(true)); - crate::pac::RCC.apb1lrstr().modify(|w| w.set_dac12rst(false)); - crate::pac::RCC.apb1lenr().modify(|w| w.set_dac12en(true)); - } - - fn disable_with_cs(_cs: critical_section::CriticalSection) { - // TODO: Decrement refcount? - crate::pac::RCC.apb1lenr().modify(|w| w.set_dac12en(false)) - } - } - - #[cfg(any(rcc_h7, rcc_h7rm0433))] - 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 diff --git a/embassy-stm32/src/dcmi.rs b/embassy-stm32/src/dcmi.rs index 4d02284b2..826b04a4b 100644 --- a/embassy-stm32/src/dcmi.rs +++ b/embassy-stm32/src/dcmi.rs @@ -394,19 +394,7 @@ where /// This method starts the capture and finishes when both the dma transfer and DCMI finish the frame transfer. /// The implication is that the input buffer size must be exactly the size of the captured frame. - /// - /// Note that when `buffer.len() > 0xffff` the capture future requires some real-time guarantees to be upheld - /// (must be polled fast enough so the buffers get switched before data is overwritten). - /// It is therefore recommended that it is run on higher priority executor. pub async fn capture(&mut self, buffer: &mut [u32]) -> Result<(), Error> { - if buffer.len() <= 0xffff { - return self.capture_small(buffer).await; - } else { - return self.capture_giant(buffer).await; - } - } - - async fn capture_small(&mut self, buffer: &mut [u32]) -> Result<(), Error> { let r = self.inner.regs(); let src = r.dr().as_ptr() as *mut u32; let request = self.dma.request(); @@ -441,116 +429,6 @@ where 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; - - let data_len = buffer.len(); - let chunk_estimate = data_len / 0xffff; - - let mut chunks = chunk_estimate + 1; - while data_len % chunks != 0 { - chunks += 1; - } - - let chunk_size = data_len / chunks; - - let mut remaining_chunks = chunks - 2; - - let mut m0ar = buffer.as_mut_ptr(); - let mut m1ar = unsafe { buffer.as_mut_ptr().add(chunk_size) }; - - let channel = &mut self.dma; - let request = channel.request(); - - let r = self.inner.regs(); - let src = r.dr().as_ptr() as *mut u32; - - 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| { - transfer.set_waker(cx.waker()); - - let buffer0_currently_accessible = transfer.is_buffer0_accessible(); - - // check if the accessible buffer changed since last poll - if buffer0_last_accessible == buffer0_currently_accessible { - return Poll::Pending; - } - buffer0_last_accessible = !buffer0_last_accessible; - - if remaining_chunks != 0 { - if remaining_chunks % 2 == 0 && buffer0_currently_accessible { - m0ar = unsafe { m0ar.add(2 * chunk_size) }; - unsafe { transfer.set_buffer0(m0ar) } - remaining_chunks -= 1; - } else if !buffer0_currently_accessible { - m1ar = unsafe { m1ar.add(2 * chunk_size) }; - unsafe { transfer.set_buffer1(m1ar) }; - remaining_chunks -= 1; - } - } else { - if buffer0_currently_accessible { - unsafe { transfer.set_buffer0(buffer.as_mut_ptr()) } - } else { - unsafe { transfer.set_buffer1(buffer.as_mut_ptr()) } - } - if last_chunk_set_for_transfer { - transfer.request_stop(); - return Poll::Ready(()); - } - last_chunk_set_for_transfer = true; - } - Poll::Pending - }); - - Self::clear_interrupt_flags(); - Self::enable_irqs(); - - let result = poll_fn(|cx| { - STATE.waker.register(cx.waker()); - - let ris = crate::pac::DCMI.ris().read(); - if ris.err_ris() { - crate::pac::DCMI.icr().write(|r| r.set_err_isc(true)); - Poll::Ready(Err(Error::PeripheralError)) - } else if ris.ovr_ris() { - crate::pac::DCMI.icr().write(|r| r.set_ovr_isc(true)); - Poll::Ready(Err(Error::Overrun)) - } else if ris.frame_ris() { - crate::pac::DCMI.icr().write(|r| r.set_frame_isc(true)); - Poll::Ready(Ok(())) - } else { - Poll::Pending - } - }); - - Self::toggle(true); - - let (_, result) = embassy_futures::join::join(dma_result, result).await; - - Self::toggle(false); - - result - } } mod sealed { diff --git a/embassy-stm32/src/dma/bdma.rs b/embassy-stm32/src/dma/bdma.rs deleted file mode 100644 index a2b83716d..000000000 --- a/embassy-stm32/src/dma/bdma.rs +++ /dev/null @@ -1,733 +0,0 @@ -//! Basic Direct Memory Acccess (BDMA) - -use core::future::Future; -use core::pin::Pin; -use core::sync::atomic::{fence, AtomicUsize, Ordering}; -use core::task::{Context, Poll, Waker}; - -use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; -use embassy_sync::waitqueue::AtomicWaker; - -use super::ringbuffer::{DmaCtrl, OverrunError, ReadableDmaRingBuffer, WritableDmaRingBuffer}; -use super::word::{Word, WordSize}; -use super::Dir; -use crate::_generated::BDMA_CHANNEL_COUNT; -use crate::interrupt::typelevel::Interrupt; -use crate::interrupt::Priority; -use crate::pac; -use crate::pac::bdma::{regs, vals}; - -/// BDMA transfer options. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -#[non_exhaustive] -pub struct TransferOptions { - /// Enable circular DMA - /// - /// Note: - /// If you enable circular mode manually, you may want to build and `.await` the `Transfer` in a separate task. - /// Since DMA in circular mode need manually stop, `.await` in current task would block the task forever. - 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<WordSize> for vals::Size { - fn from(raw: WordSize) -> Self { - match raw { - WordSize::OneByte => Self::BITS8, - WordSize::TwoBytes => Self::BITS16, - WordSize::FourBytes => Self::BITS32, - } - } -} - -impl From<Dir> 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], - } - } -} - -static STATE: State = State::new(); - -/// safety: must be called only once -pub(crate) unsafe fn init(cs: critical_section::CriticalSection, irq_priority: Priority) { - foreach_interrupt! { - ($peri:ident, bdma, $block:ident, $signal_name:ident, $irq:ident) => { - crate::interrupt::typelevel::$irq::set_priority_with_cs(cs, irq_priority); - crate::interrupt::typelevel::$irq::enable(); - }; - } - crate::_generated::init_bdma(); -} - -foreach_dma_channel! { - ($channel_peri:ident, BDMA1, bdma, $channel_num:expr, $index:expr, $dmamux:tt) => { - // BDMA1 in H7 doesn't use DMAMUX, which breaks - }; - ($channel_peri:ident, $dma_peri:ident, bdma, $channel_num:expr, $index:expr, $dmamux:tt) => { - impl sealed::Channel for crate::peripherals::$channel_peri { - fn regs(&self) -> pac::bdma::Dma { - pac::$dma_peri - } - fn num(&self) -> usize { - $channel_num - } - fn index(&self) -> usize { - $index - } - fn on_irq() { - unsafe { on_irq_inner(pac::$dma_peri, $channel_num, $index) } - } - } - - impl Channel for crate::peripherals::$channel_peri {} - }; -} - -/// 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)); - #[cfg(not(armv6m))] - STATE.complete_count[index].fetch_add(1, Ordering::Release); - #[cfg(armv6m)] - critical_section::with(|_| { - let x = STATE.complete_count[index].load(Ordering::Relaxed); - STATE.complete_count[index].store(x + 1, Ordering::Release); - }) - } else { - return; - } - - STATE.ch_wakers[index].wake(); -} - -/// DMA request type alias. -#[cfg(any(bdma_v2, dmamux))] -pub type Request = u8; -/// DMA request type alias. -#[cfg(not(any(bdma_v2, dmamux)))] -pub type Request = (); - -/// DMA channel. -#[cfg(dmamux)] -pub trait Channel: sealed::Channel + Peripheral<P = Self> + 'static + super::dmamux::MuxChannel {} -/// DMA channel. -#[cfg(not(dmamux))] -pub trait Channel: sealed::Channel + Peripheral<P = Self> + 'static {} - -pub(crate) mod sealed { - use super::*; - - pub trait Channel { - fn regs(&self) -> pac::bdma::Dma; - fn num(&self) -> usize; - fn index(&self) -> usize; - fn on_irq(); - } -} - -/// DMA transfer. -#[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> { - /// Create a new read DMA transfer (peripheral to memory). - pub unsafe fn new_read<W: Word>( - channel: impl Peripheral<P = C> + 'a, - request: Request, - peri_addr: *mut W, - buf: &'a mut [W], - options: TransferOptions, - ) -> Self { - Self::new_read_raw(channel, request, peri_addr, buf, options) - } - - /// Create a new read DMA transfer (peripheral to memory), using raw pointers. - pub unsafe fn new_read_raw<W: Word>( - channel: impl Peripheral<P = C> + '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, - ) - } - - /// Create a new write DMA transfer (memory to peripheral). - pub unsafe fn new_write<W: Word>( - channel: impl Peripheral<P = C> + 'a, - request: Request, - buf: &'a [W], - peri_addr: *mut W, - options: TransferOptions, - ) -> Self { - Self::new_write_raw(channel, request, buf, peri_addr, options) - } - - /// Create a new write DMA transfer (memory to peripheral), using raw pointers. - pub unsafe fn new_write_raw<W: Word>( - channel: impl Peripheral<P = C> + '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, - ) - } - - /// Create a new write DMA transfer (memory to peripheral), writing the same value repeatedly. - pub unsafe fn new_write_repeated<W: Word>( - channel: impl Peripheral<P = C> + '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: WordSize, - options: TransferOptions, - ) -> 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.into()); - w.set_msize(data_size.into()); - w.set_minc(incr_mem); - w.set_dir(dir.into()); - w.set_teie(true); - w.set_tcie(options.complete_transfer_ir); - w.set_htie(options.half_transfer_ir); - w.set_circ(options.circular); - if options.circular { - debug!("Setting circular mode"); - } - 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); - }); - } - - /// Request the transfer to stop. - /// - /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. - 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| { - w.set_teie(true); - w.set_tcie(true); - }); - } - - /// Return whether this transfer is still running. - /// - /// If this returns `false`, it can be because either the transfer finished, or - /// it was requested to stop early with [`request_stop`](Self::request_stop). - 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(); - let tcif = STATE.complete_count[self.channel.index()].load(Ordering::Acquire) != 0; - en && (circular || !tcif) - } - - /// Get the total remaining transfers for the channel. - /// - /// This will be zero for transfers that completed instead of being canceled with [`request_stop`](Self::request_stop). - pub fn get_remaining_transfers(&self) -> u16 { - let ch = self.channel.regs().ch(self.channel.num()); - ch.ndtr().read().ndt() - } - - /// Blocking wait until the transfer finishes. - 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); - } -} - -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<Self::Output> { - STATE.ch_wakers[self.channel.index()].register(cx.waker()); - - 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 { - #[cfg(not(armv6m))] - return STATE.complete_count[self.0.index()].swap(0, Ordering::AcqRel); - #[cfg(armv6m)] - return critical_section::with(|_| { - let x = STATE.complete_count[self.0.index()].load(Ordering::Acquire); - STATE.complete_count[self.0.index()].store(0, Ordering::Release); - x - }); - } - - fn set_waker(&mut self, waker: &Waker) { - STATE.ch_wakers[self.0.index()].register(waker); - } -} - -/// Ringbuffer for reading data using DMA circular mode. -pub struct ReadableRingBuffer<'a, C: Channel, W: Word> { - cr: regs::Cr, - channel: PeripheralRef<'a, C>, - ringbuf: ReadableDmaRingBuffer<'a, W>, -} - -impl<'a, C: Channel, W: Word> ReadableRingBuffer<'a, C, W> { - /// Create a new ring buffer. - pub unsafe fn new( - channel: impl Peripheral<P = C> + '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(true); - w.set_dir(dir.into()); - w.set_teie(true); - w.set_htie(true); - w.set_tcie(true); - w.set_circ(true); - 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: ReadableDmaRingBuffer::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 - } - - /// Start the ring buffer operation. - /// - /// You must call this after creating it for it to work. - pub fn start(&mut self) { - let ch = self.channel.regs().ch(self.channel.num()); - ch.cr().write_value(self.cr) - } - - /// Clear all data in the ring buffer. - pub fn clear(&mut self) { - self.ringbuf.clear(&mut DmaCtrlImpl(self.channel.reborrow())); - } - - /// Read elements from the ring buffer - /// Return a tuple of the length read and the length remaining in the buffer - /// If not all of the elements were read, then there will be some elements in the buffer remaining - /// The length remaining is the capacity, ring_buf.len(), less the elements 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(&mut DmaCtrlImpl(self.channel.reborrow()), buf) - } - - /// Read an exact number of elements from the ringbuffer. - /// - /// Returns the remaining number of elements available for immediate reading. - /// OverrunError is returned if the portion to be read was overwritten by the DMA controller. - /// - /// Async/Wake Behavior: - /// The underlying DMA peripheral only can wake us when its buffer pointer has reached the halfway point, - /// and when it wraps around. This means that when called with a buffer of length 'M', when this - /// ring buffer was created with a buffer of size 'N': - /// - If M equals N/2 or N/2 divides evenly into M, this function will return every N/2 elements read on the DMA source. - /// - Otherwise, this function may need up to N/2 extra elements to arrive before returning. - pub async fn read_exact(&mut self, buffer: &mut [W]) -> Result<usize, OverrunError> { - self.ringbuf - .read_exact(&mut DmaCtrlImpl(self.channel.reborrow()), buffer) - .await - } - - /// The capacity of the ringbuffer. - pub const fn capacity(&self) -> usize { - self.ringbuf.cap() - } - - /// Set a waker to be woken when at least one byte is received. - pub fn set_waker(&mut self, waker: &Waker) { - DmaCtrlImpl(self.channel.reborrow()).set_waker(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); - }); - } - - /// Request DMA to stop. - /// - /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. - 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); - }); - } - - /// Return whether DMA is still running. - /// - /// If this returns `false`, it can be because either the transfer finished, or - /// it was requested to stop early with [`request_stop`](Self::request_stop). - 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 ReadableRingBuffer<'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); - } -} - -/// Ringbuffer for writing data using DMA circular mode. -pub struct WritableRingBuffer<'a, C: Channel, W: Word> { - cr: regs::Cr, - channel: PeripheralRef<'a, C>, - ringbuf: WritableDmaRingBuffer<'a, W>, -} - -impl<'a, C: Channel, W: Word> WritableRingBuffer<'a, C, W> { - /// Create a new ring buffer. - pub unsafe fn new( - channel: impl Peripheral<P = C> + '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::MemoryToPeripheral; - 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(true); - w.set_dir(dir.into()); - w.set_teie(true); - w.set_htie(true); - w.set_tcie(true); - w.set_circ(true); - 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: WritableDmaRingBuffer::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 - } - - /// Start the ring buffer operation. - /// - /// You must call this after creating it for it to work. - pub fn start(&mut self) { - let ch = self.channel.regs().ch(self.channel.num()); - ch.cr().write_value(self.cr) - } - - /// Clear all data in the ring buffer. - pub fn clear(&mut self) { - self.ringbuf.clear(&mut DmaCtrlImpl(self.channel.reborrow())); - } - - /// Write elements to the ring buffer - /// Return a tuple of the length written and the length remaining in the buffer - pub fn write(&mut self, buf: &[W]) -> Result<(usize, usize), OverrunError> { - self.ringbuf.write(&mut DmaCtrlImpl(self.channel.reborrow()), buf) - } - - /// Write an exact number of elements to the ringbuffer. - pub async fn write_exact(&mut self, buffer: &[W]) -> Result<usize, OverrunError> { - self.ringbuf - .write_exact(&mut DmaCtrlImpl(self.channel.reborrow()), buffer) - .await - } - - /// The capacity of the ringbuffer. - pub const fn capacity(&self) -> usize { - self.ringbuf.cap() - } - - /// Set a waker to be woken when at least one byte is sent. - pub fn set_waker(&mut self, waker: &Waker) { - DmaCtrlImpl(self.channel.reborrow()).set_waker(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); - }); - } - - /// Request DMA to stop. - /// - /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. - 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); - }); - } - - /// Return whether DMA is still running. - /// - /// If this returns `false`, it can be because either the transfer finished, or - /// it was requested to stop early with [`request_stop`](Self::request_stop). - 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 WritableRingBuffer<'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 deleted file mode 100644 index 16d02f273..000000000 --- a/embassy-stm32/src/dma/dma.rs +++ /dev/null @@ -1,1005 +0,0 @@ -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_internal::{into_ref, Peripheral, PeripheralRef}; -use embassy_sync::waitqueue::AtomicWaker; - -use super::ringbuffer::{DmaCtrl, OverrunError, ReadableDmaRingBuffer, WritableDmaRingBuffer}; -use super::word::{Word, WordSize}; -use super::Dir; -use crate::_generated::DMA_CHANNEL_COUNT; -use crate::interrupt::typelevel::Interrupt; -use crate::interrupt::Priority; -use crate::pac::dma::{regs, vals}; -use crate::{interrupt, pac}; - -/// DMA transfer options. -#[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<FifoThreshold>, - /// Enable circular DMA - /// - /// Note: - /// If you enable circular mode manually, you may want to build and `.await` the `Transfer` in a separate task. - /// Since DMA in circular mode need manually stop, `.await` in current task would block the task forever. - 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 { - pburst: Burst::Single, - mburst: Burst::Single, - flow_ctrl: FlowControl::Dma, - fifo_threshold: None, - circular: false, - half_transfer_ir: false, - complete_transfer_ir: true, - } - } -} - -impl From<WordSize> for vals::Size { - fn from(raw: WordSize) -> Self { - match raw { - WordSize::OneByte => Self::BITS8, - WordSize::TwoBytes => Self::BITS16, - WordSize::FourBytes => Self::BITS32, - } - } -} - -impl From<Dir> for vals::Dir { - fn from(raw: Dir) -> Self { - match raw { - Dir::MemoryToPeripheral => Self::MEMORYTOPERIPHERAL, - Dir::PeripheralToMemory => Self::PERIPHERALTOMEMORY, - } - } -} - -/// DMA transfer burst setting. -#[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<Burst> for vals::Burst { - fn from(burst: Burst) -> Self { - match burst { - Burst::Single => vals::Burst::SINGLE, - Burst::Incr4 => vals::Burst::INCR4, - Burst::Incr8 => vals::Burst::INCR8, - Burst::Incr16 => vals::Burst::INCR16, - } - } -} - -/// DMA flow control setting. -#[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<FlowControl> for vals::Pfctrl { - fn from(flow: FlowControl) -> Self { - match flow { - FlowControl::Dma => vals::Pfctrl::DMA, - FlowControl::Peripheral => vals::Pfctrl::PERIPHERAL, - } - } -} - -/// DMA FIFO threshold. -#[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 From<FifoThreshold> 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 { - ch_wakers: [AtomicWaker; DMA_CHANNEL_COUNT], - complete_count: [AtomicUsize; DMA_CHANNEL_COUNT], -} - -impl State { - const fn new() -> Self { - const ZERO: AtomicUsize = AtomicUsize::new(0); - const AW: AtomicWaker = AtomicWaker::new(); - Self { - ch_wakers: [AW; DMA_CHANNEL_COUNT], - complete_count: [ZERO; DMA_CHANNEL_COUNT], - } - } -} - -static STATE: State = State::new(); - -/// safety: must be called only once -pub(crate) unsafe fn init(cs: critical_section::CriticalSection, irq_priority: Priority) { - foreach_interrupt! { - ($peri:ident, dma, $block:ident, $signal_name:ident, $irq:ident) => { - interrupt::typelevel::$irq::set_priority_with_cs(cs, irq_priority); - interrupt::typelevel::$irq::enable(); - }; - } - crate::_generated::init_dma(); -} - -foreach_dma_channel! { - ($channel_peri:ident, $dma_peri:ident, dma, $channel_num:expr, $index:expr, $dmamux:tt) => { - impl sealed::Channel for crate::peripherals::$channel_peri { - fn regs(&self) -> pac::dma::Dma { - pac::$dma_peri - } - fn num(&self) -> usize { - $channel_num - } - fn index(&self) -> usize { - $index - } - fn on_irq() { - unsafe { on_irq_inner(pac::$dma_peri, $channel_num, $index) } - } - } - - impl Channel for crate::peripherals::$channel_peri {} - }; -} - -/// 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(); -} - -/// DMA request type alias. (also known as DMA channel number in some chips) -#[cfg(any(dma_v2, dmamux))] -pub type Request = u8; -/// DMA request type alias. (also known as DMA channel number in some chips) -#[cfg(not(any(dma_v2, dmamux)))] -pub type Request = (); - -/// DMA channel. -#[cfg(dmamux)] -pub trait Channel: sealed::Channel + Peripheral<P = Self> + 'static + super::dmamux::MuxChannel {} -/// DMA channel. -#[cfg(not(dmamux))] -pub trait Channel: sealed::Channel + Peripheral<P = Self> + 'static {} - -pub(crate) mod sealed { - use super::*; - - pub trait Channel { - fn regs(&self) -> pac::dma::Dma; - fn num(&self) -> usize; - fn index(&self) -> usize; - fn on_irq(); - } -} - -/// DMA transfer. -#[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> { - /// Create a new read DMA transfer (peripheral to memory). - pub unsafe fn new_read<W: Word>( - channel: impl Peripheral<P = C> + 'a, - request: Request, - peri_addr: *mut W, - buf: &'a mut [W], - options: TransferOptions, - ) -> Self { - Self::new_read_raw(channel, request, peri_addr, buf, options) - } - - /// Create a new read DMA transfer (peripheral to memory), using raw pointers. - pub unsafe fn new_read_raw<W: Word>( - channel: impl Peripheral<P = C> + '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, - ) - } - - /// Create a new write DMA transfer (memory to peripheral). - pub unsafe fn new_write<W: Word>( - channel: impl Peripheral<P = C> + 'a, - request: Request, - buf: &'a [W], - peri_addr: *mut W, - options: TransferOptions, - ) -> Self { - Self::new_write_raw(channel, request, buf, peri_addr, options) - } - - /// Create a new write DMA transfer (memory to peripheral), using raw pointers. - pub unsafe fn new_write_raw<W: Word>( - channel: impl Peripheral<P = C> + '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, - ) - } - - /// Create a new write DMA transfer (memory to peripheral), writing the same value repeatedly. - pub unsafe fn new_write_repeated<W: Word>( - channel: impl Peripheral<P = C> + '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: WordSize, - options: TransferOptions, - ) -> Self { - let ch = channel.regs().st(channel.num()); - - // "Preceding reads and writes cannot be moved past subsequent writes." - fence(Ordering::SeqCst); - - let mut this = Self { channel }; - this.clear_irqs(); - - #[cfg(dmamux)] - super::dmamux::configure_dmamux(&mut *this.channel, _request); - - 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.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(incr_mem); - w.set_pinc(false); - w.set_teie(true); - w.set_tcie(options.complete_transfer_ir); - w.set_circ(options.circular); - if options.circular { - debug!("Setting circular mode"); - } - #[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 - } - - fn clear_irqs(&mut self) { - let isrn = self.channel.num() / 4; - let isrbit = self.channel.num() % 4; - - self.channel.regs().ifcr(isrn).write(|w| { - w.set_tcif(isrbit, true); - w.set_teif(isrbit, true); - }); - } - - /// Request the transfer to stop. - /// - /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. - 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); - }); - } - - /// Return whether this transfer is still running. - /// - /// If this returns `false`, it can be because either the transfer finished, or - /// it was requested to stop early with [`request_stop`](Self::request_stop). - 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() - } - - /// Blocking wait until the transfer finishes. - 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<Self::Output> { - STATE.ch_wakers[self.channel.index()].register(cx.waker()); - - if self.is_running() { - Poll::Pending - } else { - Poll::Ready(()) - } - } -} - -// ================================== - -/// Double-buffered DMA transfer. -pub struct DoubleBuffered<'a, C: Channel, W: Word> { - channel: PeripheralRef<'a, C>, - _phantom: PhantomData<W>, -} - -impl<'a, C: Channel, W: Word> DoubleBuffered<'a, C, W> { - /// Create a new read DMA transfer (peripheral to memory). - pub unsafe fn new_read( - channel: impl Peripheral<P = C> + '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(true); - w.set_pinc(false); - 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 - } - - 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); - }); - } - - /// Set the first buffer address. - /// - /// You may call this while DMA is transferring the other buffer. - 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 _); - } - - /// Set the second buffer address. - /// - /// You may call this while DMA is transferring the other buffer. - 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 _); - } - - /// Returh whether buffer0 is accessible (i.e. whether DMA is transferring buffer1 now) - pub fn is_buffer0_accessible(&mut self) -> bool { - let ch = self.channel.regs().st(self.channel.num()); - ch.cr().read().ct() == vals::Ct::MEMORY1 - } - - /// Set a waker to be woken when one of the buffers is being transferred. - pub fn set_waker(&mut self, waker: &Waker) { - STATE.ch_wakers[self.channel.index()].register(waker); - } - - /// Request the transfer to stop. - /// - /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. - 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); - }); - } - - /// Return whether this transfer is still running. - /// - /// If this returns `false`, it can be because either the transfer finished, or - /// it was requested to stop early with [`request_stop`](Self::request_stop). - 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) - } - - fn set_waker(&mut self, waker: &Waker) { - STATE.ch_wakers[self.0.index()].register(waker); - } -} - -/// Ringbuffer for receiving data using DMA circular mode. -pub struct ReadableRingBuffer<'a, C: Channel, W: Word> { - cr: regs::Cr, - channel: PeripheralRef<'a, C>, - ringbuf: ReadableDmaRingBuffer<'a, W>, -} - -impl<'a, C: Channel, W: Word> ReadableRingBuffer<'a, C, W> { - /// Create a new ring buffer. - pub unsafe fn new( - channel: impl Peripheral<P = C> + '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(true); - w.set_pinc(false); - w.set_teie(true); - w.set_htie(options.half_transfer_ir); - w.set_tcie(true); - w.set_circ(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); - - let buffer_ptr = buffer.as_mut_ptr(); - let mut this = Self { - channel, - cr: w, - ringbuf: ReadableDmaRingBuffer::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 - } - - /// Start the ring buffer operation. - /// - /// You must call this after creating it for it to work. - pub fn start(&mut self) { - let ch = self.channel.regs().st(self.channel.num()); - ch.cr().write_value(self.cr); - } - - /// Clear all data in the ring buffer. - pub fn clear(&mut self) { - self.ringbuf.clear(&mut DmaCtrlImpl(self.channel.reborrow())); - } - - /// Read elements from the ring buffer - /// Return a tuple of the length read and the length remaining in the buffer - /// If not all of the elements were read, then there will be some elements in the buffer remaining - /// The length remaining is the capacity, ring_buf.len(), less the elements 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(&mut DmaCtrlImpl(self.channel.reborrow()), buf) - } - - /// Read an exact number of elements from the ringbuffer. - /// - /// Returns the remaining number of elements available for immediate reading. - /// OverrunError is returned if the portion to be read was overwritten by the DMA controller. - /// - /// Async/Wake Behavior: - /// The underlying DMA peripheral only can wake us when its buffer pointer has reached the halfway point, - /// and when it wraps around. This means that when called with a buffer of length 'M', when this - /// ring buffer was created with a buffer of size 'N': - /// - If M equals N/2 or N/2 divides evenly into M, this function will return every N/2 elements read on the DMA source. - /// - Otherwise, this function may need up to N/2 extra elements to arrive before returning. - pub async fn read_exact(&mut self, buffer: &mut [W]) -> Result<usize, OverrunError> { - self.ringbuf - .read_exact(&mut DmaCtrlImpl(self.channel.reborrow()), buffer) - .await - } - - /// The capacity of the ringbuffer - pub const fn capacity(&self) -> usize { - self.ringbuf.cap() - } - - /// Set a waker to be woken when at least one byte is received. - pub fn set_waker(&mut self, waker: &Waker) { - DmaCtrlImpl(self.channel.reborrow()).set_waker(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); - }); - } - - /// Request DMA to stop. - /// - /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. - 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); - }); - } - - /// Return whether DMA is still running. - /// - /// If this returns `false`, it can be because either the transfer finished, or - /// it was requested to stop early with [`request_stop`](Self::request_stop). - 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 ReadableRingBuffer<'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); - } -} - -/// Ringbuffer for writing data using DMA circular mode. -pub struct WritableRingBuffer<'a, C: Channel, W: Word> { - cr: regs::Cr, - channel: PeripheralRef<'a, C>, - ringbuf: WritableDmaRingBuffer<'a, W>, -} - -impl<'a, C: Channel, W: Word> WritableRingBuffer<'a, C, W> { - /// Create a new ring buffer. - pub unsafe fn new( - channel: impl Peripheral<P = C> + '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::MemoryToPeripheral; - 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(true); - w.set_pinc(false); - w.set_teie(true); - w.set_htie(options.half_transfer_ir); - w.set_tcie(true); - w.set_circ(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); - - let buffer_ptr = buffer.as_mut_ptr(); - let mut this = Self { - channel, - cr: w, - ringbuf: WritableDmaRingBuffer::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 - } - - /// Start the ring buffer operation. - /// - /// You must call this after creating it for it to work. - pub fn start(&mut self) { - let ch = self.channel.regs().st(self.channel.num()); - ch.cr().write_value(self.cr); - } - - /// Clear all data in the ring buffer. - pub fn clear(&mut self) { - self.ringbuf.clear(&mut DmaCtrlImpl(self.channel.reborrow())); - } - - /// Write elements from the ring buffer - /// Return a tuple of the length written and the length remaining in the buffer - pub fn write(&mut self, buf: &[W]) -> Result<(usize, usize), OverrunError> { - self.ringbuf.write(&mut DmaCtrlImpl(self.channel.reborrow()), buf) - } - - /// Write an exact number of elements to the ringbuffer. - pub async fn write_exact(&mut self, buffer: &[W]) -> Result<usize, OverrunError> { - self.ringbuf - .write_exact(&mut DmaCtrlImpl(self.channel.reborrow()), buffer) - .await - } - - /// The capacity of the ringbuffer - pub const fn capacity(&self) -> usize { - self.ringbuf.cap() - } - - /// Set a waker to be woken when at least one byte is received. - pub fn set_waker(&mut self, waker: &Waker) { - DmaCtrlImpl(self.channel.reborrow()).set_waker(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); - }); - } - - /// Request DMA to stop. - /// - /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. - 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); - }); - } - - /// Return whether DMA is still running. - /// - /// If this returns `false`, it can be because either the transfer finished, or - /// it was requested to stop early with [`request_stop`](Self::request_stop). - 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 WritableRingBuffer<'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_bdma.rs b/embassy-stm32/src/dma/dma_bdma.rs new file mode 100644 index 000000000..08aba2795 --- /dev/null +++ b/embassy-stm32/src/dma/dma_bdma.rs @@ -0,0 +1,913 @@ +use core::future::Future; +use core::pin::Pin; +use core::sync::atomic::{fence, AtomicUsize, Ordering}; +use core::task::{Context, Poll, Waker}; + +use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; + +use super::ringbuffer::{DmaCtrl, OverrunError, ReadableDmaRingBuffer, WritableDmaRingBuffer}; +use super::word::{Word, WordSize}; +use super::{AnyChannel, Channel, Dir, Request, STATE}; +use crate::interrupt::typelevel::Interrupt; +use crate::interrupt::Priority; +use crate::pac; + +pub(crate) struct ChannelInfo { + pub(crate) dma: DmaInfo, + pub(crate) num: usize, + #[cfg(dmamux)] + pub(crate) dmamux: super::DmamuxInfo, +} + +#[derive(Clone, Copy)] +pub(crate) enum DmaInfo { + #[cfg(dma)] + Dma(pac::dma::Dma), + #[cfg(bdma)] + Bdma(pac::bdma::Dma), +} + +/// DMA transfer options. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] +pub struct TransferOptions { + /// Peripheral burst transfer configuration + #[cfg(dma)] + pub pburst: Burst, + /// Memory burst transfer configuration + #[cfg(dma)] + pub mburst: Burst, + /// Flow control configuration + #[cfg(dma)] + pub flow_ctrl: FlowControl, + /// FIFO threshold for DMA FIFO mode. If none, direct mode is used. + #[cfg(dma)] + pub fifo_threshold: Option<FifoThreshold>, + /// Enable circular DMA + /// + /// Note: + /// If you enable circular mode manually, you may want to build and `.await` the `Transfer` in a separate task. + /// Since DMA in circular mode need manually stop, `.await` in current task would block the task forever. + 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 { + #[cfg(dma)] + pburst: Burst::Single, + #[cfg(dma)] + mburst: Burst::Single, + #[cfg(dma)] + flow_ctrl: FlowControl::Dma, + #[cfg(dma)] + fifo_threshold: None, + circular: false, + half_transfer_ir: false, + complete_transfer_ir: true, + } + } +} + +#[cfg(dma)] +pub use dma_only::*; +#[cfg(dma)] +mod dma_only { + use pac::dma::vals; + + use super::*; + + impl From<WordSize> for vals::Size { + fn from(raw: WordSize) -> Self { + match raw { + WordSize::OneByte => Self::BITS8, + WordSize::TwoBytes => Self::BITS16, + WordSize::FourBytes => Self::BITS32, + } + } + } + + impl From<Dir> for vals::Dir { + fn from(raw: Dir) -> Self { + match raw { + Dir::MemoryToPeripheral => Self::MEMORYTOPERIPHERAL, + Dir::PeripheralToMemory => Self::PERIPHERALTOMEMORY, + } + } + } + + /// DMA transfer burst setting. + #[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<Burst> for vals::Burst { + fn from(burst: Burst) -> Self { + match burst { + Burst::Single => vals::Burst::SINGLE, + Burst::Incr4 => vals::Burst::INCR4, + Burst::Incr8 => vals::Burst::INCR8, + Burst::Incr16 => vals::Burst::INCR16, + } + } + } + + /// DMA flow control setting. + #[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<FlowControl> for vals::Pfctrl { + fn from(flow: FlowControl) -> Self { + match flow { + FlowControl::Dma => vals::Pfctrl::DMA, + FlowControl::Peripheral => vals::Pfctrl::PERIPHERAL, + } + } + } + + /// DMA FIFO threshold. + #[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 From<FifoThreshold> 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, + } + } + } +} + +#[cfg(bdma)] +mod bdma_only { + use pac::bdma::vals; + + use super::*; + + impl From<WordSize> for vals::Size { + fn from(raw: WordSize) -> Self { + match raw { + WordSize::OneByte => Self::BITS8, + WordSize::TwoBytes => Self::BITS16, + WordSize::FourBytes => Self::BITS32, + } + } + } + + impl From<Dir> for vals::Dir { + fn from(raw: Dir) -> Self { + match raw { + Dir::MemoryToPeripheral => Self::FROMMEMORY, + Dir::PeripheralToMemory => Self::FROMPERIPHERAL, + } + } + } +} + +pub(crate) struct ChannelState { + waker: AtomicWaker, + complete_count: AtomicUsize, +} + +impl ChannelState { + pub(crate) const NEW: Self = Self { + waker: AtomicWaker::new(), + complete_count: AtomicUsize::new(0), + }; +} + +/// safety: must be called only once +pub(crate) unsafe fn init( + cs: critical_section::CriticalSection, + #[cfg(dma)] dma_priority: Priority, + #[cfg(bdma)] bdma_priority: Priority, +) { + foreach_interrupt! { + ($peri:ident, dma, $block:ident, $signal_name:ident, $irq:ident) => { + crate::interrupt::typelevel::$irq::set_priority_with_cs(cs, dma_priority); + crate::interrupt::typelevel::$irq::enable(); + }; + ($peri:ident, bdma, $block:ident, $signal_name:ident, $irq:ident) => { + crate::interrupt::typelevel::$irq::set_priority_with_cs(cs, bdma_priority); + crate::interrupt::typelevel::$irq::enable(); + }; + } + crate::_generated::init_dma(); + crate::_generated::init_bdma(); +} + +impl AnyChannel { + /// Safety: Must be called with a matching set of parameters for a valid dma channel + pub(crate) unsafe fn on_irq(&self) { + let info = self.info(); + let state = &STATE[self.id as usize]; + match self.info().dma { + #[cfg(dma)] + DmaInfo::Dma(r) => { + let cr = r.st(info.num).cr(); + let isr = r.isr(info.num / 4).read(); + + if isr.teif(info.num % 4) { + panic!("DMA: error on DMA@{:08x} channel {}", r.as_ptr() as u32, info.num); + } + + if isr.htif(info.num % 4) && cr.read().htie() { + // Acknowledge half transfer complete interrupt + r.ifcr(info.num / 4).write(|w| w.set_htif(info.num % 4, true)); + } else if isr.tcif(info.num % 4) && cr.read().tcie() { + // Acknowledge transfer complete interrupt + r.ifcr(info.num / 4).write(|w| w.set_tcif(info.num % 4, true)); + state.complete_count.fetch_add(1, Ordering::Release); + } else { + return; + } + + state.waker.wake(); + } + #[cfg(bdma)] + DmaInfo::Bdma(r) => { + let isr = r.isr().read(); + let cr = r.ch(info.num).cr(); + + if isr.teif(info.num) { + panic!("DMA: error on BDMA@{:08x} channel {}", r.as_ptr() as u32, info.num); + } + + if isr.htif(info.num) && cr.read().htie() { + // Acknowledge half transfer complete interrupt + r.ifcr().write(|w| w.set_htif(info.num, true)); + } else if isr.tcif(info.num) && cr.read().tcie() { + // Acknowledge transfer complete interrupt + r.ifcr().write(|w| w.set_tcif(info.num, true)); + #[cfg(not(armv6m))] + state.complete_count.fetch_add(1, Ordering::Release); + #[cfg(armv6m)] + critical_section::with(|_| { + let x = state.complete_count.load(Ordering::Relaxed); + state.complete_count.store(x + 1, Ordering::Release); + }) + } else { + return; + } + + state.waker.wake(); + } + } + } + + unsafe fn configure( + &self, + _request: Request, + dir: Dir, + peri_addr: *const u32, + mem_addr: *mut u32, + mem_len: usize, + incr_mem: bool, + data_size: WordSize, + options: TransferOptions, + ) { + let info = self.info(); + + #[cfg(dmamux)] + super::dmamux::configure_dmamux(&info.dmamux, _request); + + assert!(mem_len > 0 && mem_len <= 0xFFFF); + + match self.info().dma { + #[cfg(dma)] + DmaInfo::Dma(r) => { + let ch = r.st(info.num); + + // "Preceding reads and writes cannot be moved past subsequent writes." + fence(Ordering::SeqCst); + + self.clear_irqs(); + + ch.par().write_value(peri_addr as u32); + ch.m0ar().write_value(mem_addr as u32); + ch.ndtr().write_value(pac::dma::regs::Ndtr(mem_len as _)); + ch.fcr().write(|w| { + if let Some(fth) = options.fifo_threshold { + // FIFO mode + w.set_dmdis(pac::dma::vals::Dmdis::DISABLED); + w.set_fth(fth.into()); + } else { + // Direct mode + w.set_dmdis(pac::dma::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(pac::dma::vals::Pl::VERYHIGH); + w.set_minc(incr_mem); + w.set_pinc(false); + w.set_teie(true); + w.set_htie(options.half_transfer_ir); + w.set_tcie(options.complete_transfer_ir); + w.set_circ(options.circular); + #[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(false); // don't start yet + }); + } + #[cfg(bdma)] + DmaInfo::Bdma(r) => { + #[cfg(bdma_v2)] + critical_section::with(|_| r.cselr().modify(|w| w.set_cs(info.num, _request))); + + let state: &ChannelState = &STATE[self.id as usize]; + let ch = r.ch(info.num); + + state.complete_count.store(0, Ordering::Release); + self.clear_irqs(); + + 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.into()); + w.set_msize(data_size.into()); + w.set_minc(incr_mem); + w.set_dir(dir.into()); + w.set_teie(true); + w.set_tcie(options.complete_transfer_ir); + w.set_htie(options.half_transfer_ir); + w.set_circ(options.circular); + w.set_pl(pac::bdma::vals::Pl::VERYHIGH); + w.set_en(false); // don't start yet + }); + } + } + } + + fn start(&self) { + let info = self.info(); + match self.info().dma { + #[cfg(dma)] + DmaInfo::Dma(r) => { + let ch = r.st(info.num); + ch.cr().modify(|w| w.set_en(true)) + } + #[cfg(bdma)] + DmaInfo::Bdma(r) => { + let ch = r.ch(info.num); + ch.cr().modify(|w| w.set_en(true)); + } + } + } + + fn clear_irqs(&self) { + let info = self.info(); + match self.info().dma { + #[cfg(dma)] + DmaInfo::Dma(r) => { + let isrn = info.num / 4; + let isrbit = info.num % 4; + + r.ifcr(isrn).write(|w| { + w.set_htif(isrbit, true); + w.set_tcif(isrbit, true); + w.set_teif(isrbit, true); + }); + } + #[cfg(bdma)] + DmaInfo::Bdma(r) => { + r.ifcr().write(|w| { + w.set_htif(info.num, true); + w.set_tcif(info.num, true); + w.set_teif(info.num, true); + }); + } + } + } + + fn request_stop(&self) { + let info = self.info(); + match self.info().dma { + #[cfg(dma)] + DmaInfo::Dma(r) => { + // Disable the channel. Keep the IEs enabled so the irqs still fire. + r.st(info.num).cr().write(|w| { + w.set_teie(true); + w.set_tcie(true); + }); + } + #[cfg(bdma)] + DmaInfo::Bdma(r) => { + // Disable the channel. Keep the IEs enabled so the irqs still fire. + r.ch(info.num).cr().write(|w| { + w.set_teie(true); + w.set_tcie(true); + }); + } + } + } + + fn is_running(&self) -> bool { + let info = self.info(); + match self.info().dma { + #[cfg(dma)] + DmaInfo::Dma(r) => r.st(info.num).cr().read().en(), + #[cfg(bdma)] + DmaInfo::Bdma(r) => { + let state: &ChannelState = &STATE[self.id as usize]; + let ch = r.ch(info.num); + let en = ch.cr().read().en(); + let circular = ch.cr().read().circ(); + let tcif = state.complete_count.load(Ordering::Acquire) != 0; + en && (circular || !tcif) + } + } + } + + fn get_remaining_transfers(&self) -> u16 { + let info = self.info(); + match self.info().dma { + #[cfg(dma)] + DmaInfo::Dma(r) => r.st(info.num).ndtr().read().ndt(), + #[cfg(bdma)] + DmaInfo::Bdma(r) => r.ch(info.num).ndtr().read().ndt(), + } + } +} + +/// DMA transfer. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct Transfer<'a> { + channel: PeripheralRef<'a, AnyChannel>, +} + +impl<'a> Transfer<'a> { + /// Create a new read DMA transfer (peripheral to memory). + pub unsafe fn new_read<W: Word>( + channel: impl Peripheral<P = impl Channel> + 'a, + request: Request, + peri_addr: *mut W, + buf: &'a mut [W], + options: TransferOptions, + ) -> Self { + Self::new_read_raw(channel, request, peri_addr, buf, options) + } + + /// Create a new read DMA transfer (peripheral to memory), using raw pointers. + pub unsafe fn new_read_raw<W: Word>( + channel: impl Peripheral<P = impl Channel> + '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.map_into(), + request, + Dir::PeripheralToMemory, + peri_addr as *const u32, + ptr as *mut u32, + len, + true, + W::size(), + options, + ) + } + + /// Create a new write DMA transfer (memory to peripheral). + pub unsafe fn new_write<W: Word>( + channel: impl Peripheral<P = impl Channel> + 'a, + request: Request, + buf: &'a [W], + peri_addr: *mut W, + options: TransferOptions, + ) -> Self { + Self::new_write_raw(channel, request, buf, peri_addr, options) + } + + /// Create a new write DMA transfer (memory to peripheral), using raw pointers. + pub unsafe fn new_write_raw<W: Word>( + channel: impl Peripheral<P = impl Channel> + '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.map_into(), + request, + Dir::MemoryToPeripheral, + peri_addr as *const u32, + ptr as *mut u32, + len, + true, + W::size(), + options, + ) + } + + /// Create a new write DMA transfer (memory to peripheral), writing the same value repeatedly. + pub unsafe fn new_write_repeated<W: Word>( + channel: impl Peripheral<P = impl Channel> + 'a, + request: Request, + repeated: &'a W, + count: usize, + peri_addr: *mut W, + options: TransferOptions, + ) -> Self { + into_ref!(channel); + + Self::new_inner( + channel.map_into(), + 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, AnyChannel>, + _request: Request, + dir: Dir, + peri_addr: *const u32, + mem_addr: *mut u32, + mem_len: usize, + incr_mem: bool, + data_size: WordSize, + options: TransferOptions, + ) -> Self { + channel.configure( + _request, dir, peri_addr, mem_addr, mem_len, incr_mem, data_size, options, + ); + channel.start(); + + Self { channel } + } + + /// Request the transfer to stop. + /// + /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. + pub fn request_stop(&mut self) { + self.channel.request_stop() + } + + /// Return whether this transfer is still running. + /// + /// If this returns `false`, it can be because either the transfer finished, or + /// it was requested to stop early with [`request_stop`](Self::request_stop). + pub fn is_running(&mut self) -> bool { + self.channel.is_running() + } + + /// 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 { + self.channel.get_remaining_transfers() + } + + /// Blocking wait until the transfer finishes. + 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> Drop for Transfer<'a> { + 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> Unpin for Transfer<'a> {} +impl<'a> Future for Transfer<'a> { + type Output = (); + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { + let state: &ChannelState = &STATE[self.channel.id as usize]; + + state.waker.register(cx.waker()); + + if self.is_running() { + Poll::Pending + } else { + Poll::Ready(()) + } + } +} + +// ============================== + +struct DmaCtrlImpl<'a>(PeripheralRef<'a, AnyChannel>); + +impl<'a> DmaCtrl for DmaCtrlImpl<'a> { + fn get_remaining_transfers(&self) -> usize { + self.0.get_remaining_transfers() as _ + } + + fn get_complete_count(&self) -> usize { + STATE[self.0.id as usize].complete_count.load(Ordering::Acquire) + } + + fn reset_complete_count(&mut self) -> usize { + let state = &STATE[self.0.id as usize]; + #[cfg(not(armv6m))] + return state.complete_count.swap(0, Ordering::AcqRel); + #[cfg(armv6m)] + return critical_section::with(|_| { + let x = state.complete_count.load(Ordering::Acquire); + state.complete_count.store(0, Ordering::Release); + x + }); + } + + fn set_waker(&mut self, waker: &Waker) { + STATE[self.0.id as usize].waker.register(waker); + } +} + +/// Ringbuffer for receiving data using DMA circular mode. +pub struct ReadableRingBuffer<'a, W: Word> { + channel: PeripheralRef<'a, AnyChannel>, + ringbuf: ReadableDmaRingBuffer<'a, W>, +} + +impl<'a, W: Word> ReadableRingBuffer<'a, W> { + /// Create a new ring buffer. + pub unsafe fn new( + channel: impl Peripheral<P = impl Channel> + 'a, + _request: Request, + peri_addr: *mut W, + buffer: &'a mut [W], + mut options: TransferOptions, + ) -> Self { + into_ref!(channel); + let channel: PeripheralRef<'a, AnyChannel> = channel.map_into(); + + let buffer_ptr = buffer.as_mut_ptr(); + let len = buffer.len(); + let dir = Dir::PeripheralToMemory; + let data_size = W::size(); + + options.complete_transfer_ir = true; + options.circular = true; + + channel.configure( + _request, + dir, + peri_addr as *mut u32, + buffer_ptr as *mut u32, + len, + true, + data_size, + options, + ); + + Self { + channel, + ringbuf: ReadableDmaRingBuffer::new(buffer), + } + } + + /// Start the ring buffer operation. + /// + /// You must call this after creating it for it to work. + pub fn start(&mut self) { + self.channel.start() + } + + /// Clear all data in the ring buffer. + pub fn clear(&mut self) { + self.ringbuf.clear(&mut DmaCtrlImpl(self.channel.reborrow())); + } + + /// Read elements from the ring buffer + /// Return a tuple of the length read and the length remaining in the buffer + /// If not all of the elements were read, then there will be some elements in the buffer remaining + /// The length remaining is the capacity, ring_buf.len(), less the elements 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(&mut DmaCtrlImpl(self.channel.reborrow()), buf) + } + + /// Read an exact number of elements from the ringbuffer. + /// + /// Returns the remaining number of elements available for immediate reading. + /// OverrunError is returned if the portion to be read was overwritten by the DMA controller. + /// + /// Async/Wake Behavior: + /// The underlying DMA peripheral only can wake us when its buffer pointer has reached the halfway point, + /// and when it wraps around. This means that when called with a buffer of length 'M', when this + /// ring buffer was created with a buffer of size 'N': + /// - If M equals N/2 or N/2 divides evenly into M, this function will return every N/2 elements read on the DMA source. + /// - Otherwise, this function may need up to N/2 extra elements to arrive before returning. + pub async fn read_exact(&mut self, buffer: &mut [W]) -> Result<usize, OverrunError> { + self.ringbuf + .read_exact(&mut DmaCtrlImpl(self.channel.reborrow()), buffer) + .await + } + + /// The capacity of the ringbuffer + pub const fn capacity(&self) -> usize { + self.ringbuf.cap() + } + + /// Set a waker to be woken when at least one byte is received. + pub fn set_waker(&mut self, waker: &Waker) { + DmaCtrlImpl(self.channel.reborrow()).set_waker(waker); + } + + /// Request DMA to stop. + /// + /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. + pub fn request_stop(&mut self) { + self.channel.request_stop() + } + + /// Return whether DMA is still running. + /// + /// If this returns `false`, it can be because either the transfer finished, or + /// it was requested to stop early with [`request_stop`](Self::request_stop). + pub fn is_running(&mut self) -> bool { + self.channel.is_running() + } +} + +impl<'a, W: Word> Drop for ReadableRingBuffer<'a, 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); + } +} + +/// Ringbuffer for writing data using DMA circular mode. +pub struct WritableRingBuffer<'a, W: Word> { + channel: PeripheralRef<'a, AnyChannel>, + ringbuf: WritableDmaRingBuffer<'a, W>, +} + +impl<'a, W: Word> WritableRingBuffer<'a, W> { + /// Create a new ring buffer. + pub unsafe fn new( + channel: impl Peripheral<P = impl Channel> + 'a, + _request: Request, + peri_addr: *mut W, + buffer: &'a mut [W], + mut options: TransferOptions, + ) -> Self { + into_ref!(channel); + let channel: PeripheralRef<'a, AnyChannel> = channel.map_into(); + + let len = buffer.len(); + let dir = Dir::MemoryToPeripheral; + let data_size = W::size(); + let buffer_ptr = buffer.as_mut_ptr(); + + options.complete_transfer_ir = true; + options.circular = true; + + channel.configure( + _request, + dir, + peri_addr as *mut u32, + buffer_ptr as *mut u32, + len, + true, + data_size, + options, + ); + + Self { + channel, + ringbuf: WritableDmaRingBuffer::new(buffer), + } + } + + /// Start the ring buffer operation. + /// + /// You must call this after creating it for it to work. + pub fn start(&mut self) { + self.channel.start() + } + + /// Clear all data in the ring buffer. + pub fn clear(&mut self) { + self.ringbuf.clear(&mut DmaCtrlImpl(self.channel.reborrow())); + } + + /// Write elements directly to the raw buffer. + /// This can be used to fill the buffer before starting the DMA transfer. + #[allow(dead_code)] + pub fn write_immediate(&mut self, buf: &[W]) -> Result<(usize, usize), OverrunError> { + self.ringbuf.write_immediate(buf) + } + + /// Write elements from the ring buffer + /// Return a tuple of the length written and the length remaining in the buffer + pub fn write(&mut self, buf: &[W]) -> Result<(usize, usize), OverrunError> { + self.ringbuf.write(&mut DmaCtrlImpl(self.channel.reborrow()), buf) + } + + /// Write an exact number of elements to the ringbuffer. + pub async fn write_exact(&mut self, buffer: &[W]) -> Result<usize, OverrunError> { + self.ringbuf + .write_exact(&mut DmaCtrlImpl(self.channel.reborrow()), buffer) + .await + } + + /// The capacity of the ringbuffer + pub const fn capacity(&self) -> usize { + self.ringbuf.cap() + } + + /// Set a waker to be woken when at least one byte is received. + pub fn set_waker(&mut self, waker: &Waker) { + DmaCtrlImpl(self.channel.reborrow()).set_waker(waker); + } + + /// Request DMA to stop. + /// + /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. + pub fn request_stop(&mut self) { + self.channel.request_stop() + } + + /// Return whether DMA is still running. + /// + /// If this returns `false`, it can be because either the transfer finished, or + /// it was requested to stop early with [`request_stop`](Self::request_stop). + pub fn is_running(&mut self) -> bool { + self.channel.is_running() + } +} + +impl<'a, W: Word> Drop for WritableRingBuffer<'a, 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 9cd494724..1e9ab5944 100644 --- a/embassy-stm32/src/dma/dmamux.rs +++ b/embassy-stm32/src/dma/dmamux.rs @@ -1,9 +1,14 @@ #![macro_use] -use crate::{pac, peripherals}; +use crate::pac; -pub(crate) fn configure_dmamux<M: MuxChannel>(channel: &mut M, request: u8) { - let ch_mux_regs = channel.mux_regs().ccr(channel.mux_num()); +pub(crate) struct DmamuxInfo { + pub(crate) mux: pac::dmamux::Dmamux, + pub(crate) num: usize, +} + +pub(crate) fn configure_dmamux(info: &DmamuxInfo, request: u8) { + let ch_mux_regs = info.mux.ccr(info.num); ch_mux_regs.write(|reg| { reg.set_nbreq(0); reg.set_dmareq_id(request); @@ -15,11 +20,7 @@ pub(crate) fn configure_dmamux<M: MuxChannel>(channel: &mut M, request: u8) { } pub(crate) mod dmamux_sealed { - use super::*; - pub trait MuxChannel { - fn mux_regs(&self) -> pac::dmamux::Dmamux; - fn mux_num(&self) -> usize; - } + pub trait MuxChannel {} } /// DMAMUX1 instance. @@ -34,18 +35,11 @@ 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 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; +macro_rules! dmamux_channel_impl { + ($channel_peri:ident, $dmamux:ident) => { + impl crate::dma::dmamux_sealed::MuxChannel for crate::peripherals::$channel_peri {} + impl crate::dma::MuxChannel for crate::peripherals::$channel_peri { + type Mux = crate::dma::$dmamux; } }; } diff --git a/embassy-stm32/src/dma/gpdma.rs b/embassy-stm32/src/dma/gpdma.rs index 34b2426b9..ef03970ef 100644 --- a/embassy-stm32/src/dma/gpdma.rs +++ b/embassy-stm32/src/dma/gpdma.rs @@ -9,13 +9,17 @@ use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef}; use embassy_sync::waitqueue::AtomicWaker; use super::word::{Word, WordSize}; -use super::Dir; -use crate::_generated::GPDMA_CHANNEL_COUNT; +use super::{AnyChannel, Channel, Dir, Request, STATE}; use crate::interrupt::typelevel::Interrupt; use crate::interrupt::Priority; use crate::pac; use crate::pac::gpdma::vals; +pub(crate) struct ChannelInfo { + pub(crate) dma: pac::gpdma::Gpdma, + pub(crate) num: usize, +} + /// GPDMA transfer options. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -38,21 +42,16 @@ impl From<WordSize> for vals::ChTr1Dw { } } -struct State { - ch_wakers: [AtomicWaker; GPDMA_CHANNEL_COUNT], +pub(crate) struct ChannelState { + waker: AtomicWaker, } -impl State { - const fn new() -> Self { - const AW: AtomicWaker = AtomicWaker::new(); - Self { - ch_wakers: [AW; GPDMA_CHANNEL_COUNT], - } - } +impl ChannelState { + pub(crate) const NEW: Self = Self { + waker: AtomicWaker::new(), + }; } -static STATE: State = State::new(); - /// safety: must be called only once pub(crate) unsafe fn init(cs: critical_section::CriticalSection, irq_priority: Priority) { foreach_interrupt! { @@ -64,87 +63,50 @@ pub(crate) unsafe fn init(cs: critical_section::CriticalSection, irq_priority: P crate::_generated::init_gpdma(); } -foreach_dma_channel! { - ($channel_peri:ident, $dma_peri:ident, gpdma, $channel_num:expr, $index:expr, $dmamux:tt) => { - impl sealed::Channel for crate::peripherals::$channel_peri { - fn regs(&self) -> pac::gpdma::Gpdma { - pac::$dma_peri - } - fn num(&self) -> usize { - $channel_num - } - fn index(&self) -> usize { - $index - } - fn on_irq() { - unsafe { on_irq_inner(pac::$dma_peri, $channel_num, $index) } - } +impl AnyChannel { + /// Safety: Must be called with a matching set of parameters for a valid dma channel + pub(crate) unsafe fn on_irq(&self) { + let info = self.info(); + let state = &STATE[self.id as usize]; + + let ch = info.dma.ch(info.num); + let sr = ch.sr().read(); + + if sr.dtef() { + panic!( + "DMA: data transfer error on DMA@{:08x} channel {}", + info.dma.as_ptr() as u32, + info.num + ); + } + if sr.usef() { + panic!( + "DMA: user settings error on DMA@{:08x} channel {}", + info.dma.as_ptr() as u32, + info.num + ); } - impl Channel for crate::peripherals::$channel_peri {} - }; -} + if sr.suspf() || sr.tcf() { + // disable all xxIEs to prevent the irq from firing again. + ch.cr().write(|_| {}); -/// 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(); - - 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 - ); - } - - 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(); - } -} - -/// DMA request type alias. (also known as DMA channel number in some chips) -pub type Request = u8; - -/// DMA channel. -#[cfg(dmamux)] -pub trait Channel: sealed::Channel + Peripheral<P = Self> + 'static + super::dmamux::MuxChannel {} -/// DMA channel. -#[cfg(not(dmamux))] -pub trait Channel: sealed::Channel + Peripheral<P = Self> + '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(); + // Wake the future. It'll look at tcf and see it's set. + state.waker.wake(); + } } } /// DMA transfer. #[must_use = "futures do nothing unless you `.await` or poll them"] -pub struct Transfer<'a, C: Channel> { - channel: PeripheralRef<'a, C>, +pub struct Transfer<'a> { + channel: PeripheralRef<'a, AnyChannel>, } -impl<'a, C: Channel> Transfer<'a, C> { +impl<'a> Transfer<'a> { /// Create a new read DMA transfer (peripheral to memory). pub unsafe fn new_read<W: Word>( - channel: impl Peripheral<P = C> + 'a, + channel: impl Peripheral<P = impl Channel> + 'a, request: Request, peri_addr: *mut W, buf: &'a mut [W], @@ -155,7 +117,7 @@ impl<'a, C: Channel> Transfer<'a, C> { /// Create a new read DMA transfer (peripheral to memory), using raw pointers. pub unsafe fn new_read_raw<W: Word>( - channel: impl Peripheral<P = C> + 'a, + channel: impl Peripheral<P = impl Channel> + 'a, request: Request, peri_addr: *mut W, buf: *mut [W], @@ -167,7 +129,7 @@ impl<'a, C: Channel> Transfer<'a, C> { assert!(len > 0 && len <= 0xFFFF); Self::new_inner( - channel, + channel.map_into(), request, Dir::PeripheralToMemory, peri_addr as *const u32, @@ -181,7 +143,7 @@ impl<'a, C: Channel> Transfer<'a, C> { /// Create a new write DMA transfer (memory to peripheral). pub unsafe fn new_write<W: Word>( - channel: impl Peripheral<P = C> + 'a, + channel: impl Peripheral<P = impl Channel> + 'a, request: Request, buf: &'a [W], peri_addr: *mut W, @@ -192,7 +154,7 @@ impl<'a, C: Channel> Transfer<'a, C> { /// Create a new write DMA transfer (memory to peripheral), using raw pointers. pub unsafe fn new_write_raw<W: Word>( - channel: impl Peripheral<P = C> + 'a, + channel: impl Peripheral<P = impl Channel> + 'a, request: Request, buf: *const [W], peri_addr: *mut W, @@ -204,7 +166,7 @@ impl<'a, C: Channel> Transfer<'a, C> { assert!(len > 0 && len <= 0xFFFF); Self::new_inner( - channel, + channel.map_into(), request, Dir::MemoryToPeripheral, peri_addr as *const u32, @@ -218,7 +180,7 @@ impl<'a, C: Channel> Transfer<'a, C> { /// Create a new write DMA transfer (memory to peripheral), writing the same value repeatedly. pub unsafe fn new_write_repeated<W: Word>( - channel: impl Peripheral<P = C> + 'a, + channel: impl Peripheral<P = impl Channel> + 'a, request: Request, repeated: &'a W, count: usize, @@ -228,7 +190,7 @@ impl<'a, C: Channel> Transfer<'a, C> { into_ref!(channel); Self::new_inner( - channel, + channel.map_into(), request, Dir::MemoryToPeripheral, peri_addr as *const u32, @@ -241,7 +203,7 @@ impl<'a, C: Channel> Transfer<'a, C> { } unsafe fn new_inner( - channel: PeripheralRef<'a, C>, + channel: PeripheralRef<'a, AnyChannel>, request: Request, dir: Dir, peri_addr: *const u32, @@ -251,7 +213,8 @@ impl<'a, C: Channel> Transfer<'a, C> { data_size: WordSize, _options: TransferOptions, ) -> Self { - let ch = channel.regs().ch(channel.num()); + let info = channel.info(); + let ch = info.dma.ch(info.num); // "Preceding reads and writes cannot be moved past subsequent writes." fence(Ordering::SeqCst); @@ -259,7 +222,7 @@ impl<'a, C: Channel> Transfer<'a, C> { let this = Self { channel }; #[cfg(dmamux)] - super::dmamux::configure_dmamux(&mut *this.channel, request); + super::dmamux::configure_dmamux(&*this.channel, request); ch.cr().write(|w| w.set_reset(true)); ch.fcr().write(|w| w.0 = 0xFFFF_FFFF); // clear all irqs @@ -311,10 +274,10 @@ impl<'a, C: Channel> Transfer<'a, C> { /// /// This doesn't immediately stop the transfer, you have to wait until [`is_running`](Self::is_running) returns false. pub fn request_stop(&mut self) { - let ch = self.channel.regs().ch(self.channel.num()); - ch.cr().modify(|w| { - w.set_susp(true); - }) + let info = self.channel.info(); + let ch = info.dma.ch(info.num); + + ch.cr().modify(|w| w.set_susp(true)) } /// Return whether this transfer is still running. @@ -322,7 +285,9 @@ impl<'a, C: Channel> Transfer<'a, C> { /// If this returns `false`, it can be because either the transfer finished, or /// it was requested to stop early with [`request_stop`](Self::request_stop). pub fn is_running(&mut self) -> bool { - let ch = self.channel.regs().ch(self.channel.num()); + let info = self.channel.info(); + let ch = info.dma.ch(info.num); + let sr = ch.sr().read(); !sr.tcf() && !sr.suspf() } @@ -330,7 +295,9 @@ impl<'a, C: Channel> Transfer<'a, C> { /// 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().ch(self.channel.num()); + let info = self.channel.info(); + let ch = info.dma.ch(info.num); + ch.br1().read().bndt() } @@ -345,7 +312,7 @@ impl<'a, C: Channel> Transfer<'a, C> { } } -impl<'a, C: Channel> Drop for Transfer<'a, C> { +impl<'a> Drop for Transfer<'a> { fn drop(&mut self) { self.request_stop(); while self.is_running() {} @@ -355,11 +322,12 @@ impl<'a, C: Channel> Drop for Transfer<'a, C> { } } -impl<'a, C: Channel> Unpin for Transfer<'a, C> {} -impl<'a, C: Channel> Future for Transfer<'a, C> { +impl<'a> Unpin for Transfer<'a> {} +impl<'a> Future for Transfer<'a> { type Output = (); fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { - STATE.ch_wakers[self.channel.index()].register(cx.waker()); + let state = &STATE[self.channel.id as usize]; + state.waker.register(cx.waker()); if self.is_running() { Poll::Pending diff --git a/embassy-stm32/src/dma/mod.rs b/embassy-stm32/src/dma/mod.rs index 38945ac33..960483f34 100644 --- a/embassy-stm32/src/dma/mod.rs +++ b/embassy-stm32/src/dma/mod.rs @@ -1,19 +1,10 @@ //! Direct Memory Access (DMA) +#![macro_use] -#[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(any(bdma, dma))] +mod dma_bdma; +#[cfg(any(bdma, dma))] +pub use dma_bdma::*; #[cfg(gpdma)] pub(crate) mod gpdma; @@ -22,16 +13,16 @@ pub use gpdma::*; #[cfg(dmamux)] mod dmamux; +#[cfg(dmamux)] +pub use dmamux::*; pub(crate) mod ringbuffer; pub mod word; use core::mem; -use embassy_hal_internal::impl_peripheral; +use embassy_hal_internal::{impl_peripheral, Peripheral}; -#[cfg(dmamux)] -pub use self::dmamux::*; use crate::interrupt::Priority; #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -41,6 +32,80 @@ enum Dir { PeripheralToMemory, } +/// DMA request type alias. (also known as DMA channel number in some chips) +#[cfg(any(dma_v2, bdma_v2, gpdma, dmamux))] +pub type Request = u8; +/// DMA request type alias. (also known as DMA channel number in some chips) +#[cfg(not(any(dma_v2, bdma_v2, gpdma, dmamux)))] +pub type Request = (); + +pub(crate) mod sealed { + pub trait Channel { + fn id(&self) -> u8; + } + pub trait ChannelInterrupt { + unsafe fn on_irq(); + } +} + +/// DMA channel. +pub trait Channel: sealed::Channel + Peripheral<P = Self> + Into<AnyChannel> + 'static { + /// Type-erase (degrade) this pin into an `AnyChannel`. + /// + /// This converts DMA channel singletons (`DMA1_CH3`, `DMA2_CH1`, ...), which + /// are all different types, into the same type. It is useful for + /// creating arrays of channels, or avoiding generics. + #[inline] + fn degrade(self) -> AnyChannel { + AnyChannel { id: self.id() } + } +} + +macro_rules! dma_channel_impl { + ($channel_peri:ident, $index:expr) => { + impl crate::dma::sealed::Channel for crate::peripherals::$channel_peri { + fn id(&self) -> u8 { + $index + } + } + impl crate::dma::sealed::ChannelInterrupt for crate::peripherals::$channel_peri { + unsafe fn on_irq() { + crate::dma::AnyChannel { id: $index }.on_irq(); + } + } + + impl crate::dma::Channel for crate::peripherals::$channel_peri {} + + impl From<crate::peripherals::$channel_peri> for crate::dma::AnyChannel { + fn from(x: crate::peripherals::$channel_peri) -> Self { + crate::dma::Channel::degrade(x) + } + } + }; +} + +/// Type-erased DMA channel. +pub struct AnyChannel { + pub(crate) id: u8, +} +impl_peripheral!(AnyChannel); + +impl AnyChannel { + fn info(&self) -> &ChannelInfo { + &crate::_generated::DMA_CHANNELS[self.id as usize] + } +} + +impl sealed::Channel for AnyChannel { + fn id(&self) -> u8 { + self.id + } +} +impl Channel for AnyChannel {} + +const CHANNEL_COUNT: usize = crate::_generated::DMA_CHANNELS.len(); +static STATE: [ChannelState; CHANNEL_COUNT] = [ChannelState::NEW; CHANNEL_COUNT]; + /// "No DMA" placeholder. /// /// You may pass this in place of a real DMA channel when creating a driver @@ -70,10 +135,14 @@ pub(crate) unsafe fn init( #[cfg(dma)] dma_priority: Priority, #[cfg(gpdma)] gpdma_priority: Priority, ) { - #[cfg(bdma)] - bdma::init(cs, bdma_priority); - #[cfg(dma)] - dma::init(cs, dma_priority); + #[cfg(any(dma, bdma))] + dma_bdma::init( + cs, + #[cfg(dma)] + dma_priority, + #[cfg(bdma)] + bdma_priority, + ); #[cfg(gpdma)] gpdma::init(cs, gpdma_priority); #[cfg(dmamux)] diff --git a/embassy-stm32/src/dma/ringbuffer.rs b/embassy-stm32/src/dma/ringbuffer.rs index c9f7a3026..23f1d67d5 100644 --- a/embassy-stm32/src/dma/ringbuffer.rs +++ b/embassy-stm32/src/dma/ringbuffer.rs @@ -37,6 +37,7 @@ pub struct ReadableDmaRingBuffer<'a, W: Word> { } #[derive(Debug, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct OverrunError; pub trait DmaCtrl { @@ -263,6 +264,17 @@ impl<'a, W: Word> WritableDmaRingBuffer<'a, W> { self.cap() - dma.get_remaining_transfers() } + /// Write elements directly to the buffer. This must be done before the DMA is started + /// or after the buffer has been cleared using `clear()`. + pub fn write_immediate(&mut self, buffer: &[W]) -> Result<(usize, usize), OverrunError> { + if self.end != 0 { + return Err(OverrunError); + } + let written = self.copy_from(buffer, 0..self.cap()); + self.end = written % self.cap(); + Ok((written, self.cap() - written)) + } + /// Write an exact number of elements to the ringbuffer. pub async fn write_exact(&mut self, dma: &mut impl DmaCtrl, buffer: &[W]) -> Result<usize, OverrunError> { let mut written_data = 0; diff --git a/embassy-stm32/src/eth/mod.rs b/embassy-stm32/src/eth/mod.rs index 448405507..71fe09c3f 100644 --- a/embassy-stm32/src/eth/mod.rs +++ b/embassy-stm32/src/eth/mod.rs @@ -13,6 +13,7 @@ use embassy_net_driver::{Capabilities, HardwareAddress, LinkState}; use embassy_sync::waitqueue::AtomicWaker; pub use self::_version::{InterruptHandler, *}; +use crate::rcc::RccPeripheral; #[allow(unused)] const MTU: usize = 1514; @@ -183,7 +184,7 @@ pub(crate) mod sealed { } /// Ethernet instance. -pub trait Instance: sealed::Instance + Send + 'static {} +pub trait Instance: sealed::Instance + RccPeripheral + Send + 'static {} impl sealed::Instance for crate::peripherals::ETH { fn regs() -> crate::pac::eth::Eth { @@ -192,12 +193,19 @@ impl sealed::Instance for crate::peripherals::ETH { } impl Instance for crate::peripherals::ETH {} +pin_trait!(RXClkPin, Instance); +pin_trait!(TXClkPin, Instance); pin_trait!(RefClkPin, Instance); pin_trait!(MDIOPin, Instance); pin_trait!(MDCPin, Instance); +pin_trait!(RXDVPin, Instance); pin_trait!(CRSPin, Instance); pin_trait!(RXD0Pin, Instance); pin_trait!(RXD1Pin, Instance); +pin_trait!(RXD2Pin, Instance); +pin_trait!(RXD3Pin, Instance); pin_trait!(TXD0Pin, Instance); pin_trait!(TXD1Pin, Instance); +pin_trait!(TXD2Pin, Instance); +pin_trait!(TXD3Pin, Instance); pin_trait!(TXEnPin, Instance); diff --git a/embassy-stm32/src/eth/v1/mod.rs b/embassy-stm32/src/eth/v1/mod.rs index 2ce5b3927..e5b7b0452 100644 --- a/embassy-stm32/src/eth/v1/mod.rs +++ b/embassy-stm32/src/eth/v1/mod.rs @@ -20,6 +20,7 @@ use crate::pac::AFIO; #[cfg(any(eth_v1b, eth_v1c))] use crate::pac::SYSCFG; use crate::pac::{ETH, RCC}; +use crate::rcc::sealed::RccPeripheral; use crate::{interrupt, Peripheral}; /// Interrupt handler. @@ -191,8 +192,7 @@ impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> { // 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 = unsafe { crate::rcc::get_freqs() }.hclk1; + let hclk = <T as RccPeripheral>::frequency(); let hclk_mhz = hclk.0 / 1_000_000; // Set the MDC clock frequency in the range 1MHz - 2.5MHz diff --git a/embassy-stm32/src/eth/v2/descriptors.rs b/embassy-stm32/src/eth/v2/descriptors.rs index 01ea8e574..645bfdb14 100644 --- a/embassy-stm32/src/eth/v2/descriptors.rs +++ b/embassy-stm32/src/eth/v2/descriptors.rs @@ -129,7 +129,7 @@ impl<'a> TDesRing<'a> { /// Receive Descriptor representation /// -/// * rdes0: recieve buffer address +/// * rdes0: receive buffer address /// * rdes1: /// * rdes2: /// * rdes3: OWN and Status diff --git a/embassy-stm32/src/eth/v2/mod.rs b/embassy-stm32/src/eth/v2/mod.rs index 59745cba0..8d69561d4 100644 --- a/embassy-stm32/src/eth/v2/mod.rs +++ b/embassy-stm32/src/eth/v2/mod.rs @@ -11,6 +11,7 @@ use crate::gpio::sealed::{AFType, Pin as _}; use crate::gpio::{AnyPin, Speed}; use crate::interrupt::InterruptExt; use crate::pac::ETH; +use crate::rcc::sealed::RccPeripheral; use crate::{interrupt, Peripheral}; /// Interrupt handler. @@ -39,12 +40,18 @@ 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], + pins: Pins<'d>, pub(crate) phy: P, pub(crate) station_management: EthernetStationManagement<T>, pub(crate) mac_addr: [u8; 6], } +/// Pins of ethernet driver. +enum Pins<'d> { + Rmii([PeripheralRef<'d, AnyPin>; 9]), + Mii([PeripheralRef<'d, AnyPin>; 14]), +} + macro_rules! config_pins { ($($pin:ident),*) => { critical_section::with(|_| { @@ -57,11 +64,11 @@ macro_rules! config_pins { } impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> { - /// Create a new Ethernet driver. + /// Create a new RMII ethernet driver using 9 pins. pub fn new<const TX: usize, const RX: usize>( queue: &'d mut PacketQueue<TX, RX>, peri: impl Peripheral<P = T> + 'd, - _irq: impl interrupt::typelevel::Binding<interrupt::typelevel::ETH, InterruptHandler> + 'd, + irq: impl interrupt::typelevel::Binding<interrupt::typelevel::ETH, InterruptHandler> + 'd, ref_clk: impl Peripheral<P = impl RefClkPin<T>> + 'd, mdio: impl Peripheral<P = impl MDIOPin<T>> + 'd, mdc: impl Peripheral<P = impl MDCPin<T>> + 'd, @@ -74,8 +81,6 @@ impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> { phy: P, mac_addr: [u8; 6], ) -> Self { - into_ref!(peri, ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en); - // Enable the necessary Clocks #[cfg(not(rcc_h5))] critical_section::with(|_| { @@ -85,7 +90,6 @@ impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> { w.set_eth1rxen(true); }); - // RMII crate::pac::SYSCFG.pmcr().modify(|w| w.set_epis(0b100)); }); @@ -99,14 +103,110 @@ impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> { w.set_ethrxen(true); }); - // RMII crate::pac::SYSCFG .pmcr() .modify(|w| w.set_eth_sel_phy(crate::pac::syscfg::vals::EthSelPhy::B_0X4)); }); + into_ref!(ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en); config_pins!(ref_clk, mdio, mdc, crs, rx_d0, rx_d1, tx_d0, tx_d1, tx_en); + let pins = Pins::Rmii([ + ref_clk.map_into(), + mdio.map_into(), + mdc.map_into(), + crs.map_into(), + rx_d0.map_into(), + rx_d1.map_into(), + tx_d0.map_into(), + tx_d1.map_into(), + tx_en.map_into(), + ]); + + Self::new_inner(queue, peri, irq, pins, phy, mac_addr) + } + + /// Create a new MII ethernet driver using 14 pins. + pub fn new_mii<const TX: usize, const RX: usize>( + queue: &'d mut PacketQueue<TX, RX>, + peri: impl Peripheral<P = T> + 'd, + irq: impl interrupt::typelevel::Binding<interrupt::typelevel::ETH, InterruptHandler> + 'd, + rx_clk: impl Peripheral<P = impl RXClkPin<T>> + 'd, + tx_clk: impl Peripheral<P = impl TXClkPin<T>> + 'd, + mdio: impl Peripheral<P = impl MDIOPin<T>> + 'd, + mdc: impl Peripheral<P = impl MDCPin<T>> + 'd, + rxdv: impl Peripheral<P = impl RXDVPin<T>> + 'd, + rx_d0: impl Peripheral<P = impl RXD0Pin<T>> + 'd, + rx_d1: impl Peripheral<P = impl RXD1Pin<T>> + 'd, + rx_d2: impl Peripheral<P = impl RXD2Pin<T>> + 'd, + rx_d3: impl Peripheral<P = impl RXD3Pin<T>> + 'd, + tx_d0: impl Peripheral<P = impl TXD0Pin<T>> + 'd, + tx_d1: impl Peripheral<P = impl TXD1Pin<T>> + 'd, + tx_d2: impl Peripheral<P = impl TXD2Pin<T>> + 'd, + tx_d3: impl Peripheral<P = impl TXD3Pin<T>> + 'd, + tx_en: impl Peripheral<P = impl TXEnPin<T>> + 'd, + phy: P, + mac_addr: [u8; 6], + ) -> Self { + // Enable necessary clocks. + #[cfg(not(rcc_h5))] + critical_section::with(|_| { + crate::pac::RCC.ahb1enr().modify(|w| { + w.set_eth1macen(true); + w.set_eth1txen(true); + w.set_eth1rxen(true); + }); + + crate::pac::SYSCFG.pmcr().modify(|w| w.set_epis(0b000)); + }); + + #[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); + }); + + // TODO: This is for RMII - what would MII need here? + crate::pac::SYSCFG + .pmcr() + .modify(|w| w.set_eth_sel_phy(crate::pac::syscfg::vals::EthSelPhy::B_0X4)); + }); + + into_ref!(rx_clk, tx_clk, mdio, mdc, rxdv, rx_d0, rx_d1, rx_d2, rx_d3, tx_d0, tx_d1, tx_d2, tx_d3, tx_en); + config_pins!(rx_clk, tx_clk, mdio, mdc, rxdv, rx_d0, rx_d1, rx_d2, rx_d3, tx_d0, tx_d1, tx_d2, tx_d3, tx_en); + + let pins = Pins::Mii([ + rx_clk.map_into(), + tx_clk.map_into(), + mdio.map_into(), + mdc.map_into(), + rxdv.map_into(), + rx_d0.map_into(), + rx_d1.map_into(), + rx_d2.map_into(), + rx_d3.map_into(), + tx_d0.map_into(), + tx_d1.map_into(), + tx_d2.map_into(), + tx_d3.map_into(), + tx_en.map_into(), + ]); + + Self::new_inner(queue, peri, irq, pins, phy, mac_addr) + } + + fn new_inner<const TX: usize, const RX: usize>( + queue: &'d mut PacketQueue<TX, RX>, + peri: impl Peripheral<P = T> + 'd, + _irq: impl interrupt::typelevel::Binding<interrupt::typelevel::ETH, InterruptHandler> + 'd, + pins: Pins<'d>, + phy: P, + mac_addr: [u8; 6], + ) -> Self { let dma = ETH.ethernet_dma(); let mac = ETH.ethernet_mac(); let mtl = ETH.ethernet_mtl(); @@ -165,8 +265,7 @@ impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> { w.set_rbsz(RX_BUFFER_SIZE as u16); }); - // NOTE(unsafe) We got the peripheral singleton, which means that `rcc::init` was called - let hclk = unsafe { crate::rcc::get_freqs() }.hclk1; + let hclk = <T as RccPeripheral>::frequency(); let hclk_mhz = hclk.0 / 1_000_000; // Set the MDC clock frequency in the range 1MHz - 2.5MHz @@ -182,24 +281,12 @@ impl<'d, T: Instance, P: PHY> Ethernet<'d, T, P> { } }; - let pins = [ - ref_clk.map_into(), - mdio.map_into(), - mdc.map_into(), - crs.map_into(), - rx_d0.map_into(), - rx_d1.map_into(), - tx_d0.map_into(), - tx_d1.map_into(), - tx_en.map_into(), - ]; - let mut this = Self { - _peri: peri, + _peri: peri.into_ref(), 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, + phy, station_management: EthernetStationManagement { peri: PhantomData, clock_range: clock_range, @@ -302,7 +389,10 @@ impl<'d, T: Instance, P: PHY> Drop for Ethernet<'d, T, P> { dma.dmacrx_cr().modify(|w| w.set_sr(false)); critical_section::with(|_| { - for pin in self.pins.iter_mut() { + for pin in match self.pins { + Pins::Rmii(ref mut pins) => pins.iter_mut(), + Pins::Mii(ref mut pins) => pins.iter_mut(), + } { pin.set_as_disconnected(); } }) diff --git a/embassy-stm32/src/exti.rs b/embassy-stm32/src/exti.rs index 2821435ef..bd10ba158 100644 --- a/embassy-stm32/src/exti.rs +++ b/embassy-stm32/src/exti.rs @@ -5,10 +5,10 @@ use core::marker::PhantomData; use core::pin::Pin; use core::task::{Context, Poll}; -use embassy_hal_internal::impl_peripheral; +use embassy_hal_internal::{impl_peripheral, into_ref}; use embassy_sync::waitqueue::AtomicWaker; -use crate::gpio::{AnyPin, Input, Level, Pin as GpioPin}; +use crate::gpio::{AnyPin, Input, Level, Pin as GpioPin, Pull}; use crate::pac::exti::regs::Lines; use crate::pac::EXTI; use crate::{interrupt, pac, peripherals, Peripheral}; @@ -93,16 +93,27 @@ impl Iterator for BitIter { /// EXTI channel, which is a limited resource. /// /// Pins PA5, PB5, PC5... all use EXTI channel 5, so you can't use EXTI on, say, PA5 and PC5 at the same time. -pub struct ExtiInput<'d, T: GpioPin> { - pin: Input<'d, T>, +pub struct ExtiInput<'d> { + pin: Input<'d>, } -impl<'d, T: GpioPin> Unpin for ExtiInput<'d, T> {} +impl<'d> Unpin for ExtiInput<'d> {} -impl<'d, T: GpioPin> ExtiInput<'d, T> { +impl<'d> ExtiInput<'d> { /// Create an EXTI input. - pub fn new(pin: Input<'d, T>, _ch: impl Peripheral<P = T::ExtiChannel> + 'd) -> Self { - Self { pin } + pub fn new<T: GpioPin>( + pin: impl Peripheral<P = T> + 'd, + ch: impl Peripheral<P = T::ExtiChannel> + 'd, + pull: Pull, + ) -> Self { + into_ref!(pin, ch); + + // Needed if using AnyPin+AnyChannel. + assert_eq!(pin.pin(), ch.number()); + + Self { + pin: Input::new(pin, pull), + } } /// Get whether the pin is high. @@ -162,7 +173,7 @@ impl<'d, T: GpioPin> ExtiInput<'d, T> { } } -impl<'d, T: GpioPin> embedded_hal_02::digital::v2::InputPin for ExtiInput<'d, T> { +impl<'d> embedded_hal_02::digital::v2::InputPin for ExtiInput<'d> { type Error = Infallible; fn is_high(&self) -> Result<bool, Self::Error> { @@ -174,11 +185,11 @@ impl<'d, T: GpioPin> embedded_hal_02::digital::v2::InputPin for ExtiInput<'d, T> } } -impl<'d, T: GpioPin> embedded_hal_1::digital::ErrorType for ExtiInput<'d, T> { +impl<'d> embedded_hal_1::digital::ErrorType for ExtiInput<'d> { type Error = Infallible; } -impl<'d, T: GpioPin> embedded_hal_1::digital::InputPin for ExtiInput<'d, T> { +impl<'d> embedded_hal_1::digital::InputPin for ExtiInput<'d> { fn is_high(&mut self) -> Result<bool, Self::Error> { Ok((*self).is_high()) } @@ -188,7 +199,7 @@ impl<'d, T: GpioPin> embedded_hal_1::digital::InputPin for ExtiInput<'d, T> { } } -impl<'d, T: GpioPin> embedded_hal_async::digital::Wait for ExtiInput<'d, T> { +impl<'d> embedded_hal_async::digital::Wait for ExtiInput<'d> { async fn wait_for_high(&mut self) -> Result<(), Self::Error> { self.wait_for_high().await; Ok(()) @@ -326,7 +337,7 @@ pub(crate) mod sealed { /// EXTI channel trait. pub trait Channel: sealed::Channel + Sized { /// Get the EXTI channel number. - fn number(&self) -> usize; + fn number(&self) -> u8; /// Type-erase (degrade) this channel into an `AnyChannel`. /// @@ -350,8 +361,8 @@ pub struct AnyChannel { impl_peripheral!(AnyChannel); impl sealed::Channel for AnyChannel {} impl Channel for AnyChannel { - fn number(&self) -> usize { - self.number as usize + fn number(&self) -> u8 { + self.number } } @@ -359,8 +370,8 @@ macro_rules! impl_exti { ($type:ident, $number:expr) => { impl sealed::Channel for peripherals::$type {} impl Channel for peripherals::$type { - fn number(&self) -> usize { - $number as usize + fn number(&self) -> u8 { + $number } } }; diff --git a/embassy-stm32/src/flash/h50.rs b/embassy-stm32/src/flash/h50.rs new file mode 100644 index 000000000..db05bef5d --- /dev/null +++ b/embassy-stm32/src/flash/h50.rs @@ -0,0 +1,124 @@ +/// STM32H50 series flash impl. See RM0492 +use core::{ + ptr::write_volatile, + sync::atomic::{fence, Ordering}, +}; + +use cortex_m::interrupt; +use pac::flash::regs::Nssr; +use pac::flash::vals::Bksel; + +use super::{Error, FlashBank, FlashRegion, FlashSector, FLASH_REGIONS, WRITE_SIZE}; +use crate::pac; + +pub(crate) const fn is_default_layout() -> bool { + true +} + +pub(crate) const fn get_flash_regions() -> &'static [&'static FlashRegion] { + &FLASH_REGIONS +} + +pub(crate) unsafe fn lock() { + pac::FLASH.nscr().modify(|w| w.set_lock(true)); +} + +pub(crate) unsafe fn unlock() { + while busy() {} + + if pac::FLASH.nscr().read().lock() { + pac::FLASH.nskeyr().write_value(0x4567_0123); + pac::FLASH.nskeyr().write_value(0xCDEF_89AB); + } +} + +pub(crate) unsafe fn enable_blocking_write() { + assert_eq!(0, WRITE_SIZE % 4); + pac::FLASH.nscr().write(|w| w.set_pg(true)); +} + +pub(crate) unsafe fn disable_blocking_write() { + pac::FLASH.nscr().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 val in buf.chunks(4) { + write_volatile(address as *mut u32, u32::from_le_bytes(val.try_into().unwrap())); + address += val.len() as u32; + + // prevents parallelism errors + fence(Ordering::SeqCst); + } + + wait_ready_blocking() +} + +pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), Error> { + assert!(sector.bank != FlashBank::Otp); + assert!(sector.index_in_bank < 8); + + while busy() {} + + interrupt::free(|_| { + pac::FLASH.nscr().modify(|w| { + w.set_bksel(match sector.bank { + FlashBank::Bank1 => Bksel::B_0X0, + FlashBank::Bank2 => Bksel::B_0X1, + _ => unreachable!(), + }); + w.set_snb(sector.index_in_bank); + w.set_ser(true); + w.set_strt(true); + }) + }); + + let ret = wait_ready_blocking(); + pac::FLASH.nscr().modify(|w| w.set_ser(false)); + ret +} + +pub(crate) unsafe fn wait_ready_blocking() -> Result<(), Error> { + loop { + let sr = pac::FLASH.nssr().read(); + + if !sr_busy(sr) { + if sr.wrperr() { + return Err(Error::Protected); + } + if sr.pgserr() { + return Err(Error::Seq); + } + if sr.strberr() { + // writing several times to the same byte in the write buffer + return Err(Error::Prog); + } + if sr.incerr() { + // attempting write operation before completion of previous + // write operation + return Err(Error::Seq); + } + + return Ok(()); + } + } +} + +pub(crate) unsafe fn clear_all_err() { + pac::FLASH.nsccr().modify(|w| { + w.set_clr_wrperr(true); + w.set_clr_pgserr(true); + w.set_clr_strberr(true); + w.set_clr_incerr(true); + }) +} + +fn sr_busy(sr: Nssr) -> bool { + // Note: RM0492 sometimes incorrectly refers to WBNE as NSWBNE + sr.bsy() || sr.dbne() || sr.wbne() +} + +fn busy() -> bool { + let sr = pac::FLASH.nssr().read(); + sr_busy(sr) +} diff --git a/embassy-stm32/src/flash/h7.rs b/embassy-stm32/src/flash/h7.rs index ae395d568..743925e17 100644 --- a/embassy-stm32/src/flash/h7.rs +++ b/embassy-stm32/src/flash/h7.rs @@ -77,12 +77,12 @@ pub(crate) unsafe fn blocking_write(start_address: u32, buf: &[u8; WRITE_SIZE]) } } - bank.cr().write(|w| w.set_pg(false)); - cortex_m::asm::isb(); cortex_m::asm::dsb(); fence(Ordering::SeqCst); + bank.cr().write(|w| w.set_pg(false)); + res.unwrap() } @@ -100,6 +100,10 @@ pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), E w.set_start(true); }); + cortex_m::asm::isb(); + cortex_m::asm::dsb(); + fence(Ordering::SeqCst); + let ret: Result<(), Error> = blocking_wait_ready(bank); bank.cr().modify(|w| w.set_ser(false)); bank_clear_all_err(bank); diff --git a/embassy-stm32/src/flash/mod.rs b/embassy-stm32/src/flash/mod.rs index 47232f4a4..1d8031e82 100644 --- a/embassy-stm32/src/flash/mod.rs +++ b/embassy-stm32/src/flash/mod.rs @@ -101,10 +101,12 @@ pub enum FlashBank { #[cfg_attr(any(flash_g0, flash_g4), path = "g.rs")] #[cfg_attr(flash_h7, path = "h7.rs")] #[cfg_attr(flash_h7ab, path = "h7.rs")] +#[cfg_attr(flash_u5, path = "u5.rs")] +#[cfg_attr(flash_h50, path = "h50.rs")] #[cfg_attr( not(any( flash_l0, flash_l1, flash_l4, flash_wl, flash_wb, flash_f0, flash_f1, flash_f3, flash_f4, flash_f7, flash_g0, - flash_g4, flash_h7, flash_h7ab + flash_g4, flash_h7, flash_h7ab, flash_u5, flash_h50 )), path = "other.rs" )] diff --git a/embassy-stm32/src/flash/u5.rs b/embassy-stm32/src/flash/u5.rs new file mode 100644 index 000000000..3787082f9 --- /dev/null +++ b/embassy-stm32/src/flash/u5.rs @@ -0,0 +1,105 @@ +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(crate) const fn is_default_layout() -> bool { + true +} + +pub(crate) const fn get_flash_regions() -> &'static [&'static FlashRegion] { + &FLASH_REGIONS +} + +pub(crate) unsafe fn lock() { + pac::FLASH.seccr().modify(|w| w.set_lock(true)); +} + +pub(crate) unsafe fn unlock() { + if pac::FLASH.seccr().read().lock() { + pac::FLASH.seckeyr().write_value(0x4567_0123); + pac::FLASH.seckeyr().write_value(0xCDEF_89AB); + } +} + +pub(crate) unsafe fn enable_blocking_write() { + assert_eq!(0, WRITE_SIZE % 4); + + pac::FLASH.seccr().write(|w| { + w.set_pg(pac::flash::vals::SeccrPg::B_0X1); + }); +} + +pub(crate) unsafe fn disable_blocking_write() { + pac::FLASH.seccr().write(|w| w.set_pg(pac::flash::vals::SeccrPg::B_0X0)); +} + +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; + + // prevents parallelism errors + fence(Ordering::SeqCst); + } + + blocking_wait_ready() +} + +pub(crate) unsafe fn blocking_erase_sector(sector: &FlashSector) -> Result<(), Error> { + pac::FLASH.seccr().modify(|w| { + w.set_per(pac::flash::vals::SeccrPer::B_0X1); + w.set_pnb(sector.index_in_bank) + }); + + pac::FLASH.seccr().modify(|w| { + w.set_strt(true); + }); + + let ret: Result<(), Error> = blocking_wait_ready(); + pac::FLASH + .seccr() + .modify(|w| w.set_per(pac::flash::vals::SeccrPer::B_0X0)); + clear_all_err(); + ret +} + +pub(crate) unsafe fn clear_all_err() { + // read and write back the same value. + // This clears all "write 1 to clear" bits. + pac::FLASH.secsr().modify(|_| {}); +} + +unsafe fn blocking_wait_ready() -> Result<(), Error> { + loop { + let sr = pac::FLASH.secsr().read(); + + if !sr.bsy() { + if sr.pgserr() { + return Err(Error::Seq); + } + + if sr.sizerr() { + return Err(Error::Size); + } + + if sr.pgaerr() { + return Err(Error::Unaligned); + } + + if sr.wrperr() { + return Err(Error::Protected); + } + + if sr.progerr() { + return Err(Error::Prog); + } + + return Ok(()); + } + } +} diff --git a/embassy-stm32/src/fmc.rs b/embassy-stm32/src/fmc.rs index 873c8a70c..9d731a512 100644 --- a/embassy-stm32/src/fmc.rs +++ b/embassy-stm32/src/fmc.rs @@ -36,8 +36,10 @@ where // 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)))] + #[cfg(not(any(fmc_v1x3, fmc_v2x1, fsmc_v1x0, fsmc_v1x3, fsmc_v2x3, fsmc_v3x1, fmc_v4)))] T::REGS.bcr1().modify(|r| r.set_fmcen(true)); + #[cfg(any(fmc_v4))] + T::REGS.nor_psram().bcr1().modify(|r| r.set_fmcen(true)); } /// Get the kernel clock currently in use for this FMC instance. @@ -60,8 +62,10 @@ where // 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)))] + #[cfg(not(any(fmc_v1x3, fmc_v2x1, fsmc_v1x0, fsmc_v1x3, fsmc_v2x3, fsmc_v3x1, fmc_v4)))] T::REGS.bcr1().modify(|r| r.set_fmcen(true)); + #[cfg(any(fmc_v4))] + T::REGS.nor_psram().bcr1().modify(|r| r.set_fmcen(true)); } fn source_clock_hz(&self) -> u32 { diff --git a/embassy-stm32/src/gpio.rs b/embassy-stm32/src/gpio.rs index 7eb70496f..00e3e1727 100644 --- a/embassy-stm32/src/gpio.rs +++ b/embassy-stm32/src/gpio.rs @@ -6,6 +6,7 @@ use core::convert::Infallible; use critical_section::CriticalSection; use embassy_hal_internal::{impl_peripheral, into_ref, PeripheralRef}; +use self::sealed::Pin as _; use crate::pac::gpio::{self, vals}; use crate::{pac, peripherals, Peripheral}; @@ -14,41 +15,21 @@ use crate::{pac, peripherals, Peripheral}; /// This pin can either be a disconnected, input, or output pin, or both. The level register bit will remain /// set while not in output mode, so the pin's level will be 'remembered' when it is not in output /// mode. -pub struct Flex<'d, T: Pin> { - pub(crate) pin: PeripheralRef<'d, T>, +pub struct Flex<'d> { + pub(crate) pin: PeripheralRef<'d, AnyPin>, } -impl<'d, T: Pin> Flex<'d, T> { +impl<'d> Flex<'d> { /// Wrap the pin in a `Flex`. /// /// The pin remains disconnected. The initial output level is unspecified, but can be changed /// before the pin is put into output mode. /// #[inline] - pub fn new(pin: impl Peripheral<P = T> + 'd) -> Self { + pub fn new(pin: impl Peripheral<P = impl Pin> + 'd) -> Self { into_ref!(pin); // Pin will be in disconnected state. - Self { pin } - } - - /// Type-erase (degrade) this pin into an `AnyPin`. - /// - /// This converts pin singletons (`PA5`, `PB6`, ...), which - /// are all different types, into the same type. It is useful for - /// creating arrays of pins, or avoiding generics. - #[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::<AnyPin>(), - } + Self { pin: pin.map_into() } } /// Put the pin into input mode. @@ -218,7 +199,7 @@ impl<'d, T: Pin> Flex<'d, T> { } } -impl<'d, T: Pin> Drop for Flex<'d, T> { +impl<'d> Drop for Flex<'d> { #[inline] fn drop(&mut self) { critical_section::with(|_| { @@ -268,7 +249,7 @@ impl From<Pull> for vals::Pupdr { /// Speed settings /// -/// These vary dpeending on the chip, ceck the reference manual or datasheet for details. +/// These vary depending on the chip, check the reference manual or datasheet for details. #[allow(missing_docs)] #[derive(Debug, Copy, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -309,31 +290,19 @@ impl From<Speed> for vals::Ospeedr { } /// GPIO input driver. -pub struct Input<'d, T: Pin> { - pub(crate) pin: Flex<'d, T>, +pub struct Input<'d> { + pub(crate) pin: Flex<'d>, } -impl<'d, T: Pin> Input<'d, T> { +impl<'d> Input<'d> { /// Create GPIO input driver for a [Pin] with the provided [Pull] configuration. #[inline] - pub fn new(pin: impl Peripheral<P = T> + 'd, pull: Pull) -> Self { + pub fn new(pin: impl Peripheral<P = impl Pin> + 'd, pull: Pull) -> Self { let mut pin = Flex::new(pin); pin.set_as_input(pull); Self { pin } } - /// Type-erase (degrade) this pin into an `AnyPin`. - /// - /// This converts pin singletons (`PA5`, `PB6`, ...), which - /// are all different types, into the same type. It is useful for - /// creating arrays of pins, or avoiding generics. - #[inline] - pub fn degrade(self) -> Input<'d, AnyPin> { - Input { - pin: self.pin.degrade(), - } - } - /// Get whether the pin input level is high. #[inline] pub fn is_high(&self) -> bool { @@ -386,14 +355,14 @@ impl From<Level> for bool { /// Note that pins will **return to their floating state** when `Output` is dropped. /// If pins should retain their state indefinitely, either keep ownership of the /// `Output`, or pass it to [`core::mem::forget`]. -pub struct Output<'d, T: Pin> { - pub(crate) pin: Flex<'d, T>, +pub struct Output<'d> { + pub(crate) pin: Flex<'d>, } -impl<'d, T: Pin> Output<'d, T> { +impl<'d> Output<'d> { /// Create GPIO output driver for a [Pin] with the provided [Level] and [Speed] configuration. #[inline] - pub fn new(pin: impl Peripheral<P = T> + 'd, initial_output: Level, speed: Speed) -> Self { + pub fn new(pin: impl Peripheral<P = impl Pin> + 'd, initial_output: Level, speed: Speed) -> Self { let mut pin = Flex::new(pin); match initial_output { Level::High => pin.set_high(), @@ -403,18 +372,6 @@ impl<'d, T: Pin> Output<'d, T> { Self { pin } } - /// Type-erase (degrade) this pin into an `AnyPin`. - /// - /// This converts pin singletons (`PA5`, `PB6`, ...), which - /// are all different types, into the same type. It is useful for - /// creating arrays of pins, or avoiding generics. - #[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) { @@ -463,14 +420,14 @@ impl<'d, T: Pin> Output<'d, T> { /// Note that pins will **return to their floating state** when `OutputOpenDrain` is dropped. /// If pins should retain their state indefinitely, either keep ownership of the /// `OutputOpenDrain`, or pass it to [`core::mem::forget`]. -pub struct OutputOpenDrain<'d, T: Pin> { - pub(crate) pin: Flex<'d, T>, +pub struct OutputOpenDrain<'d> { + pub(crate) pin: Flex<'d>, } -impl<'d, T: Pin> OutputOpenDrain<'d, T> { +impl<'d> OutputOpenDrain<'d> { /// Create a new GPIO open drain output driver for a [Pin] with the provided [Level] and [Speed], [Pull] configuration. #[inline] - pub fn new(pin: impl Peripheral<P = T> + 'd, initial_output: Level, speed: Speed, pull: Pull) -> Self { + pub fn new(pin: impl Peripheral<P = impl Pin> + 'd, initial_output: Level, speed: Speed, pull: Pull) -> Self { let mut pin = Flex::new(pin); match initial_output { @@ -482,18 +439,6 @@ impl<'d, T: Pin> OutputOpenDrain<'d, T> { Self { pin } } - /// Type-erase (degrade) this pin into an `AnyPin`. - /// - /// This converts pin singletons (`PA5`, `PB6`, ...), which - /// are all different types, into the same type. It is useful for - /// creating arrays of pins, or avoiding generics. - #[inline] - pub fn degrade(self) -> Output<'d, AnyPin> { - Output { - pin: self.pin.degrade(), - } - } - /// Get whether the pin input level is high. #[inline] pub fn is_high(&self) -> bool { @@ -836,7 +781,7 @@ pub(crate) unsafe fn init(_cs: CriticalSection) { }); } -impl<'d, T: Pin> embedded_hal_02::digital::v2::InputPin for Input<'d, T> { +impl<'d> embedded_hal_02::digital::v2::InputPin for Input<'d> { type Error = Infallible; #[inline] @@ -850,7 +795,7 @@ impl<'d, T: Pin> embedded_hal_02::digital::v2::InputPin for Input<'d, T> { } } -impl<'d, T: Pin> embedded_hal_02::digital::v2::OutputPin for Output<'d, T> { +impl<'d> embedded_hal_02::digital::v2::OutputPin for Output<'d> { type Error = Infallible; #[inline] @@ -866,7 +811,7 @@ impl<'d, T: Pin> embedded_hal_02::digital::v2::OutputPin for Output<'d, T> { } } -impl<'d, T: Pin> embedded_hal_02::digital::v2::StatefulOutputPin for Output<'d, T> { +impl<'d> embedded_hal_02::digital::v2::StatefulOutputPin for Output<'d> { #[inline] fn is_set_high(&self) -> Result<bool, Self::Error> { Ok(self.is_set_high()) @@ -879,7 +824,7 @@ impl<'d, T: Pin> embedded_hal_02::digital::v2::StatefulOutputPin for Output<'d, } } -impl<'d, T: Pin> embedded_hal_02::digital::v2::ToggleableOutputPin for Output<'d, T> { +impl<'d> embedded_hal_02::digital::v2::ToggleableOutputPin for Output<'d> { type Error = Infallible; #[inline] fn toggle(&mut self) -> Result<(), Self::Error> { @@ -888,7 +833,7 @@ impl<'d, T: Pin> embedded_hal_02::digital::v2::ToggleableOutputPin for Output<'d } } -impl<'d, T: Pin> embedded_hal_02::digital::v2::OutputPin for OutputOpenDrain<'d, T> { +impl<'d> embedded_hal_02::digital::v2::OutputPin for OutputOpenDrain<'d> { type Error = Infallible; #[inline] @@ -904,7 +849,7 @@ impl<'d, T: Pin> embedded_hal_02::digital::v2::OutputPin for OutputOpenDrain<'d, } } -impl<'d, T: Pin> embedded_hal_02::digital::v2::StatefulOutputPin for OutputOpenDrain<'d, T> { +impl<'d> embedded_hal_02::digital::v2::StatefulOutputPin for OutputOpenDrain<'d> { #[inline] fn is_set_high(&self) -> Result<bool, Self::Error> { Ok(self.is_set_high()) @@ -917,7 +862,7 @@ impl<'d, T: Pin> embedded_hal_02::digital::v2::StatefulOutputPin for OutputOpenD } } -impl<'d, T: Pin> embedded_hal_02::digital::v2::ToggleableOutputPin for OutputOpenDrain<'d, T> { +impl<'d> embedded_hal_02::digital::v2::ToggleableOutputPin for OutputOpenDrain<'d> { type Error = Infallible; #[inline] fn toggle(&mut self) -> Result<(), Self::Error> { @@ -926,7 +871,7 @@ impl<'d, T: Pin> embedded_hal_02::digital::v2::ToggleableOutputPin for OutputOpe } } -impl<'d, T: Pin> embedded_hal_02::digital::v2::InputPin for Flex<'d, T> { +impl<'d> embedded_hal_02::digital::v2::InputPin for Flex<'d> { type Error = Infallible; #[inline] @@ -940,7 +885,7 @@ impl<'d, T: Pin> embedded_hal_02::digital::v2::InputPin for Flex<'d, T> { } } -impl<'d, T: Pin> embedded_hal_02::digital::v2::OutputPin for Flex<'d, T> { +impl<'d> embedded_hal_02::digital::v2::OutputPin for Flex<'d> { type Error = Infallible; #[inline] @@ -956,7 +901,7 @@ impl<'d, T: Pin> embedded_hal_02::digital::v2::OutputPin for Flex<'d, T> { } } -impl<'d, T: Pin> embedded_hal_02::digital::v2::StatefulOutputPin for Flex<'d, T> { +impl<'d> embedded_hal_02::digital::v2::StatefulOutputPin for Flex<'d> { #[inline] fn is_set_high(&self) -> Result<bool, Self::Error> { Ok(self.is_set_high()) @@ -969,7 +914,7 @@ impl<'d, T: Pin> embedded_hal_02::digital::v2::StatefulOutputPin for Flex<'d, T> } } -impl<'d, T: Pin> embedded_hal_02::digital::v2::ToggleableOutputPin for Flex<'d, T> { +impl<'d> embedded_hal_02::digital::v2::ToggleableOutputPin for Flex<'d> { type Error = Infallible; #[inline] fn toggle(&mut self) -> Result<(), Self::Error> { @@ -978,11 +923,11 @@ impl<'d, T: Pin> embedded_hal_02::digital::v2::ToggleableOutputPin for Flex<'d, } } -impl<'d, T: Pin> embedded_hal_1::digital::ErrorType for Input<'d, T> { +impl<'d> embedded_hal_1::digital::ErrorType for Input<'d> { type Error = Infallible; } -impl<'d, T: Pin> embedded_hal_1::digital::InputPin for Input<'d, T> { +impl<'d> embedded_hal_1::digital::InputPin for Input<'d> { #[inline] fn is_high(&mut self) -> Result<bool, Self::Error> { Ok((*self).is_high()) @@ -994,11 +939,11 @@ impl<'d, T: Pin> embedded_hal_1::digital::InputPin for Input<'d, T> { } } -impl<'d, T: Pin> embedded_hal_1::digital::ErrorType for Output<'d, T> { +impl<'d> embedded_hal_1::digital::ErrorType for Output<'d> { type Error = Infallible; } -impl<'d, T: Pin> embedded_hal_1::digital::OutputPin for Output<'d, T> { +impl<'d> embedded_hal_1::digital::OutputPin for Output<'d> { #[inline] fn set_high(&mut self) -> Result<(), Self::Error> { Ok(self.set_high()) @@ -1010,7 +955,7 @@ impl<'d, T: Pin> embedded_hal_1::digital::OutputPin for Output<'d, T> { } } -impl<'d, T: Pin> embedded_hal_1::digital::StatefulOutputPin for Output<'d, T> { +impl<'d> embedded_hal_1::digital::StatefulOutputPin for Output<'d> { #[inline] fn is_set_high(&mut self) -> Result<bool, Self::Error> { Ok((*self).is_set_high()) @@ -1023,11 +968,11 @@ impl<'d, T: Pin> embedded_hal_1::digital::StatefulOutputPin for Output<'d, T> { } } -impl<'d, T: Pin> embedded_hal_1::digital::ErrorType for OutputOpenDrain<'d, T> { +impl<'d> embedded_hal_1::digital::ErrorType for OutputOpenDrain<'d> { type Error = Infallible; } -impl<'d, T: Pin> embedded_hal_1::digital::InputPin for OutputOpenDrain<'d, T> { +impl<'d> embedded_hal_1::digital::InputPin for OutputOpenDrain<'d> { #[inline] fn is_high(&mut self) -> Result<bool, Self::Error> { Ok((*self).is_high()) @@ -1039,7 +984,7 @@ impl<'d, T: Pin> embedded_hal_1::digital::InputPin for OutputOpenDrain<'d, T> { } } -impl<'d, T: Pin> embedded_hal_1::digital::OutputPin for OutputOpenDrain<'d, T> { +impl<'d> embedded_hal_1::digital::OutputPin for OutputOpenDrain<'d> { #[inline] fn set_high(&mut self) -> Result<(), Self::Error> { Ok(self.set_high()) @@ -1051,7 +996,7 @@ impl<'d, T: Pin> embedded_hal_1::digital::OutputPin for OutputOpenDrain<'d, T> { } } -impl<'d, T: Pin> embedded_hal_1::digital::StatefulOutputPin for OutputOpenDrain<'d, T> { +impl<'d> embedded_hal_1::digital::StatefulOutputPin for OutputOpenDrain<'d> { #[inline] fn is_set_high(&mut self) -> Result<bool, Self::Error> { Ok((*self).is_set_high()) @@ -1064,7 +1009,7 @@ impl<'d, T: Pin> embedded_hal_1::digital::StatefulOutputPin for OutputOpenDrain< } } -impl<'d, T: Pin> embedded_hal_1::digital::InputPin for Flex<'d, T> { +impl<'d> embedded_hal_1::digital::InputPin for Flex<'d> { #[inline] fn is_high(&mut self) -> Result<bool, Self::Error> { Ok((*self).is_high()) @@ -1076,7 +1021,7 @@ impl<'d, T: Pin> embedded_hal_1::digital::InputPin for Flex<'d, T> { } } -impl<'d, T: Pin> embedded_hal_1::digital::OutputPin for Flex<'d, T> { +impl<'d> embedded_hal_1::digital::OutputPin for Flex<'d> { #[inline] fn set_high(&mut self) -> Result<(), Self::Error> { Ok(self.set_high()) @@ -1088,11 +1033,11 @@ impl<'d, T: Pin> embedded_hal_1::digital::OutputPin for Flex<'d, T> { } } -impl<'d, T: Pin> embedded_hal_1::digital::ErrorType for Flex<'d, T> { +impl<'d> embedded_hal_1::digital::ErrorType for Flex<'d> { type Error = Infallible; } -impl<'d, T: Pin> embedded_hal_1::digital::StatefulOutputPin for Flex<'d, T> { +impl<'d> embedded_hal_1::digital::StatefulOutputPin for Flex<'d> { #[inline] fn is_set_high(&mut self) -> Result<bool, Self::Error> { Ok((*self).is_set_high()) diff --git a/embassy-stm32/src/hash/mod.rs b/embassy-stm32/src/hash/mod.rs new file mode 100644 index 000000000..b47814f8b --- /dev/null +++ b/embassy-stm32/src/hash/mod.rs @@ -0,0 +1,592 @@ +//! Hash generator (HASH) +use core::cmp::min; +#[cfg(hash_v2)] +use core::future::poll_fn; +use core::marker::PhantomData; +#[cfg(hash_v2)] +use core::ptr; +#[cfg(hash_v2)] +use core::task::Poll; + +use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; +use stm32_metapac::hash::regs::*; + +use crate::dma::NoDma; +#[cfg(hash_v2)] +use crate::dma::Transfer; +use crate::interrupt::typelevel::Interrupt; +use crate::peripherals::HASH; +use crate::rcc::sealed::RccPeripheral; +use crate::{interrupt, pac, peripherals, Peripheral}; + +#[cfg(hash_v1)] +const NUM_CONTEXT_REGS: usize = 51; +#[cfg(hash_v3)] +const NUM_CONTEXT_REGS: usize = 103; +#[cfg(any(hash_v2, hash_v4))] +const NUM_CONTEXT_REGS: usize = 54; + +const HASH_BUFFER_LEN: usize = 132; +const DIGEST_BLOCK_SIZE: usize = 128; + +static HASH_WAKER: AtomicWaker = AtomicWaker::new(); + +/// HASH interrupt handler. +pub struct InterruptHandler<T: Instance> { + _phantom: PhantomData<T>, +} + +impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandler<T> { + unsafe fn on_interrupt() { + let bits = T::regs().sr().read(); + if bits.dinis() { + T::regs().imr().modify(|reg| reg.set_dinie(false)); + HASH_WAKER.wake(); + } + if bits.dcis() { + T::regs().imr().modify(|reg| reg.set_dcie(false)); + HASH_WAKER.wake(); + } + } +} + +///Hash algorithm selection +#[derive(Clone, Copy, PartialEq)] +pub enum Algorithm { + /// SHA-1 Algorithm + SHA1 = 0, + + #[cfg(any(hash_v1, hash_v2, hash_v4))] + /// MD5 Algorithm + MD5 = 1, + + /// SHA-224 Algorithm + SHA224 = 2, + + /// SHA-256 Algorithm + SHA256 = 3, + + #[cfg(hash_v3)] + /// SHA-384 Algorithm + SHA384 = 12, + + #[cfg(hash_v3)] + /// SHA-512/224 Algorithm + SHA512_224 = 13, + + #[cfg(hash_v3)] + /// SHA-512/256 Algorithm + SHA512_256 = 14, + + #[cfg(hash_v3)] + /// SHA-256 Algorithm + SHA512 = 15, +} + +/// Input data width selection +#[repr(u8)] +#[derive(Clone, Copy)] +pub enum DataType { + ///32-bit data, no data is swapped. + Width32 = 0, + ///16-bit data, each half-word is swapped. + Width16 = 1, + ///8-bit data, all bytes are swapped. + Width8 = 2, + ///1-bit data, all bits are swapped. + Width1 = 3, +} + +/// Stores the state of the HASH peripheral for suspending/resuming +/// digest calculation. +pub struct Context<'c> { + first_word_sent: bool, + key_sent: bool, + buffer: [u8; HASH_BUFFER_LEN], + buflen: usize, + algo: Algorithm, + format: DataType, + imr: u32, + str: u32, + cr: u32, + csr: [u32; NUM_CONTEXT_REGS], + key: HmacKey<'c>, +} + +type HmacKey<'k> = Option<&'k [u8]>; + +/// HASH driver. +pub struct Hash<'d, T: Instance, D = NoDma> { + _peripheral: PeripheralRef<'d, T>, + #[allow(dead_code)] + dma: PeripheralRef<'d, D>, +} + +impl<'d, T: Instance, D> Hash<'d, T, D> { + /// Instantiates, resets, and enables the HASH peripheral. + pub fn new( + peripheral: impl Peripheral<P = T> + 'd, + dma: impl Peripheral<P = D> + 'd, + _irq: impl interrupt::typelevel::Binding<T::Interrupt, InterruptHandler<T>> + 'd, + ) -> Self { + HASH::enable_and_reset(); + into_ref!(peripheral, dma); + let instance = Self { + _peripheral: peripheral, + dma: dma, + }; + + T::Interrupt::unpend(); + unsafe { T::Interrupt::enable() }; + + instance + } + + /// Starts computation of a new hash and returns the saved peripheral state. + pub fn start<'c>(&mut self, algorithm: Algorithm, format: DataType, key: HmacKey<'c>) -> Context<'c> { + // Define a context for this new computation. + let mut ctx = Context { + first_word_sent: false, + key_sent: false, + buffer: [0; HASH_BUFFER_LEN], + buflen: 0, + algo: algorithm, + format: format, + imr: 0, + str: 0, + cr: 0, + csr: [0; NUM_CONTEXT_REGS], + key, + }; + + // Set the data type in the peripheral. + T::regs().cr().modify(|w| w.set_datatype(ctx.format as u8)); + + // Select the algorithm. + #[cfg(hash_v1)] + if ctx.algo == Algorithm::MD5 { + T::regs().cr().modify(|w| w.set_algo(true)); + } + + #[cfg(hash_v2)] + { + // Select the algorithm. + let mut algo0 = false; + let mut algo1 = false; + if ctx.algo == Algorithm::MD5 || ctx.algo == Algorithm::SHA256 { + algo0 = true; + } + if ctx.algo == Algorithm::SHA224 || ctx.algo == Algorithm::SHA256 { + algo1 = true; + } + T::regs().cr().modify(|w| w.set_algo0(algo0)); + T::regs().cr().modify(|w| w.set_algo1(algo1)); + } + + #[cfg(any(hash_v3, hash_v4))] + T::regs().cr().modify(|w| w.set_algo(ctx.algo as u8)); + + // Configure HMAC mode if a key is provided. + if let Some(key) = ctx.key { + T::regs().cr().modify(|w| w.set_mode(true)); + if key.len() > 64 { + T::regs().cr().modify(|w| w.set_lkey(true)); + } + } + + T::regs().cr().modify(|w| w.set_init(true)); + + // Store and return the state of the peripheral. + self.store_context(&mut ctx); + ctx + } + + /// Restores the peripheral state using the given context, + /// then updates the state with the provided data. + /// Peripheral state is saved upon return. + pub fn update_blocking<'c>(&mut self, ctx: &mut Context<'c>, input: &[u8]) { + // Restore the peripheral state. + self.load_context(&ctx); + + // Load the HMAC key if provided. + if !ctx.key_sent { + if let Some(key) = ctx.key { + self.accumulate_blocking(key); + T::regs().str().write(|w| w.set_dcal(true)); + // Block waiting for digest. + while !T::regs().sr().read().dinis() {} + } + ctx.key_sent = true; + } + + let mut data_waiting = input.len() + ctx.buflen; + if data_waiting < DIGEST_BLOCK_SIZE || (data_waiting < ctx.buffer.len() && !ctx.first_word_sent) { + // There isn't enough data to digest a block, so append it to the buffer. + ctx.buffer[ctx.buflen..ctx.buflen + input.len()].copy_from_slice(input); + ctx.buflen += input.len(); + self.store_context(ctx); + return; + } + + let mut ilen_remaining = input.len(); + let mut input_start = 0; + + // Handle first block. + if !ctx.first_word_sent { + let empty_len = ctx.buffer.len() - ctx.buflen; + let copy_len = min(empty_len, ilen_remaining); + // Fill the buffer. + if copy_len > 0 { + ctx.buffer[ctx.buflen..ctx.buflen + copy_len].copy_from_slice(&input[0..copy_len]); + ctx.buflen += copy_len; + ilen_remaining -= copy_len; + input_start += copy_len; + } + self.accumulate_blocking(ctx.buffer.as_slice()); + data_waiting -= ctx.buflen; + ctx.buflen = 0; + ctx.first_word_sent = true; + } + + if data_waiting < DIGEST_BLOCK_SIZE { + // There isn't enough data remaining to process another block, so store it. + ctx.buffer[0..ilen_remaining].copy_from_slice(&input[input_start..input_start + ilen_remaining]); + ctx.buflen += ilen_remaining; + } else { + // First ingest the data in the buffer. + let empty_len = DIGEST_BLOCK_SIZE - ctx.buflen; + if empty_len > 0 { + let copy_len = min(empty_len, ilen_remaining); + ctx.buffer[ctx.buflen..ctx.buflen + copy_len] + .copy_from_slice(&input[input_start..input_start + copy_len]); + ctx.buflen += copy_len; + ilen_remaining -= copy_len; + input_start += copy_len; + } + self.accumulate_blocking(&ctx.buffer[0..DIGEST_BLOCK_SIZE]); + ctx.buflen = 0; + + // Move any extra data to the now-empty buffer. + let leftovers = ilen_remaining % 64; + if leftovers > 0 { + ctx.buffer[0..leftovers].copy_from_slice(&input[input.len() - leftovers..input.len()]); + ctx.buflen += leftovers; + ilen_remaining -= leftovers; + } + + // Hash the remaining data. + self.accumulate_blocking(&input[input_start..input_start + ilen_remaining]); + } + + // Save the peripheral context. + self.store_context(ctx); + } + + /// Restores the peripheral state using the given context, + /// then updates the state with the provided data. + /// Peripheral state is saved upon return. + #[cfg(hash_v2)] + pub async fn update<'c>(&mut self, ctx: &mut Context<'c>, input: &[u8]) + where + D: crate::hash::Dma<T>, + { + // Restore the peripheral state. + self.load_context(&ctx); + + // Load the HMAC key if provided. + if !ctx.key_sent { + if let Some(key) = ctx.key { + self.accumulate(key).await; + } + ctx.key_sent = true; + } + + let data_waiting = input.len() + ctx.buflen; + if data_waiting < DIGEST_BLOCK_SIZE { + // There isn't enough data to digest a block, so append it to the buffer. + ctx.buffer[ctx.buflen..ctx.buflen + input.len()].copy_from_slice(input); + ctx.buflen += input.len(); + self.store_context(ctx); + return; + } + + // Enable multiple DMA transfers. + T::regs().cr().modify(|w| w.set_mdmat(true)); + + let mut ilen_remaining = input.len(); + let mut input_start = 0; + + // First ingest the data in the buffer. + let empty_len = DIGEST_BLOCK_SIZE - ctx.buflen; + if empty_len > 0 { + let copy_len = min(empty_len, ilen_remaining); + ctx.buffer[ctx.buflen..ctx.buflen + copy_len].copy_from_slice(&input[input_start..input_start + copy_len]); + ctx.buflen += copy_len; + ilen_remaining -= copy_len; + input_start += copy_len; + } + self.accumulate(&ctx.buffer[..DIGEST_BLOCK_SIZE]).await; + ctx.buflen = 0; + + // Move any extra data to the now-empty buffer. + let leftovers = ilen_remaining % DIGEST_BLOCK_SIZE; + if leftovers > 0 { + assert!(ilen_remaining >= leftovers); + ctx.buffer[0..leftovers].copy_from_slice(&input[input.len() - leftovers..input.len()]); + ctx.buflen += leftovers; + ilen_remaining -= leftovers; + } else { + ctx.buffer + .copy_from_slice(&input[input.len() - DIGEST_BLOCK_SIZE..input.len()]); + ctx.buflen += DIGEST_BLOCK_SIZE; + ilen_remaining -= DIGEST_BLOCK_SIZE; + } + + // Hash the remaining data. + self.accumulate(&input[input_start..input_start + ilen_remaining]).await; + + // Save the peripheral context. + self.store_context(ctx); + } + + /// Computes a digest for the given context. + /// The digest buffer must be large enough to accomodate a digest for the selected algorithm. + /// The largest returned digest size is 128 bytes for SHA-512. + /// Panics if the supplied digest buffer is too short. + pub fn finish_blocking<'c>(&mut self, mut ctx: Context<'c>, digest: &mut [u8]) -> usize { + // Restore the peripheral state. + self.load_context(&ctx); + + // Hash the leftover bytes, if any. + self.accumulate_blocking(&ctx.buffer[0..ctx.buflen]); + ctx.buflen = 0; + + //Start the digest calculation. + T::regs().str().write(|w| w.set_dcal(true)); + + // Load the HMAC key if provided. + if let Some(key) = ctx.key { + while !T::regs().sr().read().dinis() {} + self.accumulate_blocking(key); + T::regs().str().write(|w| w.set_dcal(true)); + } + + // Block until digest computation is complete. + while !T::regs().sr().read().dcis() {} + + // Return the digest. + let digest_words = match ctx.algo { + Algorithm::SHA1 => 5, + #[cfg(any(hash_v1, hash_v2, hash_v4))] + Algorithm::MD5 => 4, + Algorithm::SHA224 => 7, + Algorithm::SHA256 => 8, + #[cfg(hash_v3)] + Algorithm::SHA384 => 12, + #[cfg(hash_v3)] + Algorithm::SHA512_224 => 7, + #[cfg(hash_v3)] + Algorithm::SHA512_256 => 8, + #[cfg(hash_v3)] + Algorithm::SHA512 => 16, + }; + + let digest_len_bytes = digest_words * 4; + // Panics if the supplied digest buffer is too short. + if digest.len() < digest_len_bytes { + panic!("Digest buffer must be at least {} bytes long.", digest_words * 4); + } + + let mut i = 0; + while i < digest_words { + let word = T::regs().hr(i).read(); + digest[(i * 4)..((i * 4) + 4)].copy_from_slice(word.to_be_bytes().as_slice()); + i += 1; + } + digest_len_bytes + } + + /// Computes a digest for the given context. + /// The digest buffer must be large enough to accomodate a digest for the selected algorithm. + /// The largest returned digest size is 128 bytes for SHA-512. + /// Panics if the supplied digest buffer is too short. + #[cfg(hash_v2)] + pub async fn finish<'c>(&mut self, mut ctx: Context<'c>, digest: &mut [u8]) -> usize + where + D: crate::hash::Dma<T>, + { + // Restore the peripheral state. + self.load_context(&ctx); + + // Must be cleared prior to the last DMA transfer. + T::regs().cr().modify(|w| w.set_mdmat(false)); + + // Hash the leftover bytes, if any. + self.accumulate(&ctx.buffer[0..ctx.buflen]).await; + ctx.buflen = 0; + + // Load the HMAC key if provided. + if let Some(key) = ctx.key { + self.accumulate(key).await; + } + + // Wait for completion. + poll_fn(|cx| { + // Check if already done. + let bits = T::regs().sr().read(); + if bits.dcis() { + return Poll::Ready(()); + } + // Register waker, then enable interrupts. + HASH_WAKER.register(cx.waker()); + T::regs().imr().modify(|reg| reg.set_dcie(true)); + // Check for completion. + let bits = T::regs().sr().read(); + if bits.dcis() { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; + + // Return the digest. + let digest_words = match ctx.algo { + Algorithm::SHA1 => 5, + #[cfg(any(hash_v1, hash_v2, hash_v4))] + Algorithm::MD5 => 4, + Algorithm::SHA224 => 7, + Algorithm::SHA256 => 8, + #[cfg(hash_v3)] + Algorithm::SHA384 => 12, + #[cfg(hash_v3)] + Algorithm::SHA512_224 => 7, + #[cfg(hash_v3)] + Algorithm::SHA512_256 => 8, + #[cfg(hash_v3)] + Algorithm::SHA512 => 16, + }; + + let digest_len_bytes = digest_words * 4; + // Panics if the supplied digest buffer is too short. + if digest.len() < digest_len_bytes { + panic!("Digest buffer must be at least {} bytes long.", digest_words * 4); + } + + let mut i = 0; + while i < digest_words { + let word = T::regs().hr(i).read(); + digest[(i * 4)..((i * 4) + 4)].copy_from_slice(word.to_be_bytes().as_slice()); + i += 1; + } + digest_len_bytes + } + + /// Push data into the hash core. + fn accumulate_blocking(&mut self, input: &[u8]) { + // Set the number of valid bits. + let num_valid_bits: u8 = (8 * (input.len() % 4)) as u8; + T::regs().str().modify(|w| w.set_nblw(num_valid_bits)); + + let mut i = 0; + while i < input.len() { + let mut word: [u8; 4] = [0; 4]; + let copy_idx = min(i + 4, input.len()); + word[0..copy_idx - i].copy_from_slice(&input[i..copy_idx]); + T::regs().din().write_value(u32::from_ne_bytes(word)); + i += 4; + } + } + + /// Push data into the hash core. + #[cfg(hash_v2)] + async fn accumulate(&mut self, input: &[u8]) + where + D: crate::hash::Dma<T>, + { + // Ignore an input length of 0. + if input.len() == 0 { + return; + } + + // Set the number of valid bits. + let num_valid_bits: u8 = (8 * (input.len() % 4)) as u8; + T::regs().str().modify(|w| w.set_nblw(num_valid_bits)); + + // Configure DMA to transfer input to hash core. + let dma_request = self.dma.request(); + let dst_ptr = T::regs().din().as_ptr(); + let mut num_words = input.len() / 4; + if input.len() % 4 > 0 { + num_words += 1; + } + let src_ptr = ptr::slice_from_raw_parts(input.as_ptr().cast(), num_words); + let dma_transfer = + unsafe { Transfer::new_write_raw(&mut self.dma, dma_request, src_ptr, dst_ptr, Default::default()) }; + T::regs().cr().modify(|w| w.set_dmae(true)); + + // Wait for the transfer to complete. + dma_transfer.await; + } + + /// Save the peripheral state to a context. + fn store_context<'c>(&mut self, ctx: &mut Context<'c>) { + // Block waiting for data in ready. + while !T::regs().sr().read().dinis() {} + + // Store peripheral context. + ctx.imr = T::regs().imr().read().0; + ctx.str = T::regs().str().read().0; + ctx.cr = T::regs().cr().read().0; + let mut i = 0; + while i < NUM_CONTEXT_REGS { + ctx.csr[i] = T::regs().csr(i).read(); + i += 1; + } + } + + /// Restore the peripheral state from a context. + fn load_context(&mut self, ctx: &Context) { + // Restore the peripheral state from the context. + T::regs().imr().write_value(Imr { 0: ctx.imr }); + T::regs().str().write_value(Str { 0: ctx.str }); + T::regs().cr().write_value(Cr { 0: ctx.cr }); + T::regs().cr().modify(|w| w.set_init(true)); + let mut i = 0; + while i < NUM_CONTEXT_REGS { + T::regs().csr(i).write_value(ctx.csr[i]); + i += 1; + } + } +} + +pub(crate) mod sealed { + use super::*; + + pub trait Instance { + fn regs() -> pac::hash::Hash; + } +} + +/// HASH instance trait. +pub trait Instance: sealed::Instance + Peripheral<P = Self> + crate::rcc::RccPeripheral + 'static + Send { + /// Interrupt for this HASH instance. + type Interrupt: interrupt::typelevel::Interrupt; +} + +foreach_interrupt!( + ($inst:ident, hash, HASH, GLOBAL, $irq:ident) => { + impl Instance for peripherals::$inst { + type Interrupt = crate::interrupt::typelevel::$irq; + } + + impl sealed::Instance for peripherals::$inst { + fn regs() -> crate::pac::hash::Hash { + crate::pac::$inst + } + } + }; +); + +dma_trait!(Dma, Instance); diff --git a/embassy-stm32/src/hrtim/mod.rs b/embassy-stm32/src/hrtim/mod.rs index faefaabbc..3ec646fc3 100644 --- a/embassy-stm32/src/hrtim/mod.rs +++ b/embassy-stm32/src/hrtim/mod.rs @@ -10,8 +10,6 @@ pub use traits::Instance; #[allow(unused_imports)] use crate::gpio::sealed::{AFType, Pin}; use crate::gpio::AnyPin; -#[cfg(stm32f334)] -use crate::rcc::get_freqs; use crate::time::Hertz; use crate::Peripheral; @@ -182,7 +180,7 @@ impl<'d, T: Instance> AdvancedPwm<'d, T> { T::enable_and_reset(); #[cfg(stm32f334)] - if unsafe { get_freqs() }.hrtim.is_some() { + if crate::pac::RCC.cfgr3().read().hrtim1sw() == crate::pac::rcc::vals::Timsw::PLL1_P { // Enable and and stabilize the DLL T::regs().dllcr().modify(|w| { w.set_cal(true); diff --git a/embassy-stm32/src/hrtim/traits.rs b/embassy-stm32/src/hrtim/traits.rs index cfd31c47c..dcc2b9ef4 100644 --- a/embassy-stm32/src/hrtim/traits.rs +++ b/embassy-stm32/src/hrtim/traits.rs @@ -80,10 +80,12 @@ pub(crate) mod sealed { fn set_master_frequency(frequency: Hertz) { let f = frequency.0; - #[cfg(not(stm32f334))] + + // TODO: wire up HRTIM to the RCC mux infra. + //#[cfg(stm32f334)] + //let timer_f = unsafe { crate::rcc::get_freqs() }.hrtim.unwrap_or(Self::frequency()).0; + //#[cfg(not(stm32f334))] let timer_f = Self::frequency().0; - #[cfg(stm32f334)] - let timer_f = unsafe { crate::rcc::get_freqs() }.hrtim.unwrap_or(Self::frequency()).0; let psc_min = (timer_f / f) / (u16::MAX as u32 / 32); let psc = if Self::regs().isr().read().dllrdy() { @@ -103,10 +105,12 @@ pub(crate) mod sealed { fn set_channel_frequency(channel: usize, frequency: Hertz) { let f = frequency.0; - #[cfg(not(stm32f334))] + + // TODO: wire up HRTIM to the RCC mux infra. + //#[cfg(stm32f334)] + //let timer_f = unsafe { crate::rcc::get_freqs() }.hrtim.unwrap_or(Self::frequency()).0; + //#[cfg(not(stm32f334))] let timer_f = Self::frequency().0; - #[cfg(stm32f334)] - let timer_f = unsafe { crate::rcc::get_freqs() }.hrtim.unwrap_or(Self::frequency()).0; let psc_min = (timer_f / f) / (u16::MAX as u32 / 32); let psc = if Self::regs().isr().read().dllrdy() { diff --git a/embassy-stm32/src/i2s.rs b/embassy-stm32/src/i2s.rs index 1f85c0bc5..fa9ec0532 100644 --- a/embassy-stm32/src/i2s.rs +++ b/embassy-stm32/src/i2s.rs @@ -4,7 +4,6 @@ use embassy_hal_internal::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}; @@ -80,20 +79,20 @@ impl Format { #[cfg(any(spi_v1, spi_f1))] 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, + Format::Data16Channel16 => vals::Datlen::BITS16, + Format::Data16Channel32 => vals::Datlen::BITS16, + Format::Data24Channel32 => vals::Datlen::BITS24, + Format::Data32Channel32 => vals::Datlen::BITS32, } } #[cfg(any(spi_v1, spi_f1))] 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, + Format::Data16Channel16 => vals::Chlen::BITS16, + Format::Data16Channel32 => vals::Chlen::BITS32, + Format::Data24Channel32 => vals::Chlen::BITS32, + Format::Data32Channel32 => vals::Chlen::BITS32, } } } @@ -193,10 +192,10 @@ impl<'d, T: Instance, Tx, Rx> I2S<'d, T, Tx, Rx> { spi_cfg.frequency = freq; let spi = Spi::new_internal(peri, txdma, rxdma, spi_cfg); - #[cfg(all(rcc_f4, not(stm32f410)))] - let pclk = unsafe { get_freqs() }.plli2s1_q.unwrap(); - - #[cfg(stm32f410)] + // TODO move i2s to the new mux infra. + //#[cfg(all(rcc_f4, not(stm32f410)))] + //let pclk = unsafe { get_freqs() }.plli2s1_q.unwrap(); + //#[cfg(stm32f410)] let pclk = T::frequency(); let (odd, div) = compute_baud_rate(pclk, freq, config.master_clock, config.format); diff --git a/embassy-stm32/src/ipcc.rs b/embassy-stm32/src/ipcc.rs index 663a7f59d..523719bb9 100644 --- a/embassy-stm32/src/ipcc.rs +++ b/embassy-stm32/src/ipcc.rs @@ -7,7 +7,6 @@ use core::task::Poll; use self::sealed::Instance; use crate::interrupt; use crate::interrupt::typelevel::Interrupt; -use crate::pac::rcc::vals::{Lptim1sel, Lptim2sel}; use crate::peripherals::IPCC; use crate::rcc::sealed::RccPeripheral; @@ -105,7 +104,8 @@ impl Ipcc { IPCC::enable_and_reset(); IPCC::set_cpu2(true); - _configure_pwr(); + // set RF wake-up clock = LSE + crate::pac::RCC.csr().modify(|w| w.set_rfwkpsel(0b01)); let regs = IPCC::regs(); @@ -271,18 +271,3 @@ pub(crate) mod sealed { fn state() -> &'static State; } } - -fn _configure_pwr() { - // TODO: move the rest of this to rcc - let rcc = crate::pac::RCC; - - // 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(Lptim1sel::PCLK1); - w.set_lptim2sel(Lptim2sel::PCLK1); - }); -} diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index e18b16935..aba53ad80 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -34,6 +34,8 @@ pub mod adc; pub mod can; #[cfg(crc)] pub mod crc; +#[cfg(cryp)] +pub mod cryp; #[cfg(dac)] pub mod dac; #[cfg(dcmi)] @@ -45,6 +47,8 @@ pub mod exti; pub mod flash; #[cfg(fmc)] pub mod fmc; +#[cfg(hash)] +pub mod hash; #[cfg(hrtim)] pub mod hrtim; #[cfg(i2c)] @@ -216,6 +220,11 @@ pub fn init(config: Config) -> Peripherals { #[cfg(dbgmcu)] crate::pac::DBGMCU.cr().modify(|cr| { + #[cfg(any(dbgmcu_h5))] + { + cr.set_stop(config.enable_debug_during_sleep); + cr.set_standby(config.enable_debug_during_sleep); + } #[cfg(any(dbgmcu_f0, dbgmcu_c0, dbgmcu_g0, dbgmcu_u5, dbgmcu_wba, dbgmcu_l5))] { cr.set_dbg_stop(config.enable_debug_during_sleep); diff --git a/embassy-stm32/src/opamp.rs b/embassy-stm32/src/opamp.rs index ae8b3cacc..cf531e266 100644 --- a/embassy-stm32/src/opamp.rs +++ b/embassy-stm32/src/opamp.rs @@ -90,6 +90,7 @@ impl<'d, T: Instance> OpAmp<'d, T> { in_pin.set_as_analog(); out_pin.set_as_analog(); + // PGA_GAIN value may have different meaning in different MCU serials, use with caution. let (vm_sel, pga_gain) = match gain { OpAmpGain::Mul1 => (0b11, 0b00), OpAmpGain::Mul2 => (0b10, 0b00), @@ -127,6 +128,7 @@ impl<'d, T: Instance> OpAmp<'d, T> { into_ref!(pin); pin.set_as_analog(); + // PGA_GAIN value may have different meaning in different MCU serials, use with caution. let (vm_sel, pga_gain) = match gain { OpAmpGain::Mul1 => (0b11, 0b00), OpAmpGain::Mul2 => (0b10, 0b00), diff --git a/embassy-stm32/src/rcc/bd.rs b/embassy-stm32/src/rcc/bd.rs index d20f58185..39407b28c 100644 --- a/embassy-stm32/src/rcc/bd.rs +++ b/embassy-stm32/src/rcc/bd.rs @@ -201,12 +201,19 @@ impl LsConfig { bdcr().modify(|w| w.set_bdrst(true)); bdcr().modify(|w| w.set_bdrst(false)); } - #[cfg(any(stm32h5))] - { - bdcr().modify(|w| w.set_vswrst(true)); - bdcr().modify(|w| w.set_vswrst(false)); - } - #[cfg(any(stm32c0))] + // H5 has a terrible, terrible errata: 'SRAM2 is erased when the backup domain is reset' + // pending a more sane sane way to handle this, just don't reset BD for now. + // This means the RTCSEL write below will have no effect, only if it has already been written + // after last power-on. Since it's uncommon to dynamically change RTCSEL, this is better than + // letting half our RAM go magically *poof*. + // STM32H503CB/EB/KB/RB device errata - 2.2.8 SRAM2 unduly erased upon a backup domain reset + // STM32H562xx/563xx/573xx device errata - 2.2.14 SRAM2 is erased when the backup domain is reset + //#[cfg(any(stm32h5))] + //{ + // bdcr().modify(|w| w.set_vswrst(true)); + // bdcr().modify(|w| w.set_vswrst(false)); + //} + #[cfg(any(stm32c0, stm32l0))] { bdcr().modify(|w| w.set_rtcrst(true)); bdcr().modify(|w| w.set_rtcrst(false)); diff --git a/embassy-stm32/src/rcc/c0.rs b/embassy-stm32/src/rcc/c0.rs index 68f029ca0..349f978c5 100644 --- a/embassy-stm32/src/rcc/c0.rs +++ b/embassy-stm32/src/rcc/c0.rs @@ -1,145 +1,184 @@ use crate::pac::flash::vals::Latency; -use crate::pac::rcc::vals::Sw; -pub use crate::pac::rcc::vals::{Hpre as AHBPrescaler, Hsidiv as HSIPrescaler, Ppre as APBPrescaler}; +pub use crate::pac::rcc::vals::{ + Hpre as AHBPrescaler, Hsidiv as HsiSysDiv, Hsikerdiv as HsiKerDiv, Ppre as APBPrescaler, Sw as Sysclk, +}; 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); -/// System clock mux source -#[derive(Clone, Copy)] -pub enum ClockSrc { - HSE(Hertz), - HSI(HSIPrescaler), - LSI, +/// HSE Mode +#[derive(Clone, Copy, Eq, PartialEq)] +pub enum HseMode { + /// crystal/ceramic oscillator (HSEBYP=0) + Oscillator, + /// external analog clock (low swing) (HSEBYP=1) + Bypass, +} + +/// HSE Configuration +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct Hse { + /// HSE frequency. + pub freq: Hertz, + /// HSE mode. + pub mode: HseMode, +} + +/// HSI Configuration +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct Hsi { + /// Division factor for HSISYS clock. Default is 4. + pub sys_div: HsiSysDiv, + /// Division factor for HSIKER clock. Default is 3. + pub ker_div: HsiKerDiv, } /// Clocks configutation +#[non_exhaustive] pub struct Config { - pub mux: ClockSrc, + /// HSI Configuration + pub hsi: Option<Hsi>, + + /// HSE Configuration + pub hse: Option<Hse>, + + /// System Clock Configuration + pub sys: Sysclk, + pub ahb_pre: AHBPrescaler, - pub apb_pre: APBPrescaler, + pub apb1_pre: APBPrescaler, + + /// Low-Speed Clock Configuration pub ls: super::LsConfig, + + /// Per-peripheral kernel clock selection muxes + pub mux: super::mux::ClockMux, } impl Default for Config { #[inline] fn default() -> Config { Config { - mux: ClockSrc::HSI(HSIPrescaler::DIV1), + hsi: Some(Hsi { + sys_div: HsiSysDiv::DIV4, + ker_div: HsiKerDiv::DIV3, + }), + hse: None, + sys: Sysclk::HSISYS, ahb_pre: AHBPrescaler::DIV1, - apb_pre: APBPrescaler::DIV1, + apb1_pre: APBPrescaler::DIV1, ls: Default::default(), + mux: Default::default(), } } } pub(crate) unsafe fn init(config: Config) { - let (sys_clk, sw) = match config.mux { - ClockSrc::HSI(div) => { - // Enable HSI - RCC.cr().write(|w| { - w.set_hsidiv(div); - w.set_hsion(true) + // Configure HSI + let (hsi, hsisys, hsiker) = match config.hsi { + None => { + RCC.cr().modify(|w| w.set_hsion(false)); + (None, None, None) + } + Some(hsi) => { + RCC.cr().modify(|w| { + w.set_hsidiv(hsi.sys_div); + w.set_hsikerdiv(hsi.ker_div); + w.set_hsion(true); }); while !RCC.cr().read().hsirdy() {} - - (HSI_FREQ / div, Sw::HSI) - } - ClockSrc::HSE(freq) => { - // Enable HSE - RCC.cr().write(|w| w.set_hseon(true)); - while !RCC.cr().read().hserdy() {} - - (freq, Sw::HSE) - } - ClockSrc::LSI => { - // Enable LSI - RCC.csr2().write(|w| w.set_lsion(true)); - while !RCC.csr2().read().lsirdy() {} - (super::LSI_FREQ, Sw::LSI) + ( + Some(HSI_FREQ), + Some(HSI_FREQ / hsi.sys_div), + Some(HSI_FREQ / hsi.ker_div), + ) } }; + // Configure HSE + let hse = match config.hse { + None => { + RCC.cr().modify(|w| w.set_hseon(false)); + None + } + Some(hse) => { + match hse.mode { + HseMode::Bypass => assert!(max::HSE_BYP.contains(&hse.freq)), + HseMode::Oscillator => assert!(max::HSE_OSC.contains(&hse.freq)), + } + + RCC.cr().modify(|w| w.set_hsebyp(hse.mode != HseMode::Oscillator)); + RCC.cr().modify(|w| w.set_hseon(true)); + while !RCC.cr().read().hserdy() {} + Some(hse.freq) + } + }; + + let sys = match config.sys { + Sysclk::HSISYS => unwrap!(hsisys), + Sysclk::HSE => unwrap!(hse), + _ => unreachable!(), + }; + + assert!(max::SYSCLK.contains(&sys)); + + // Calculate the AHB frequency (HCLK), among other things so we can calculate the correct flash read latency. + let hclk = sys / config.ahb_pre; + assert!(max::HCLK.contains(&hclk)); + + let (pclk1, pclk1_tim) = super::util::calc_pclk(hclk, config.apb1_pre); + assert!(max::PCLK.contains(&pclk1)); + + let latency = match hclk.0 { + ..=24_000_000 => Latency::WS0, + _ => Latency::WS1, + }; + + // Configure flash read access latency based on voltage scale and frequency + FLASH.acr().modify(|w| { + w.set_latency(latency); + }); + + // Spin until the effective flash latency is set. + while FLASH.acr().read().latency() != latency {} + + // Now that boost mode and flash read access latency are configured, set up SYSCLK + RCC.cfgr().modify(|w| { + w.set_sw(config.sys); + w.set_hpre(config.ahb_pre); + w.set_ppre(config.apb1_pre); + }); + let rtc = config.ls.init(); - // Determine the flash latency implied by the target clock speed - // RM0454 § 3.3.4: - let target_flash_latency = if sys_clk <= Hertz(24_000_000) { - Latency::WS0 - } else { - Latency::WS1 - }; + config.mux.init(); - // 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; - } + set_clocks!( + sys: Some(sys), + hclk1: Some(hclk), + pclk1: Some(pclk1), + pclk1_tim: Some(pclk1_tim), + hsi: hsi, + hsiker: hsiker, + hse: hse, + rtc: rtc, - // 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, config.apb_pre); - 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_freq = sys_clk / config.ahb_pre; - - let (apb_freq, apb_tim_freq) = match config.apb_pre { - APBPrescaler::DIV1 => (ahb_freq, ahb_freq), - pre => { - let freq = ahb_freq / pre; - (freq, freq * 2u32) - } - }; - - set_freqs(Clocks { - hsi: None, + // TODO + lsi: None, lse: None, - sys: sys_clk, - hclk1: ahb_freq, - pclk1: apb_freq, - pclk1_tim: apb_tim_freq, - rtc, - }); + ); +} + +mod max { + use core::ops::RangeInclusive; + + use crate::time::Hertz; + + pub(crate) const HSE_OSC: RangeInclusive<Hertz> = Hertz(4_000_000)..=Hertz(48_000_000); + pub(crate) const HSE_BYP: RangeInclusive<Hertz> = Hertz(0)..=Hertz(48_000_000); + pub(crate) const SYSCLK: RangeInclusive<Hertz> = Hertz(0)..=Hertz(48_000_000); + pub(crate) const PCLK: RangeInclusive<Hertz> = Hertz(8)..=Hertz(48_000_000); + pub(crate) const HCLK: RangeInclusive<Hertz> = Hertz(0)..=Hertz(48_000_000); } diff --git a/embassy-stm32/src/rcc/f0.rs b/embassy-stm32/src/rcc/f0.rs deleted file mode 100644 index feaa2f4c0..000000000 --- a/embassy-stm32/src/rcc/f0.rs +++ /dev/null @@ -1,172 +0,0 @@ -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}; -use crate::time::Hertz; - -/// HSI speed -pub const HSI_FREQ: Hertz = Hertz(8_000_000); - -/// Configuration of the clocks -/// -/// hse takes precedence over hsi48 if both are enabled -#[non_exhaustive] -#[derive(Default)] -pub struct Config { - pub hse: Option<Hertz>, - pub bypass_hse: bool, - pub usb_pll: bool, - - #[cfg(not(stm32f0x0))] - pub hsi48: bool, - - pub sys_ck: Option<Hertz>, - pub hclk: Option<Hertz>, - pub pclk: Option<Hertz>, - - pub ls: super::LsConfig, -} - -pub(crate) unsafe fn init(config: Config) { - let sysclk = config.sys_ck.map(|v| v.0).unwrap_or(HSI_FREQ.0); - - let (src_clk, use_hsi48) = config.hse.map(|v| (v.0, false)).unwrap_or_else(|| { - #[cfg(not(stm32f0x0))] - if config.hsi48 { - return (48_000_000, true); - } - (HSI_FREQ.0, false) - }); - - let (pllmul_bits, real_sysclk) = if sysclk == src_clk { - (None, sysclk) - } else { - let prediv = if config.hse.is_some() { 1 } else { 2 }; - let pllmul = (2 * prediv * sysclk + src_clk) / src_clk / 2; - let pllmul = pllmul.max(2).min(16); - - let pllmul_bits = pllmul as u8 - 2; - let real_sysclk = pllmul * src_clk / prediv; - (Some(pllmul_bits), real_sysclk) - }; - - let hpre_bits = config - .hclk - .map(|hclk| match real_sysclk / hclk.0 { - 0 => unreachable!(), - 1 => 0b0111, - 2 => 0b1000, - 3..=5 => 0b1001, - 6..=11 => 0b1010, - 12..=39 => 0b1011, - 40..=95 => 0b1100, - 96..=191 => 0b1101, - 192..=383 => 0b1110, - _ => 0b1111, - }) - .unwrap_or(0b0111); - let hclk = real_sysclk / (1 << (hpre_bits - 0b0111)); - - let ppre_bits = config - .pclk - .map(|pclk| match hclk / pclk.0 { - 0 => unreachable!(), - 1 => 0b011, - 2 => 0b100, - 3..=5 => 0b101, - 6..=11 => 0b110, - _ => 0b111, - }) - .unwrap_or(0b011); - - let ppre: u8 = 1 << (ppre_bits - 0b011); - let pclk = hclk / u32::from(ppre); - - let timer_mul = if ppre == 1 { 1 } else { 2 }; - - FLASH.acr().write(|w| { - w.set_latency(if real_sysclk <= 24_000_000 { - Latency::WS0 - } else { - Latency::WS1 - }); - }); - - match (config.hse.is_some(), use_hsi48) { - (true, _) => { - RCC.cr().modify(|w| { - w.set_csson(true); - w.set_hseon(true); - w.set_hsebyp(config.bypass_hse); - }); - while !RCC.cr().read().hserdy() {} - - if pllmul_bits.is_some() { - RCC.cfgr().modify(|w| w.set_pllsrc(Pllsrc::HSE_DIV_PREDIV)) - } - } - // use_hsi48 will always be false for stm32f0x0 - #[cfg(not(stm32f0x0))] - (false, true) => { - RCC.cr2().modify(|w| w.set_hsi48on(true)); - while !RCC.cr2().read().hsi48rdy() {} - - if pllmul_bits.is_some() { - RCC.cfgr().modify(|w| w.set_pllsrc(Pllsrc::HSI48_DIV_PREDIV)) - } - } - _ => { - RCC.cr().modify(|w| w.set_hsion(true)); - while !RCC.cr().read().hsirdy() {} - - if pllmul_bits.is_some() { - RCC.cfgr().modify(|w| w.set_pllsrc(Pllsrc::HSI_DIV2)) - } - } - } - - if config.usb_pll { - RCC.cfgr3().modify(|w| w.set_usbsw(Usbsw::PLL1_P)); - } - // TODO: Option to use CRS (Clock Recovery) - - if let Some(pllmul_bits) = 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::from_bits(ppre_bits)); - w.set_hpre(Hpre::from_bits(hpre_bits)); - w.set_sw(Sw::PLL1_P) - }); - } else { - RCC.cfgr().modify(|w| { - 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); - } else if use_hsi48 { - #[cfg(not(stm32f0x0))] - w.set_sw(Sw::HSI48); - } else { - w.set_sw(Sw::HSI) - } - }) - } - - let rtc = config.ls.init(); - - set_freqs(Clocks { - sys: Hertz(real_sysclk), - pclk1: Hertz(pclk), - pclk2: Hertz(pclk), - pclk1_tim: Hertz(pclk * timer_mul), - pclk2_tim: Hertz(pclk * timer_mul), - hclk1: Hertz(hclk), - rtc, - }); -} diff --git a/embassy-stm32/src/rcc/f013.rs b/embassy-stm32/src/rcc/f013.rs new file mode 100644 index 000000000..215f8a3d2 --- /dev/null +++ b/embassy-stm32/src/rcc/f013.rs @@ -0,0 +1,456 @@ +use crate::pac::flash::vals::Latency; +#[cfg(stm32f1)] +pub use crate::pac::rcc::vals::Adcpre as ADCPrescaler; +#[cfg(stm32f3)] +pub use crate::pac::rcc::vals::Adcpres as AdcPllPrescaler; +use crate::pac::rcc::vals::Pllsrc; +#[cfg(stm32f1)] +pub use crate::pac::rcc::vals::Pllxtpre as PllPreDiv; +#[cfg(any(stm32f0, stm32f3))] +pub use crate::pac::rcc::vals::Prediv as PllPreDiv; +pub use crate::pac::rcc::vals::{Hpre as AHBPrescaler, Pllmul as PllMul, Ppre as APBPrescaler, Sw as Sysclk}; +use crate::pac::{FLASH, RCC}; +use crate::time::Hertz; + +/// HSI speed +pub const HSI_FREQ: Hertz = Hertz(8_000_000); + +#[derive(Clone, Copy, Eq, PartialEq)] +pub enum HseMode { + /// crystal/ceramic oscillator (HSEBYP=0) + Oscillator, + /// external analog clock (low swing) (HSEBYP=1) + Bypass, +} + +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct Hse { + /// HSE frequency. + pub freq: Hertz, + /// HSE mode. + pub mode: HseMode, +} + +#[derive(Clone, Copy, Eq, PartialEq)] +pub enum PllSource { + HSE, + HSI, + #[cfg(rcc_f0v4)] + HSI48, +} + +#[derive(Clone, Copy)] +pub struct Pll { + pub src: PllSource, + + /// PLL pre-divider. + /// + /// On some chips, this must be 2 if `src == HSI`. Init will panic if this is not the case. + pub prediv: PllPreDiv, + + /// PLL multiplication factor. + pub mul: PllMul, +} + +#[cfg(all(stm32f3, not(rcc_f37)))] +#[derive(Clone, Copy)] +pub enum AdcClockSource { + Pll(AdcPllPrescaler), + Hclk(AdcHclkPrescaler), +} + +#[cfg(all(stm32f3, not(rcc_f37)))] +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum AdcHclkPrescaler { + Div1, + Div2, + Div4, +} + +#[cfg(stm32f334)] +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum HrtimClockSource { + BusClk, + PllClk, +} + +/// Clocks configutation +#[non_exhaustive] +pub struct Config { + pub hsi: bool, + pub hse: Option<Hse>, + #[cfg(crs)] + pub hsi48: Option<super::Hsi48Config>, + pub sys: Sysclk, + + pub pll: Option<Pll>, + + pub ahb_pre: AHBPrescaler, + pub apb1_pre: APBPrescaler, + #[cfg(not(stm32f0))] + pub apb2_pre: APBPrescaler, + + #[cfg(stm32f1)] + pub adc_pre: ADCPrescaler, + + #[cfg(all(stm32f3, not(rcc_f37)))] + pub adc: AdcClockSource, + #[cfg(all(stm32f3, not(rcc_f37), adc3_common))] + pub adc34: AdcClockSource, + + /// Per-peripheral kernel clock selection muxes + pub mux: super::mux::ClockMux, + + pub ls: super::LsConfig, +} + +impl Default for Config { + fn default() -> Self { + Self { + hsi: true, + hse: None, + #[cfg(crs)] + hsi48: Some(Default::default()), + sys: Sysclk::HSI, + pll: None, + ahb_pre: AHBPrescaler::DIV1, + apb1_pre: APBPrescaler::DIV1, + #[cfg(not(stm32f0))] + apb2_pre: APBPrescaler::DIV1, + ls: Default::default(), + + #[cfg(stm32f1)] + // ensure ADC is not out of range by default even if APB2 is maxxed out (36mhz) + adc_pre: ADCPrescaler::DIV6, + + #[cfg(all(stm32f3, not(rcc_f37)))] + adc: AdcClockSource::Hclk(AdcHclkPrescaler::Div1), + #[cfg(all(stm32f3, not(rcc_f37), adc3_common))] + adc34: AdcClockSource::Hclk(AdcHclkPrescaler::Div1), + + mux: Default::default(), + } + } +} + +/// Initialize and Set the clock frequencies +pub(crate) unsafe fn init(config: Config) { + // Configure HSI + let hsi = match config.hsi { + false => { + RCC.cr().modify(|w| w.set_hsion(false)); + None + } + true => { + RCC.cr().modify(|w| w.set_hsion(true)); + while !RCC.cr().read().hsirdy() {} + Some(HSI_FREQ) + } + }; + + // Configure HSE + let hse = match config.hse { + None => { + RCC.cr().modify(|w| w.set_hseon(false)); + None + } + Some(hse) => { + match hse.mode { + HseMode::Bypass => assert!(max::HSE_BYP.contains(&hse.freq)), + HseMode::Oscillator => assert!(max::HSE_OSC.contains(&hse.freq)), + } + + RCC.cr().modify(|w| w.set_hsebyp(hse.mode != HseMode::Oscillator)); + RCC.cr().modify(|w| w.set_hseon(true)); + while !RCC.cr().read().hserdy() {} + Some(hse.freq) + } + }; + + // configure HSI48 + #[cfg(crs)] + let hsi48 = config.hsi48.map(|config| super::init_hsi48(config)); + #[cfg(not(crs))] + let hsi48: Option<Hertz> = None; + + // Enable PLL + let pll = config.pll.map(|pll| { + let (src_val, src_freq) = match pll.src { + #[cfg(any(rcc_f0v3, rcc_f0v4, rcc_f3v3))] + PllSource::HSI => (Pllsrc::HSI_DIV_PREDIV, unwrap!(hsi)), + #[cfg(not(any(rcc_f0v3, rcc_f0v4, rcc_f3v3)))] + PllSource::HSI => { + if pll.prediv != PllPreDiv::DIV2 { + panic!("if PLL source is HSI, PLL prediv must be 2."); + } + (Pllsrc::HSI_DIV2, unwrap!(hsi)) + } + PllSource::HSE => (Pllsrc::HSE_DIV_PREDIV, unwrap!(hse)), + #[cfg(rcc_f0v4)] + PllSource::HSI48 => (Pllsrc::HSI48_DIV_PREDIV, unwrap!(hsi48)), + }; + let in_freq = src_freq / pll.prediv; + assert!(max::PLL_IN.contains(&in_freq)); + let out_freq = in_freq * pll.mul; + assert!(max::PLL_OUT.contains(&out_freq)); + + #[cfg(not(stm32f1))] + RCC.cfgr2().modify(|w| w.set_prediv(pll.prediv)); + RCC.cfgr().modify(|w| { + w.set_pllmul(pll.mul); + w.set_pllsrc(src_val); + #[cfg(stm32f1)] + w.set_pllxtpre(pll.prediv); + }); + RCC.cr().modify(|w| w.set_pllon(true)); + while !RCC.cr().read().pllrdy() {} + + out_freq + }); + + #[cfg(stm32f3)] + let pll_mul_2 = pll.map(|pll| pll * 2u32); + + #[cfg(any(rcc_f1, rcc_f1cl, stm32f3))] + let usb = match pll { + Some(Hertz(72_000_000)) => Some(crate::pac::rcc::vals::Usbpre::DIV1_5), + Some(Hertz(48_000_000)) => Some(crate::pac::rcc::vals::Usbpre::DIV1), + _ => None, + } + .map(|usbpre| { + RCC.cfgr().modify(|w| w.set_usbpre(usbpre)); + Hertz(48_000_000) + }); + + // Configure sysclk + let sys = match config.sys { + Sysclk::HSI => unwrap!(hsi), + Sysclk::HSE => unwrap!(hse), + Sysclk::PLL1_P => unwrap!(pll), + _ => unreachable!(), + }; + + let hclk = sys / config.ahb_pre; + let (pclk1, pclk1_tim) = super::util::calc_pclk(hclk, config.apb1_pre); + #[cfg(not(stm32f0))] + let (pclk2, pclk2_tim) = super::util::calc_pclk(hclk, config.apb2_pre); + #[cfg(stm32f0)] + let (pclk2, pclk2_tim) = (pclk1, pclk1_tim); + + assert!(max::HCLK.contains(&hclk)); + assert!(max::PCLK1.contains(&pclk1)); + #[cfg(not(stm32f0))] + assert!(max::PCLK2.contains(&pclk2)); + + #[cfg(stm32f1)] + let adc = pclk2 / config.adc_pre; + #[cfg(stm32f1)] + assert!(max::ADC.contains(&adc)); + + // Set latency based on HCLK frquency + #[cfg(stm32f0)] + let latency = match hclk.0 { + ..=24_000_000 => Latency::WS0, + _ => Latency::WS1, + }; + #[cfg(any(stm32f1, stm32f3))] + let latency = match hclk.0 { + ..=24_000_000 => Latency::WS0, + ..=48_000_000 => Latency::WS1, + _ => Latency::WS2, + }; + FLASH.acr().modify(|w| { + w.set_latency(latency); + // RM0316: "The prefetch buffer must be kept on when using a prescaler + // different from 1 on the AHB clock.", "Half-cycle access cannot be + // used when there is a prescaler different from 1 on the AHB clock" + #[cfg(stm32f3)] + if config.ahb_pre != AHBPrescaler::DIV1 { + w.set_hlfcya(false); + w.set_prftbe(true); + } + #[cfg(not(stm32f3))] + w.set_prftbe(true); + }); + + // Set prescalers + // CFGR has been written before (PLL, PLL48) don't overwrite these settings + RCC.cfgr().modify(|w: &mut stm32_metapac::rcc::regs::Cfgr| { + #[cfg(not(stm32f0))] + { + w.set_ppre1(config.apb1_pre); + w.set_ppre2(config.apb2_pre); + } + #[cfg(stm32f0)] + w.set_ppre(config.apb1_pre); + w.set_hpre(config.ahb_pre); + #[cfg(stm32f1)] + w.set_adcpre(config.adc_pre); + }); + + // Wait for the new prescalers to kick in + // "The clocks are divided with the new prescaler factor from + // 1 to 16 AHB cycles after write" + cortex_m::asm::delay(16); + + // CFGR has been written before (PLL, PLL48, clock divider) don't overwrite these settings + RCC.cfgr().modify(|w| w.set_sw(config.sys)); + while RCC.cfgr().read().sws() != config.sys {} + + let rtc = config.ls.init(); + + #[cfg(all(stm32f3, not(rcc_f37)))] + use crate::pac::adccommon::vals::Ckmode; + + #[cfg(all(stm32f3, not(rcc_f37)))] + let adc = match config.adc { + AdcClockSource::Pll(adcpres) => { + RCC.cfgr2().modify(|w| w.set_adc12pres(adcpres)); + crate::pac::ADC_COMMON + .ccr() + .modify(|w| w.set_ckmode(Ckmode::ASYNCHRONOUS)); + + unwrap!(pll) / adcpres + } + AdcClockSource::Hclk(adcpres) => { + assert!(!(adcpres == AdcHclkPrescaler::Div1 && config.ahb_pre != AHBPrescaler::DIV1)); + + let (div, ckmode) = match adcpres { + AdcHclkPrescaler::Div1 => (1u32, Ckmode::SYNCDIV1), + AdcHclkPrescaler::Div2 => (2u32, Ckmode::SYNCDIV2), + AdcHclkPrescaler::Div4 => (4u32, Ckmode::SYNCDIV4), + }; + crate::pac::ADC_COMMON.ccr().modify(|w| w.set_ckmode(ckmode)); + + hclk / div + } + }; + + #[cfg(all(stm32f3, not(rcc_f37), adc3_common))] + let adc34 = match config.adc34 { + AdcClockSource::Pll(adcpres) => { + RCC.cfgr2().modify(|w| w.set_adc34pres(adcpres)); + crate::pac::ADC3_COMMON + .ccr() + .modify(|w| w.set_ckmode(Ckmode::ASYNCHRONOUS)); + + unwrap!(pll) / adcpres + } + AdcClockSource::Hclk(adcpres) => { + assert!(!(adcpres == AdcHclkPrescaler::Div1 && config.ahb_pre != AHBPrescaler::DIV1)); + + let (div, ckmode) = match adcpres { + AdcHclkPrescaler::Div1 => (1u32, Ckmode::SYNCDIV1), + AdcHclkPrescaler::Div2 => (2u32, Ckmode::SYNCDIV2), + AdcHclkPrescaler::Div4 => (4u32, Ckmode::SYNCDIV4), + }; + crate::pac::ADC3_COMMON.ccr().modify(|w| w.set_ckmode(ckmode)); + + hclk / div + } + }; + + /* + TODO: Maybe add something like this to clock_mux? How can we autogenerate the data for this? + let hrtim = match config.hrtim { + // Must be configured after the bus is ready, otherwise it won't work + HrtimClockSource::BusClk => None, + HrtimClockSource::PllClk => { + use crate::pac::rcc::vals::Timsw; + + // Make sure that we're using the PLL + let pll = unwrap!(pll); + assert!((pclk2 == pll) || (pclk2 * 2u32 == pll)); + + RCC.cfgr3().modify(|w| w.set_hrtim1sw(Timsw::PLL1_P)); + + Some(pll * 2u32) + } + }; + */ + + config.mux.init(); + + set_clocks!( + hsi: hsi, + hse: hse, + pll1_p: pll, + #[cfg(stm32f3)] + pll1_p_mul_2: pll_mul_2, + hsi_div_244: hsi.map(|h| h / 244u32), + sys: Some(sys), + pclk1: Some(pclk1), + pclk2: Some(pclk2), + pclk1_tim: Some(pclk1_tim), + pclk2_tim: Some(pclk2_tim), + hclk1: Some(hclk), + #[cfg(all(stm32f3, not(rcc_f37)))] + adc: Some(adc), + #[cfg(all(stm32f3, not(rcc_f37), adc3_common))] + adc34: Some(adc34), + rtc: rtc, + hsi48: hsi48, + #[cfg(any(rcc_f1, rcc_f1cl, stm32f3))] + usb: usb, + lse: None, + ); +} + +#[cfg(stm32f0)] +mod max { + use core::ops::RangeInclusive; + + use crate::time::Hertz; + + pub(crate) const HSE_OSC: RangeInclusive<Hertz> = Hertz(4_000_000)..=Hertz(32_000_000); + pub(crate) const HSE_BYP: RangeInclusive<Hertz> = Hertz(1_000_000)..=Hertz(32_000_000); + + pub(crate) const HCLK: RangeInclusive<Hertz> = Hertz(0)..=Hertz(48_000_000); + pub(crate) const PCLK1: RangeInclusive<Hertz> = Hertz(0)..=Hertz(48_000_000); + + pub(crate) const PLL_IN: RangeInclusive<Hertz> = Hertz(1_000_000)..=Hertz(24_000_000); + pub(crate) const PLL_OUT: RangeInclusive<Hertz> = Hertz(16_000_000)..=Hertz(48_000_000); +} + +#[cfg(stm32f1)] +mod max { + use core::ops::RangeInclusive; + + use crate::time::Hertz; + + #[cfg(not(rcc_f1cl))] + pub(crate) const HSE_OSC: RangeInclusive<Hertz> = Hertz(4_000_000)..=Hertz(16_000_000); + #[cfg(not(rcc_f1cl))] + pub(crate) const HSE_BYP: RangeInclusive<Hertz> = Hertz(1_000_000)..=Hertz(25_000_000); + + #[cfg(rcc_f1cl)] + pub(crate) const HSE_OSC: RangeInclusive<Hertz> = Hertz(3_000_000)..=Hertz(25_000_000); + #[cfg(rcc_f1cl)] + pub(crate) const HSE_BYP: RangeInclusive<Hertz> = Hertz(1_000_000)..=Hertz(50_000_000); + + pub(crate) const HCLK: RangeInclusive<Hertz> = Hertz(0)..=Hertz(72_000_000); + pub(crate) const PCLK1: RangeInclusive<Hertz> = Hertz(0)..=Hertz(36_000_000); + pub(crate) const PCLK2: RangeInclusive<Hertz> = Hertz(0)..=Hertz(72_000_000); + + pub(crate) const PLL_IN: RangeInclusive<Hertz> = Hertz(1_000_000)..=Hertz(25_000_000); + pub(crate) const PLL_OUT: RangeInclusive<Hertz> = Hertz(16_000_000)..=Hertz(72_000_000); + + pub(crate) const ADC: RangeInclusive<Hertz> = Hertz(0)..=Hertz(14_000_000); +} + +#[cfg(stm32f3)] +mod max { + use core::ops::RangeInclusive; + + use crate::time::Hertz; + + pub(crate) const HSE_OSC: RangeInclusive<Hertz> = Hertz(4_000_000)..=Hertz(32_000_000); + pub(crate) const HSE_BYP: RangeInclusive<Hertz> = Hertz(1_000_000)..=Hertz(32_000_000); + + pub(crate) const HCLK: RangeInclusive<Hertz> = Hertz(0)..=Hertz(72_000_000); + pub(crate) const PCLK1: RangeInclusive<Hertz> = Hertz(0)..=Hertz(36_000_000); + pub(crate) const PCLK2: RangeInclusive<Hertz> = Hertz(0)..=Hertz(72_000_000); + + pub(crate) const PLL_IN: RangeInclusive<Hertz> = Hertz(1_000_000)..=Hertz(24_000_000); + pub(crate) const PLL_OUT: RangeInclusive<Hertz> = Hertz(16_000_000)..=Hertz(72_000_000); +} diff --git a/embassy-stm32/src/rcc/f1.rs b/embassy-stm32/src/rcc/f1.rs deleted file mode 100644 index 169551e45..000000000 --- a/embassy-stm32/src/rcc/f1.rs +++ /dev/null @@ -1,192 +0,0 @@ -use core::convert::TryFrom; - -use super::{set_freqs, Clocks}; -use crate::pac::flash::vals::Latency; -use crate::pac::rcc::vals::*; -use crate::pac::{FLASH, RCC}; -use crate::time::Hertz; - -/// HSI speed -pub const HSI_FREQ: Hertz = Hertz(8_000_000); - -/// Configuration of the clocks -/// -#[non_exhaustive] -#[derive(Default)] -pub struct Config { - pub hse: Option<Hertz>, - - pub sys_ck: Option<Hertz>, - pub hclk: Option<Hertz>, - pub pclk1: Option<Hertz>, - pub pclk2: Option<Hertz>, - pub adcclk: Option<Hertz>, - pub pllxtpre: bool, - - pub ls: super::LsConfig, -} - -pub(crate) unsafe fn init(config: Config) { - 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; - - let (pllmul_bits, real_sysclk) = if pllmul == 1 { - (None, config.hse.map(|hse| hse.0).unwrap_or(HSI_FREQ.0)) - } else { - let pllmul = core::cmp::min(core::cmp::max(pllmul, 1), 16); - (Some(pllmul as u8 - 2), pllsrcclk * pllmul) - }; - - assert!(real_sysclk <= 72_000_000); - - let hpre_bits = config - .hclk - .map(|hclk| match real_sysclk / hclk.0 { - 0 => unreachable!(), - 1 => 0b0111, - 2 => 0b1000, - 3..=5 => 0b1001, - 6..=11 => 0b1010, - 12..=39 => 0b1011, - 40..=95 => 0b1100, - 96..=191 => 0b1101, - 192..=383 => 0b1110, - _ => 0b1111, - }) - .unwrap_or(0b0111); - - let hclk = if hpre_bits >= 0b1100 { - real_sysclk / (1 << (hpre_bits - 0b0110)) - } else { - real_sysclk / (1 << (hpre_bits - 0b0111)) - }; - - assert!(hclk <= 72_000_000); - - let ppre1_bits = config - .pclk1 - .map(|pclk1| match hclk / pclk1.0 { - 0 => unreachable!(), - 1 => 0b011, - 2 => 0b100, - 3..=5 => 0b101, - 6..=11 => 0b110, - _ => 0b111, - }) - .unwrap_or(0b011); - - let ppre1 = 1 << (ppre1_bits - 0b011); - let pclk1 = hclk / u32::try_from(ppre1).unwrap(); - let timer_mul1 = if ppre1 == 1 { 1 } else { 2 }; - - assert!(pclk1 <= 36_000_000); - - let ppre2_bits = config - .pclk2 - .map(|pclk2| match hclk / pclk2.0 { - 0 => unreachable!(), - 1 => 0b011, - 2 => 0b100, - 3..=5 => 0b101, - 6..=11 => 0b110, - _ => 0b111, - }) - .unwrap_or(0b011); - - let ppre2 = 1 << (ppre2_bits - 0b011); - let pclk2 = hclk / u32::try_from(ppre2).unwrap(); - let timer_mul2 = if ppre2 == 1 { 1 } else { 2 }; - - assert!(pclk2 <= 72_000_000); - - FLASH.acr().write(|w| { - w.set_latency(if real_sysclk <= 24_000_000 { - Latency::WS0 - } else if real_sysclk <= 48_000_000 { - Latency::WS1 - } else { - Latency::WS2 - }); - // the prefetch buffer is enabled by default, let's keep it enabled - w.set_prftbe(true); - }); - - // the USB clock is only valid if an external crystal is used, the PLL is enabled, and the - // PLL output frequency is a supported one. - // usbpre == false: divide clock by 1.5, otherwise no division - #[cfg(not(rcc_f100))] - let (usbpre, _usbclk_valid) = match (config.hse, pllmul_bits, real_sysclk) { - (Some(_), Some(_), 72_000_000) => (false, true), - (Some(_), Some(_), 48_000_000) => (true, true), - _ => (true, false), - }; - - let apre_bits: u8 = config - .adcclk - .map(|adcclk| match pclk2 / adcclk.0 { - 0..=2 => 0b00, - 3..=4 => 0b01, - 5..=7 => 0b10, - _ => 0b11, - }) - .unwrap_or(0b11); - - let apre = (apre_bits + 1) << 1; - let adcclk = pclk2 / unwrap!(u32::try_from(apre)); - - assert!(adcclk <= 14_000_000); - - if config.hse.is_some() { - // enable HSE and wait for it to be ready - RCC.cr().modify(|w| w.set_hseon(true)); - while !RCC.cr().read().hserdy() {} - } - - 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::from_bits(pllmul_bits)); - w.set_pllsrc(Pllsrc::from_bits(config.hse.is_some() as u8)); - }); - - RCC.cr().modify(|w| w.set_pllon(true)); - while !RCC.cr().read().pllrdy() {} - } - - // Only needed for stm32f103? - RCC.cfgr().modify(|w| { - w.set_adcpre(Adcpre::from_bits(apre_bits)); - w.set_ppre2(Ppre::from_bits(ppre2_bits)); - w.set_ppre1(Ppre::from_bits(ppre1_bits)); - w.set_hpre(Hpre::from_bits(hpre_bits)); - #[cfg(not(rcc_f100))] - w.set_usbpre(Usbpre::from_bits(usbpre as u8)); - w.set_sw(if pllmul_bits.is_some() { - Sw::PLL1_P - } else if config.hse.is_some() { - Sw::HSE - } else { - Sw::HSI - }); - }); - - let rtc = config.ls.init(); - - set_freqs(Clocks { - sys: Hertz(real_sysclk), - pclk1: Hertz(pclk1), - pclk2: Hertz(pclk2), - pclk1_tim: Hertz(pclk1 * timer_mul1), - pclk2_tim: Hertz(pclk2 * timer_mul2), - hclk1: Hertz(hclk), - adc: Some(Hertz(adcclk)), - rtc, - }); -} diff --git a/embassy-stm32/src/rcc/f.rs b/embassy-stm32/src/rcc/f247.rs similarity index 90% rename from embassy-stm32/src/rcc/f.rs rename to embassy-stm32/src/rcc/f247.rs index 36d9f178f..7b252870c 100644 --- a/embassy-stm32/src/rcc/f.rs +++ b/embassy-stm32/src/rcc/f247.rs @@ -7,7 +7,6 @@ pub use crate::pac::rcc::vals::{ #[cfg(any(stm32f4, stm32f7))] use crate::pac::PWR; use crate::pac::{FLASH, RCC}; -use crate::rcc::{set_freqs, Clocks}; use crate::time::Hertz; // TODO: on some F4s, PLLM is shared between all PLLs. Enforce that. @@ -96,6 +95,9 @@ pub struct Config { pub ls: super::LsConfig, + /// Per-peripheral kernel clock selection muxes + pub mux: super::mux::ClockMux, + #[cfg(stm32f2)] pub voltage: VoltageScale, } @@ -121,6 +123,7 @@ impl Default for Config { #[cfg(stm32f2)] voltage: VoltageScale::Range3, + mux: Default::default(), } } } @@ -183,9 +186,9 @@ pub(crate) unsafe fn init(config: Config) { }; let pll = init_pll(PllInstance::Pll, config.pll, &pll_input); #[cfg(any(stm32f2, all(stm32f4, not(stm32f410)), stm32f7))] - let _plli2s = init_pll(PllInstance::Plli2s, config.plli2s, &pll_input); + let plli2s = init_pll(PllInstance::Plli2s, config.plli2s, &pll_input); #[cfg(any(stm32f446, stm32f427, stm32f437, stm32f4x9, stm32f7))] - let _pllsai = init_pll(PllInstance::Pllsai, config.pllsai, &pll_input); + let pllsai = init_pll(PllInstance::Pllsai, config.pllsai, &pll_input); // Configure sysclk let sys = match config.sys { @@ -257,27 +260,46 @@ pub(crate) unsafe fn init(config: Config) { }); while RCC.cfgr().read().sws() != config.sys {} - set_freqs(Clocks { - sys, - hclk1: hclk, - hclk2: hclk, - hclk3: hclk, - pclk1, - pclk2, - pclk1_tim, - pclk2_tim, - rtc, - pll1_q: pll.q, - #[cfg(all(rcc_f4, not(stm32f410)))] - plli2s1_q: _plli2s.q, - #[cfg(all(rcc_f4, not(stm32f410)))] - plli2s1_r: _plli2s.r, + config.mux.init(); - #[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479))] - pllsai1_q: _pllsai.q, - #[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479))] - pllsai1_r: _pllsai.r, - }); + set_clocks!( + hsi: hsi, + hse: hse, + lse: None, // TODO + lsi: None, // TODO + sys: Some(sys), + hclk1: Some(hclk), + hclk2: Some(hclk), + hclk3: Some(hclk), + pclk1: Some(pclk1), + pclk2: Some(pclk2), + pclk1_tim: Some(pclk1_tim), + pclk2_tim: Some(pclk2_tim), + rtc: rtc, + pll1_q: pll.q, + + #[cfg(any(stm32f2, all(stm32f4, not(stm32f410)), stm32f7))] + plli2s1_p: plli2s.p, + #[cfg(any(stm32f2, all(stm32f4, not(stm32f410)), stm32f7))] + plli2s1_q: plli2s.q, + #[cfg(any(stm32f2, all(stm32f4, not(stm32f410)), stm32f7))] + plli2s1_r: plli2s.r, + + #[cfg(any(stm32f446, stm32f427, stm32f437, stm32f4x9, stm32f7))] + pllsai1_p: pllsai.p, + #[cfg(any(stm32f446, stm32f427, stm32f437, stm32f4x9, stm32f7))] + pllsai1_q: pllsai.q, + #[cfg(any(stm32f446, stm32f427, stm32f437, stm32f4x9, stm32f7))] + pllsai1_r: pllsai.r, + + // TODO workaround until f4 rcc is fixed in stm32-data + #[cfg(not(any(stm32f446, stm32f427, stm32f437, stm32f4x9, stm32f7)))] + pllsai1_q: None, + + hsi_div488: hsi.map(|hsi| hsi/488u32), + hsi_hse: None, + afif: None, + ); } struct PllInput { diff --git a/embassy-stm32/src/rcc/f3.rs b/embassy-stm32/src/rcc/f3.rs deleted file mode 100644 index bf035fd25..000000000 --- a/embassy-stm32/src/rcc/f3.rs +++ /dev/null @@ -1,459 +0,0 @@ -#[cfg(rcc_f3)] -use crate::pac::adccommon::vals::Ckmode; -use crate::pac::flash::vals::Latency; -pub use crate::pac::rcc::vals::Adcpres; -use crate::pac::rcc::vals::{Hpre, Pllmul, Pllsrc, Ppre, Prediv, Sw, Usbpre}; -use crate::pac::{FLASH, RCC}; -use crate::rcc::{set_freqs, Clocks}; -use crate::time::Hertz; - -/// HSI speed -pub const HSI_FREQ: Hertz = Hertz(8_000_000); - -#[cfg(rcc_f3)] -impl From<AdcClockSource> for Ckmode { - fn from(value: AdcClockSource) -> Self { - match value { - AdcClockSource::BusDiv1 => Ckmode::SYNCDIV1, - AdcClockSource::BusDiv2 => Ckmode::SYNCDIV2, - AdcClockSource::BusDiv4 => Ckmode::SYNCDIV4, - _ => unreachable!(), - } - } -} - -#[derive(Clone, Copy)] -pub enum AdcClockSource { - Pll(Adcpres), - BusDiv1, - BusDiv2, - BusDiv4, -} - -impl AdcClockSource { - pub fn bus_div(&self) -> u32 { - match self { - Self::BusDiv1 => 1, - Self::BusDiv2 => 2, - Self::BusDiv4 => 4, - _ => unreachable!(), - } - } -} - -#[derive(Default)] -pub enum HrtimClockSource { - #[default] - BusClk, - PllClk, -} - -/// Clocks configutation -#[non_exhaustive] -#[derive(Default)] -pub struct Config { - /// Frequency of HSE oscillator - /// 4MHz to 32MHz - pub hse: Option<Hertz>, - /// Bypass HSE for an external clock - pub bypass_hse: bool, - /// Frequency of the System Clock - pub sysclk: Option<Hertz>, - /// Frequency of AHB bus - pub hclk: Option<Hertz>, - /// Frequency of APB1 bus - /// - Max frequency 36MHz - pub pclk1: Option<Hertz>, - /// Frequency of APB2 bus - /// - Max frequency with HSE is 72MHz - /// - Max frequency without HSE is 64MHz - pub pclk2: Option<Hertz>, - /// USB clock setup - /// It is valid only when, - /// - HSE is enabled, - /// - The System clock frequency is either 48MHz or 72MHz - /// - APB1 clock has a minimum frequency of 10MHz - pub pll48: bool, - #[cfg(rcc_f3)] - /// ADC clock setup - /// - For AHB, a psc of 4 or less must be used - pub adc: Option<AdcClockSource>, - #[cfg(rcc_f3)] - /// ADC clock setup - /// - For AHB, a psc of 4 or less must be used - pub adc34: Option<AdcClockSource>, - #[cfg(stm32f334)] - pub hrtim: HrtimClockSource, - pub ls: super::LsConfig, -} - -// Information required to setup the PLL clock -#[derive(Clone, Copy)] -struct PllConfig { - pll_src: Pllsrc, - pll_mul: Pllmul, - pll_div: Option<Prediv>, -} - -/// Initialize and Set the clock frequencies -pub(crate) unsafe fn init(config: Config) { - // Calculate the real System clock, and PLL configuration if applicable - let (sysclk, pll_config) = get_sysclk(&config); - assert!(sysclk.0 <= 72_000_000); - - // Calculate real AHB clock - let hclk = config.hclk.map(|h| h).unwrap_or(sysclk); - let hpre = match sysclk.0 / hclk.0 { - 0 => unreachable!(), - 1 => Hpre::DIV1, - 2 => Hpre::DIV2, - 3..=5 => Hpre::DIV4, - 6..=11 => Hpre::DIV8, - 12..=39 => Hpre::DIV16, - 40..=95 => Hpre::DIV64, - 96..=191 => Hpre::DIV128, - 192..=383 => Hpre::DIV256, - _ => Hpre::DIV512, - }; - let hclk = sysclk / hpre; - assert!(hclk <= Hertz(72_000_000)); - - // Calculate real APB1 clock - let pclk1 = config.pclk1.unwrap_or(hclk); - let ppre1 = match hclk / pclk1 { - 0 => unreachable!(), - 1 => Ppre::DIV1, - 2 => Ppre::DIV2, - 3..=5 => Ppre::DIV4, - 6..=11 => Ppre::DIV8, - _ => Ppre::DIV16, - }; - let timer_mul1 = if ppre1 == Ppre::DIV1 { 1u32 } else { 2 }; - let pclk1 = hclk / ppre1; - assert!(pclk1 <= Hertz(36_000_000)); - - // Calculate real APB2 clock - let pclk2 = config.pclk2.unwrap_or(hclk); - let ppre2 = match hclk / pclk2 { - 0 => unreachable!(), - 1 => Ppre::DIV1, - 2 => Ppre::DIV2, - 3..=5 => Ppre::DIV4, - 6..=11 => Ppre::DIV8, - _ => Ppre::DIV16, - }; - let timer_mul2 = if ppre2 == Ppre::DIV1 { 1u32 } else { 2 }; - let pclk2 = hclk / ppre2; - assert!(pclk2 <= Hertz(72_000_000)); - - // Set latency based on HCLK frquency - // RM0316: "The prefetch buffer must be kept on when using a prescaler - // different from 1 on the AHB clock.", "Half-cycle access cannot be - // used when there is a prescaler different from 1 on the AHB clock" - FLASH.acr().modify(|w| { - w.set_latency(if hclk <= Hertz(24_000_000) { - Latency::WS0 - } else if hclk <= Hertz(48_000_000) { - Latency::WS1 - } else { - Latency::WS2 - }); - if hpre != Hpre::DIV1 { - w.set_hlfcya(false); - w.set_prftbe(true); - } - }); - - // Enable HSE - // RM0316: "Bits 31:26 Reserved, must be kept at reset value." - if config.hse.is_some() { - RCC.cr().modify(|w| { - w.set_hsebyp(config.bypass_hse); - // We turn on clock security to switch to HSI when HSE fails - w.set_csson(true); - w.set_hseon(true); - }); - while !RCC.cr().read().hserdy() {} - } - - // Enable PLL - // RM0316: "Reserved, must be kept at reset value." - if let Some(ref pll_config) = pll_config { - RCC.cfgr().modify(|w| { - w.set_pllmul(pll_config.pll_mul); - w.set_pllsrc(pll_config.pll_src); - }); - if let Some(pll_div) = pll_config.pll_div { - RCC.cfgr2().modify(|w| w.set_prediv(pll_div)); - } - RCC.cr().modify(|w| w.set_pllon(true)); - while !RCC.cr().read().pllrdy() {} - } - - // CFGR has been written before (PLL) don't overwrite these settings - if config.pll48 { - let usb_pre = get_usb_pre(&config, sysclk, pclk1, &pll_config); - RCC.cfgr().modify(|w| { - w.set_usbpre(usb_pre); - }); - } - - // Set prescalers - // CFGR has been written before (PLL, PLL48) don't overwrite these settings - RCC.cfgr().modify(|w| { - w.set_ppre2(ppre2); - w.set_ppre1(ppre1); - w.set_hpre(hpre); - }); - - // Wait for the new prescalers to kick in - // "The clocks are divided with the new prescaler factor from - // 1 to 16 AHB cycles after write" - cortex_m::asm::delay(16); - - // CFGR has been written before (PLL, PLL48, clock divider) don't overwrite these settings - RCC.cfgr().modify(|w| { - w.set_sw(match (pll_config, config.hse) { - (Some(_), _) => Sw::PLL1_P, - (None, Some(_)) => Sw::HSE, - (None, None) => Sw::HSI, - }) - }); - - #[cfg(rcc_f3)] - let adc = config.adc.map(|adc| match adc { - AdcClockSource::Pll(adcpres) => { - RCC.cfgr2().modify(|w| { - // Make sure that we're using the PLL - pll_config.unwrap(); - w.set_adc12pres(adcpres); - - sysclk / adcpres - }) - } - _ => crate::pac::ADC_COMMON.ccr().modify(|w| { - assert!(!(adc.bus_div() == 1 && hpre != Hpre::DIV1)); - - w.set_ckmode(adc.into()); - - sysclk / adc.bus_div() - }), - }); - - #[cfg(all(rcc_f3, adc3_common))] - let adc34 = config.adc34.map(|adc| match adc { - AdcClockSource::Pll(adcpres) => { - RCC.cfgr2().modify(|w| { - // Make sure that we're using the PLL - pll_config.unwrap(); - w.set_adc34pres(adcpres); - - sysclk / adcpres - }) - } - _ => crate::pac::ADC_COMMON.ccr().modify(|w| { - assert!(!(adc.bus_div() == 1 && hpre != Hpre::DIV1)); - - w.set_ckmode(adc.into()); - - sysclk / adc.bus_div() - }), - }); - - #[cfg(stm32f334)] - let hrtim = match config.hrtim { - // Must be configured after the bus is ready, otherwise it won't work - HrtimClockSource::BusClk => None, - HrtimClockSource::PllClk => { - use crate::pac::rcc::vals::Timsw; - - // Make sure that we're using the PLL - pll_config.unwrap(); - assert!((pclk2 == sysclk) || (pclk2 * 2u32 == sysclk)); - - RCC.cfgr3().modify(|w| w.set_hrtim1sw(Timsw::PLL1_P)); - - Some(sysclk * 2u32) - } - }; - - let rtc = config.ls.init(); - - set_freqs(Clocks { - sys: sysclk, - pclk1: pclk1, - pclk2: pclk2, - pclk1_tim: pclk1 * timer_mul1, - pclk2_tim: pclk2 * timer_mul2, - hclk1: hclk, - #[cfg(rcc_f3)] - adc: adc, - #[cfg(all(rcc_f3, adc3_common))] - adc34: adc34, - #[cfg(all(rcc_f3, not(adc3_common)))] - adc34: None, - #[cfg(stm32f334)] - hrtim: hrtim, - rtc, - }); -} - -#[inline] -fn get_sysclk(config: &Config) -> (Hertz, Option<PllConfig>) { - match (config.sysclk, config.hse) { - (Some(sysclk), Some(hse)) if sysclk == hse => (hse, None), - (Some(sysclk), None) if sysclk == HSI_FREQ => (HSI_FREQ, None), - // If the user selected System clock is different from HSI or HSE - // we will have to setup PLL clock source - (Some(sysclk), _) => { - let (sysclk, pll_config) = calc_pll(config, sysclk); - (sysclk, Some(pll_config)) - } - (None, Some(hse)) => (hse, None), - (None, None) => (HSI_FREQ, None), - } -} - -#[inline] -fn calc_pll(config: &Config, Hertz(sysclk): Hertz) -> (Hertz, PllConfig) { - // Calculates the Multiplier and the Divisor to arrive at - // the required System clock from PLL source frequency - let get_mul_div = |sysclk, pllsrcclk| { - let bus_div = gcd(sysclk, pllsrcclk); - let mut multiplier = sysclk / bus_div; - let mut divisor = pllsrcclk / bus_div; - // Minimum PLL multiplier is two - if multiplier == 1 { - multiplier *= 2; - divisor *= 2; - } - assert!(multiplier <= 16); - assert!(divisor <= 16); - (multiplier, divisor) - }; - // Based on the source of Pll, we calculate the actual system clock - // frequency, PLL's source identifier, multiplier and divisor - let (act_sysclk, pll_src, pll_mul, pll_div) = match config.hse { - Some(Hertz(hse)) => { - let (multiplier, divisor) = get_mul_div(sysclk, hse); - ( - Hertz((hse / divisor) * multiplier), - Pllsrc::HSE_DIV_PREDIV, - into_pll_mul(multiplier), - Some(into_pre_div(divisor)), - ) - } - None => { - cfg_if::cfg_if! { - // For some chips PREDIV is always two, and cannot be changed - if #[cfg(any(flashsize_d, flashsize_e))] { - let (multiplier, divisor) = get_mul_div(sysclk, HSI_FREQ.0); - ( - Hertz((HSI_FREQ.0 / divisor) * multiplier), - Pllsrc::HSI_DIV_PREDIV, - into_pll_mul(multiplier), - Some(into_pre_div(divisor)), - ) - } else { - let pllsrcclk = HSI_FREQ.0 / 2; - let multiplier = sysclk / pllsrcclk; - assert!(multiplier <= 16); - ( - Hertz(pllsrcclk * multiplier), - Pllsrc::HSI_DIV2, - into_pll_mul(multiplier), - None, - ) - } - } - } - }; - ( - act_sysclk, - PllConfig { - pll_src, - pll_mul, - pll_div, - }, - ) -} - -#[inline] -#[allow(unused_variables)] -fn get_usb_pre(config: &Config, sysclk: Hertz, pclk1: Hertz, pll_config: &Option<PllConfig>) -> Usbpre { - cfg_if::cfg_if! { - // Some chips do not have USB - if #[cfg(any(stm32f301, stm32f318, stm32f334))] { - panic!("USB clock not supported by the chip"); - } else { - let usb_ok = config.hse.is_some() && pll_config.is_some() && (pclk1 >= Hertz(10_000_000)); - match (usb_ok, sysclk) { - (true, Hertz(72_000_000)) => Usbpre::DIV1_5, - (true, Hertz(48_000_000)) => Usbpre::DIV1, - _ => panic!( - "USB clock is only valid if the PLL output frequency is either 48MHz or 72MHz" - ), - } - } - } -} - -// This function assumes cases when multiplier is one and it -// being greater than 16 is made impossible -#[inline] -fn into_pll_mul(multiplier: u32) -> Pllmul { - match multiplier { - 2 => Pllmul::MUL2, - 3 => Pllmul::MUL3, - 4 => Pllmul::MUL4, - 5 => Pllmul::MUL5, - 6 => Pllmul::MUL6, - 7 => Pllmul::MUL7, - 8 => Pllmul::MUL8, - 9 => Pllmul::MUL9, - 10 => Pllmul::MUL10, - 11 => Pllmul::MUL11, - 12 => Pllmul::MUL12, - 13 => Pllmul::MUL13, - 14 => Pllmul::MUL14, - 15 => Pllmul::MUL15, - 16 => Pllmul::MUL16, - _ => unreachable!(), - } -} - -// This function assumes the incoming divisor cannot be greater -// than 16 -#[inline] -fn into_pre_div(divisor: u32) -> Prediv { - match divisor { - 1 => Prediv::DIV1, - 2 => Prediv::DIV2, - 3 => Prediv::DIV3, - 4 => Prediv::DIV4, - 5 => Prediv::DIV5, - 6 => Prediv::DIV6, - 7 => Prediv::DIV7, - 8 => Prediv::DIV8, - 9 => Prediv::DIV9, - 10 => Prediv::DIV10, - 11 => Prediv::DIV11, - 12 => Prediv::DIV12, - 13 => Prediv::DIV13, - 14 => Prediv::DIV14, - 15 => Prediv::DIV15, - 16 => Prediv::DIV16, - _ => unreachable!(), - } -} - -// Determine GCD using Euclidean algorithm -#[inline] -fn gcd(mut a: u32, mut b: u32) -> u32 { - while b != 0 { - let r = a % b; - a = b; - b = r; - } - a -} diff --git a/embassy-stm32/src/rcc/g0.rs b/embassy-stm32/src/rcc/g0.rs index b38fe1dcc..ea4422ccc 100644 --- a/embassy-stm32/src/rcc/g0.rs +++ b/embassy-stm32/src/rcc/g0.rs @@ -1,15 +1,16 @@ use crate::pac::flash::vals::Latency; -use crate::pac::rcc::vals::{self, Sw}; +pub use crate::pac::pwr::vals::Vos as VoltageRange; pub use crate::pac::rcc::vals::{ - Hpre as AHBPrescaler, Hsidiv as HSIPrescaler, Pllm, Plln, Pllp, Pllq, Pllr, Ppre as APBPrescaler, + Hpre as AHBPrescaler, Pllm as PllPreDiv, Plln as PllMul, Pllp as PllPDiv, Pllq as PllQDiv, Pllr as PllRDiv, + Pllsrc as PllSource, Ppre as APBPrescaler, Sw as Sysclk, }; use crate::pac::{FLASH, PWR, RCC}; -use crate::rcc::{set_freqs, Clocks}; use crate::time::Hertz; /// HSI speed pub const HSI_FREQ: Hertz = Hertz(16_000_000); +/// HSE Mode #[derive(Clone, Copy, Eq, PartialEq)] pub enum HseMode { /// crystal/ceramic oscillator (HSEBYP=0) @@ -18,353 +19,291 @@ pub enum HseMode { Bypass, } -/// System clock mux source -#[derive(Clone, Copy)] -pub enum ClockSrc { - HSE(Hertz, HseMode), - HSI(HSIPrescaler), - PLL(PllConfig), - LSI, -} - -/// The PLL configuration. -/// -/// * `VCOCLK = source / m * n` -/// * `PLLRCLK = VCOCLK / r` -/// * `PLLQCLK = VCOCLK / q` -/// * `PLLPCLK = VCOCLK / p` -#[derive(Clone, Copy)] -pub struct PllConfig { - /// The source from which the PLL receives a clock signal - pub source: PllSource, - /// The initial divisor of that clock signal - pub m: Pllm, - /// The PLL VCO multiplier, which must be in the range `8..=86`. - pub n: Plln, - /// The final divisor for `PLLRCLK` output which drives the system clock - pub r: Pllr, - - /// The divisor for the `PLLQCLK` output, if desired - pub q: Option<Pllq>, - - /// The divisor for the `PLLPCLK` output, if desired - pub p: Option<Pllp>, -} - -impl Default for PllConfig { - #[inline] - fn default() -> PllConfig { - // HSI / 1 * 8 / 2 = 64 MHz - PllConfig { - source: PllSource::HSI, - m: Pllm::DIV1, - n: Plln::MUL8, - r: Pllr::DIV2, - q: None, - p: None, - } - } -} - +/// HSE Configuration #[derive(Clone, Copy, Eq, PartialEq)] -pub enum PllSource { - HSI, - HSE(Hertz, HseMode), +pub struct Hse { + /// HSE frequency. + pub freq: Hertz, + /// HSE mode. + pub mode: HseMode, } -/// Sets the source for the 48MHz clock to the USB peripheral. -#[cfg(any(stm32g0b1, stm32g0c1, stm32g0b0))] -pub enum UsbSrc { - /// Use the High Speed Internal Oscillator. The CRS must be used to calibrate the - /// oscillator to comply with the USB specification for oscillator tolerance. - #[cfg(any(stm32g0b1, stm32g0c1))] - Hsi48(super::Hsi48Config), - /// Use the PLLQ output. The PLL must be configured to output a 48MHz clock. The - /// PLL needs to be using the HSE source to comply with the USB specification for oscillator - /// tolerance. - PllQ, - /// Use the HSE source directly. The HSE must be a 48MHz source. The HSE source must comply - /// with the USB specification for oscillator tolerance. - HSE, +/// 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: PllSource, + + /// PLL pre-divider + pub prediv: PllPreDiv, + + /// PLL multiplication factor for VCO + pub mul: PllMul, + + /// PLL division factor for P clock (ADC Clock) + pub divp: Option<PllPDiv>, + + /// PLL division factor for Q clock (USB, I2S23, SAI1, FDCAN, QSPI) + pub divq: Option<PllQDiv>, + + /// PLL division factor for R clock (SYSCLK) + pub divr: Option<PllRDiv>, } /// Clocks configutation +#[non_exhaustive] pub struct Config { - pub mux: ClockSrc, + /// HSI Enable + pub hsi: bool, + + /// HSE Configuration + pub hse: Option<Hse>, + + /// System Clock Configuration + pub sys: Sysclk, + + /// HSI48 Configuration + #[cfg(crs)] + pub hsi48: Option<super::Hsi48Config>, + + /// PLL Configuration + pub pll: Option<Pll>, + + /// If PLL is requested as the main clock source in the `sys` field then the PLL configuration + /// MUST turn on the PLLR output. pub ahb_pre: AHBPrescaler, - pub apb_pre: APBPrescaler, - pub low_power_run: bool, + pub apb1_pre: APBPrescaler, + + /// Low-Speed Clock Configuration pub ls: super::LsConfig, - #[cfg(any(stm32g0b1, stm32g0c1, stm32g0b0))] - pub usb_src: Option<UsbSrc>, + + pub low_power_run: bool, + + pub voltage_range: VoltageRange, + + /// Per-peripheral kernel clock selection muxes + pub mux: super::mux::ClockMux, } impl Default for Config { #[inline] fn default() -> Config { Config { - mux: ClockSrc::HSI(HSIPrescaler::DIV1), + hsi: true, + hse: None, + sys: Sysclk::HSI, + #[cfg(crs)] + hsi48: Some(Default::default()), + pll: None, ahb_pre: AHBPrescaler::DIV1, - apb_pre: APBPrescaler::DIV1, + apb1_pre: APBPrescaler::DIV1, low_power_run: false, ls: Default::default(), - #[cfg(any(stm32g0b1, stm32g0c1, stm32g0b0))] - usb_src: None, + voltage_range: VoltageRange::RANGE1, + mux: Default::default(), } } } -impl PllConfig { - pub(crate) fn init(self) -> (Hertz, Option<Hertz>, Option<Hertz>) { - let (src, input_freq) = match self.source { - PllSource::HSI => (vals::Pllsrc::HSI, HSI_FREQ), - PllSource::HSE(freq, _) => (vals::Pllsrc::HSE, freq), - }; - - let m_freq = input_freq / self.m; - // RM0454 § 5.4.4: - // > Caution: The software must set these bits so that the PLL input frequency after the - // > /M divider is between 2.66 and 16 MHz. - debug_assert!(m_freq.0 >= 2_660_000 && m_freq.0 <= 16_000_000); - - let n_freq = m_freq * self.n as u32; - // RM0454 § 5.4.4: - // > Caution: The software must set these bits so that the VCO output frequency is between - // > 64 and 344 MHz. - debug_assert!(n_freq.0 >= 64_000_000 && n_freq.0 <= 344_000_000); - - let r_freq = n_freq / self.r; - // RM0454 § 5.4.4: - // > Caution: The software must set this bitfield so as not to exceed 64 MHz on this clock. - debug_assert!(r_freq.0 <= 64_000_000); - - let q_freq = self.q.map(|q| n_freq / q); - let p_freq = self.p.map(|p| n_freq / p); - - // RM0454 § 5.2.3: - // > To modify the PLL configuration, proceed as follows: - // > 1. Disable the PLL by setting PLLON to 0 in Clock control register (RCC_CR). - RCC.cr().modify(|w| w.set_pllon(false)); - - // > 2. Wait until PLLRDY is cleared. The PLL is now fully stopped. - while RCC.cr().read().pllrdy() {} - - // > 3. Change the desired parameter. - // Enable whichever clock source we're using, and wait for it to become ready - match self.source { - PllSource::HSI => { - RCC.cr().write(|w| w.set_hsion(true)); - while !RCC.cr().read().hsirdy() {} - } - PllSource::HSE(_, mode) => { - RCC.cr().write(|w| { - w.set_hsebyp(mode != HseMode::Oscillator); - w.set_hseon(true); - }); - while !RCC.cr().read().hserdy() {} - } - } - - // Configure PLLCFGR - RCC.pllcfgr().modify(|w| { - w.set_pllr(self.r); - w.set_pllren(false); - w.set_pllq(self.q.unwrap_or(Pllq::DIV2)); - w.set_pllqen(false); - w.set_pllp(self.p.unwrap_or(Pllp::DIV2)); - w.set_pllpen(false); - w.set_plln(self.n); - w.set_pllm(self.m); - w.set_pllsrc(src) - }); - - // > 4. Enable the PLL again by setting PLLON to 1. - RCC.cr().modify(|w| w.set_pllon(true)); - - // Wait for the PLL to become ready - while !RCC.cr().read().pllrdy() {} - - // > 5. Enable the desired PLL outputs by configuring PLLPEN, PLLQEN, and PLLREN in PLL - // > configuration register (RCC_PLLCFGR). - RCC.pllcfgr().modify(|w| { - // We'll use R for system clock, so enable that unconditionally - w.set_pllren(true); - - // We may also use Q or P - w.set_pllqen(self.q.is_some()); - w.set_pllpen(self.p.is_some()); - }); - - (r_freq, q_freq, p_freq) - } +#[derive(Default)] +pub struct PllFreq { + pub pll_p: Option<Hertz>, + pub pll_q: Option<Hertz>, + pub pll_r: Option<Hertz>, } pub(crate) unsafe fn init(config: Config) { - let mut pll1_q_freq = None; - let mut pll1_p_freq = None; - - let (sys_clk, sw) = match config.mux { - ClockSrc::HSI(div) => { - // Enable HSI - RCC.cr().write(|w| { - w.set_hsidiv(div); - w.set_hsion(true) - }); + // Configure HSI + let hsi = match config.hsi { + false => { + RCC.cr().modify(|w| w.set_hsion(false)); + None + } + true => { + RCC.cr().modify(|w| w.set_hsion(true)); while !RCC.cr().read().hsirdy() {} - - (HSI_FREQ / div, Sw::HSI) - } - ClockSrc::HSE(freq, mode) => { - // Enable HSE - RCC.cr().write(|w| { - w.set_hseon(true); - w.set_hsebyp(mode != HseMode::Oscillator); - }); - while !RCC.cr().read().hserdy() {} - - (freq, Sw::HSE) - } - ClockSrc::PLL(pll) => { - let (r_freq, q_freq, p_freq) = pll.init(); - - pll1_q_freq = q_freq; - pll1_p_freq = p_freq; - - (r_freq, Sw::PLL1_R) - } - ClockSrc::LSI => { - // Enable LSI - RCC.csr().write(|w| w.set_lsion(true)); - while !RCC.csr().read().lsirdy() {} - (super::LSI_FREQ, Sw::LSI) + Some(HSI_FREQ) } }; - // Determine the flash latency implied by the target clock speed - // RM0454 § 3.3.4: - let target_flash_latency = if sys_clk.0 <= 24_000_000 { - Latency::WS0 - } else if sys_clk.0 <= 48_000_000 { - Latency::WS1 - } else { - Latency::WS2 - }; - - // 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; + // Configure HSE + let hse = match config.hse { + None => { + RCC.cr().modify(|w| w.set_hseon(false)); + None } - - // RM0454 § 3.3.5: - // > 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, config.apb_pre); - 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; + Some(hse) => { + match hse.mode { + HseMode::Bypass => assert!(max::HSE_BYP.contains(&hse.freq)), + HseMode::Oscillator => assert!(max::HSE_OSC.contains(&hse.freq)), } - } - // Set the flash latency to require fewer wait states - FLASH.acr().modify(|w| w.set_latency(target_flash_latency)); - } - - let ahb_freq = sys_clk / config.ahb_pre; - - let (apb_freq, apb_tim_freq) = match config.apb_pre { - APBPrescaler::DIV1 => (ahb_freq, ahb_freq), - pre => { - let freq = ahb_freq / pre; - (freq, freq * 2u32) + RCC.cr().modify(|w| w.set_hsebyp(hse.mode != HseMode::Oscillator)); + RCC.cr().modify(|w| w.set_hseon(true)); + while !RCC.cr().read().hserdy() {} + Some(hse.freq) } }; + // Configure HSI48 if required + #[cfg(crs)] + let hsi48 = config.hsi48.map(super::init_hsi48); + + let pll = config + .pll + .map(|pll_config| { + let src_freq = match pll_config.source { + PllSource::HSI => unwrap!(hsi), + PllSource::HSE => unwrap!(hse), + _ => unreachable!(), + }; + + // Disable PLL before configuration + RCC.cr().modify(|w| w.set_pllon(false)); + while RCC.cr().read().pllrdy() {} + + let in_freq = src_freq / pll_config.prediv; + assert!(max::PLL_IN.contains(&in_freq)); + let internal_freq = in_freq * pll_config.mul; + + assert!(max::PLL_VCO.contains(&internal_freq)); + + RCC.pllcfgr().write(|w| { + w.set_plln(pll_config.mul); + w.set_pllm(pll_config.prediv); + w.set_pllsrc(pll_config.source.into()); + }); + + let pll_p_freq = pll_config.divp.map(|div_p| { + RCC.pllcfgr().modify(|w| { + w.set_pllp(div_p); + w.set_pllpen(true); + }); + let freq = internal_freq / div_p; + assert!(max::PLL_P.contains(&freq)); + freq + }); + + let pll_q_freq = pll_config.divq.map(|div_q| { + RCC.pllcfgr().modify(|w| { + w.set_pllq(div_q); + w.set_pllqen(true); + }); + let freq = internal_freq / div_q; + assert!(max::PLL_Q.contains(&freq)); + freq + }); + + let pll_r_freq = pll_config.divr.map(|div_r| { + RCC.pllcfgr().modify(|w| { + w.set_pllr(div_r); + w.set_pllren(true); + }); + let freq = internal_freq / div_r; + assert!(max::PLL_R.contains(&freq)); + freq + }); + + // 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, + } + }) + .unwrap_or_default(); + + let sys = match config.sys { + Sysclk::HSI => unwrap!(hsi), + Sysclk::HSE => unwrap!(hse), + Sysclk::PLL1_R => unwrap!(pll.pll_r), + _ => unreachable!(), + }; + + assert!(max::SYSCLK.contains(&sys)); + + // Calculate the AHB frequency (HCLK), among other things so we can calculate the correct flash read latency. + let hclk = sys / config.ahb_pre; + assert!(max::HCLK.contains(&hclk)); + + let (pclk1, pclk1_tim) = super::util::calc_pclk(hclk, config.apb1_pre); + assert!(max::PCLK.contains(&pclk1)); + + let latency = match (config.voltage_range, hclk.0) { + (VoltageRange::RANGE1, ..=24_000_000) => Latency::WS0, + (VoltageRange::RANGE1, ..=48_000_000) => Latency::WS1, + (VoltageRange::RANGE1, _) => Latency::WS2, + (VoltageRange::RANGE2, ..=8_000_000) => Latency::WS0, + (VoltageRange::RANGE2, ..=16_000_000) => Latency::WS1, + (VoltageRange::RANGE2, _) => Latency::WS2, + _ => unreachable!(), + }; + + // Configure flash read access latency based on voltage scale and frequency (RM0444 3.3.4) + FLASH.acr().modify(|w| { + w.set_latency(latency); + }); + + // Spin until the effective flash latency is set. + while FLASH.acr().read().latency() != latency {} + + // Now that boost mode and flash read access latency are configured, set up SYSCLK + RCC.cfgr().modify(|w| { + w.set_sw(config.sys); + w.set_hpre(config.ahb_pre); + w.set_ppre(config.apb1_pre); + }); + if config.low_power_run { - assert!(sys_clk.0 <= 2_000_000); + assert!(sys <= Hertz(2_000_000)); PWR.cr1().modify(|w| w.set_lpr(true)); } let rtc = config.ls.init(); - let lse_freq = config.ls.lse.map(|lse| lse.frequency); - let hsi_freq = (sw == Sw::HSI).then_some(HSI_FREQ); - let hsi_div_8_freq = hsi_freq.map(|f| f / 8u32); - let lsi_freq = (sw == Sw::LSI).then_some(super::LSI_FREQ); - let hse_freq = (sw == Sw::HSE).then_some(sys_clk); + config.mux.init(); - #[cfg(any(stm32g0b1, stm32g0c1, stm32g0b0))] - let hsi48_freq = config.usb_src.and_then(|config| { - match config { - UsbSrc::PllQ => { - // Make sure the PLLQ is enabled and running at 48Mhz - assert!(pll1_q_freq.is_some() && pll1_q_freq.unwrap().0 == 48_000_000); - RCC.ccipr2() - .modify(|w| w.set_usbsel(crate::pac::rcc::vals::Usbsel::PLL1_Q)); - None - } - UsbSrc::HSE => { - // Make sure the HSE is enabled and running at 48Mhz - assert!(hse_freq.is_some() && hse_freq.unwrap().0 == 48_000_000); - RCC.ccipr2() - .modify(|w| w.set_usbsel(crate::pac::rcc::vals::Usbsel::HSE)); - None - } - #[cfg(any(stm32g0b1, stm32g0c1))] - UsbSrc::Hsi48(config) => { - let freq = super::init_hsi48(config); - RCC.ccipr2() - .modify(|w| w.set_usbsel(crate::pac::rcc::vals::Usbsel::HSI48)); - Some(freq) - } - } - }); - #[cfg(not(any(stm32g0b1, stm32g0c1, stm32g0b0)))] - let hsi48_freq: Option<Hertz> = None; + set_clocks!( + sys: Some(sys), + hclk1: Some(hclk), + pclk1: Some(pclk1), + pclk1_tim: Some(pclk1_tim), + pll1_p: pll.pll_p, + pll1_q: pll.pll_q, + pll1_r: pll.pll_r, + hsi: hsi, + hse: hse, + #[cfg(crs)] + hsi48: hsi48, + rtc: rtc, + hsi_div_8: hsi.map(|h| h / 8u32), + hsi_div_488: hsi.map(|h| h / 488u32), - set_freqs(Clocks { - sys: sys_clk, - hclk1: ahb_freq, - pclk1: apb_freq, - pclk1_tim: apb_tim_freq, - hsi: hsi_freq, - hsi48: hsi48_freq, - hsi_div_8: hsi_div_8_freq, - hse: hse_freq, - lse: lse_freq, - lsi: lsi_freq, - pll1_q: pll1_q_freq, - pll1_p: pll1_p_freq, - rtc, - }); + // TODO + lsi: None, + lse: None, + ); +} + +mod max { + use core::ops::RangeInclusive; + + use crate::time::Hertz; + + pub(crate) const HSE_OSC: RangeInclusive<Hertz> = Hertz(4_000_000)..=Hertz(48_000_000); + pub(crate) const HSE_BYP: RangeInclusive<Hertz> = Hertz(0)..=Hertz(48_000_000); + pub(crate) const SYSCLK: RangeInclusive<Hertz> = Hertz(0)..=Hertz(64_000_000); + pub(crate) const PCLK: RangeInclusive<Hertz> = Hertz(8)..=Hertz(64_000_000); + pub(crate) const HCLK: RangeInclusive<Hertz> = Hertz(0)..=Hertz(64_000_000); + pub(crate) const PLL_IN: RangeInclusive<Hertz> = Hertz(2_660_000)..=Hertz(16_000_000); + pub(crate) const PLL_VCO: RangeInclusive<Hertz> = Hertz(96_000_000)..=Hertz(344_000_000); + pub(crate) const PLL_P: RangeInclusive<Hertz> = Hertz(3_090_000)..=Hertz(122_000_000); + pub(crate) const PLL_Q: RangeInclusive<Hertz> = Hertz(12_000_000)..=Hertz(128_000_000); + pub(crate) const PLL_R: RangeInclusive<Hertz> = Hertz(12_000_000)..=Hertz(64_000_000); } diff --git a/embassy-stm32/src/rcc/g4.rs b/embassy-stm32/src/rcc/g4.rs index fca364c21..cd2d2a8a2 100644 --- a/embassy-stm32/src/rcc/g4.rs +++ b/embassy-stm32/src/rcc/g4.rs @@ -1,40 +1,30 @@ -use stm32_metapac::flash::vals::Latency; -use stm32_metapac::rcc::vals::{Adcsel, Pllsrc, Sw}; -use stm32_metapac::FLASH; - +use crate::pac::flash::vals::Latency; pub use crate::pac::rcc::vals::{ - Adcsel as AdcClockSource, Hpre as AHBPrescaler, Pllm as PllM, Plln as PllN, Pllp as PllP, Pllq as PllQ, - Pllr as PllR, Ppre as APBPrescaler, + Hpre as AHBPrescaler, Pllm as PllPreDiv, Plln as PllMul, Pllp as PllPDiv, Pllq as PllQDiv, Pllr as PllRDiv, + Pllsrc as PllSource, Ppre as APBPrescaler, Sw as Sysclk, }; -use crate::pac::{PWR, RCC}; -use crate::rcc::{set_freqs, Clocks}; +use crate::pac::{FLASH, PWR, RCC}; use crate::time::Hertz; /// HSI speed pub const HSI_FREQ: Hertz = Hertz(16_000_000); -/// System clock mux source -#[derive(Clone, Copy)] -pub enum ClockSrc { - HSE(Hertz), - HSI, - PLL, +/// HSE Mode +#[derive(Clone, Copy, Eq, PartialEq)] +pub enum HseMode { + /// crystal/ceramic oscillator (HSEBYP=0) + Oscillator, + /// external analog clock (low swing) (HSEBYP=1) + Bypass, } -/// PLL clock input source -#[derive(Clone, Copy, Debug)] -pub enum PllSource { - HSI, - HSE(Hertz), -} - -impl Into<Pllsrc> for PllSource { - fn into(self) -> Pllsrc { - match self { - PllSource::HSE(..) => Pllsrc::HSE, - PllSource::HSI => Pllsrc::HSI, - } - } +/// HSE Configuration +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct Hse { + /// HSE frequency. + pub freq: Hertz, + /// HSE mode. + pub mode: HseMode, } /// PLL Configuration @@ -47,68 +37,79 @@ pub struct Pll { pub source: PllSource, /// PLL pre-divider - pub prediv_m: PllM, + pub prediv: PllPreDiv, /// PLL multiplication factor for VCO - pub mul_n: PllN, + pub mul: PllMul, /// PLL division factor for P clock (ADC Clock) - pub div_p: Option<PllP>, + pub divp: Option<PllPDiv>, /// PLL division factor for Q clock (USB, I2S23, SAI1, FDCAN, QSPI) - pub div_q: Option<PllQ>, + pub divq: Option<PllQDiv>, /// PLL division factor for R clock (SYSCLK) - pub div_r: Option<PllR>, -} - -/// 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(super::Hsi48Config), - /// 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, + pub divr: Option<PllRDiv>, } /// Clocks configutation +#[non_exhaustive] pub struct Config { - pub mux: ClockSrc, + /// HSI Enable + pub hsi: bool, + + /// HSE Configuration + pub hse: Option<Hse>, + + /// System Clock Configuration + pub sys: Sysclk, + + /// HSI48 Configuration + pub hsi48: Option<super::Hsi48Config>, + + /// PLL Configuration + pub pll: Option<Pll>, + + /// If PLL is requested as the main clock source in the `sys` field then the PLL configuration + /// MUST turn on the PLLR output. pub ahb_pre: AHBPrescaler, 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<Pll>, - /// Sets the clock source for the 48MHz clock used by the USB and RNG peripherals. - pub clock_48mhz_src: Option<Clock48MhzSrc>, - pub adc12_clock_source: AdcClockSource, - pub adc345_clock_source: AdcClockSource, + pub low_power_run: bool, + + /// Low-Speed Clock Configuration pub ls: super::LsConfig, + + /// Enable range1 boost mode + /// Recommended when the SYSCLK frequency is greater than 150MHz. + pub boost: bool, + + /// Per-peripheral kernel clock selection muxes + pub mux: super::mux::ClockMux, } impl Default for Config { #[inline] fn default() -> Config { Config { - mux: ClockSrc::HSI, + hsi: true, + hse: None, + sys: Sysclk::HSI, + hsi48: Some(Default::default()), + pll: None, ahb_pre: AHBPrescaler::DIV1, apb1_pre: APBPrescaler::DIV1, apb2_pre: APBPrescaler::DIV1, low_power_run: false, - pll: None, - clock_48mhz_src: Some(Clock48MhzSrc::Hsi48(Default::default())), - adc12_clock_source: Adcsel::DISABLE, - adc345_clock_source: Adcsel::DISABLE, ls: Default::default(), + boost: false, + mux: Default::default(), } } } +#[derive(Default)] pub struct PllFreq { pub pll_p: Option<Hertz>, pub pll_q: Option<Hertz>, @@ -116,208 +117,233 @@ pub struct PllFreq { } pub(crate) unsafe fn init(config: Config) { - let pll_freq = config.pll.map(|pll_config| { - let src_freq = match pll_config.source { - PllSource::HSI => { - RCC.cr().write(|w| w.set_hsion(true)); - while !RCC.cr().read().hsirdy() {} - - HSI_FREQ - } - PllSource::HSE(freq) => { - RCC.cr().write(|w| w.set_hseon(true)); - while !RCC.cr().read().hserdy() {} - freq - } - }; - - // 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 * pll_config.mul_n; - - RCC.pllcfgr().write(|w| { - w.set_plln(pll_config.mul_n); - w.set_pllm(pll_config.prediv_m); - w.set_pllsrc(pll_config.source.into()); - }); - - let pll_p_freq = pll_config.div_p.map(|div_p| { - RCC.pllcfgr().modify(|w| { - w.set_pllp(div_p); - w.set_pllpen(true); - }); - internal_freq / div_p - }); - - let pll_q_freq = pll_config.div_q.map(|div_q| { - RCC.pllcfgr().modify(|w| { - w.set_pllq(div_q); - w.set_pllqen(true); - }); - internal_freq / div_q - }); - - let pll_r_freq = pll_config.div_r.map(|div_r| { - RCC.pllcfgr().modify(|w| { - w.set_pllr(div_r); - w.set_pllren(true); - }); - internal_freq / div_r - }); - - // 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, + // Configure HSI + let hsi = match config.hsi { + false => { + RCC.cr().modify(|w| w.set_hsion(false)); + None } - }); - - let (sys_clk, sw) = match config.mux { - ClockSrc::HSI => { - // Enable HSI - RCC.cr().write(|w| w.set_hsion(true)); + true => { + RCC.cr().modify(|w| w.set_hsion(true)); while !RCC.cr().read().hsirdy() {} - - (HSI_FREQ, Sw::HSI) - } - ClockSrc::HSE(freq) => { - // Enable HSE - RCC.cr().write(|w| w.set_hseon(true)); - while !RCC.cr().read().hserdy() {} - - (freq, 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)); - } - } - - (Hertz(freq), Sw::PLL1_R) + Some(HSI_FREQ) } }; + // Configure HSE + let hse = match config.hse { + None => { + RCC.cr().modify(|w| w.set_hseon(false)); + None + } + Some(hse) => { + match hse.mode { + HseMode::Bypass => assert!(max::HSE_BYP.contains(&hse.freq)), + HseMode::Oscillator => assert!(max::HSE_OSC.contains(&hse.freq)), + } + + RCC.cr().modify(|w| w.set_hsebyp(hse.mode != HseMode::Oscillator)); + RCC.cr().modify(|w| w.set_hseon(true)); + while !RCC.cr().read().hserdy() {} + Some(hse.freq) + } + }; + + // Configure HSI48 if required + let hsi48 = config.hsi48.map(super::init_hsi48); + + let pll = config + .pll + .map(|pll_config| { + let src_freq = match pll_config.source { + PllSource::HSI => unwrap!(hsi), + PllSource::HSE => unwrap!(hse), + _ => unreachable!(), + }; + + // Disable PLL before configuration + RCC.cr().modify(|w| w.set_pllon(false)); + while RCC.cr().read().pllrdy() {} + + let in_freq = src_freq / pll_config.prediv; + assert!(max::PLL_IN.contains(&in_freq)); + let internal_freq = in_freq * pll_config.mul; + + assert!(max::PLL_VCO.contains(&internal_freq)); + + RCC.pllcfgr().write(|w| { + w.set_plln(pll_config.mul); + w.set_pllm(pll_config.prediv); + w.set_pllsrc(pll_config.source.into()); + }); + + let pll_p_freq = pll_config.divp.map(|div_p| { + RCC.pllcfgr().modify(|w| { + w.set_pllp(div_p); + w.set_pllpen(true); + }); + let freq = internal_freq / div_p; + assert!(max::PLL_P.contains(&freq)); + freq + }); + + let pll_q_freq = pll_config.divq.map(|div_q| { + RCC.pllcfgr().modify(|w| { + w.set_pllq(div_q); + w.set_pllqen(true); + }); + let freq = internal_freq / div_q; + assert!(max::PLL_Q.contains(&freq)); + freq + }); + + let pll_r_freq = pll_config.divr.map(|div_r| { + RCC.pllcfgr().modify(|w| { + w.set_pllr(div_r); + w.set_pllren(true); + }); + let freq = internal_freq / div_r; + assert!(max::PLL_R.contains(&freq)); + freq + }); + + // 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, + } + }) + .unwrap_or_default(); + + let sys = match config.sys { + Sysclk::HSI => unwrap!(hsi), + Sysclk::HSE => unwrap!(hse), + Sysclk::PLL1_R => unwrap!(pll.pll_r), + _ => unreachable!(), + }; + + assert!(max::SYSCLK.contains(&sys)); + + // Calculate the AHB frequency (HCLK), among other things so we can calculate the correct flash read latency. + let hclk = sys / config.ahb_pre; + assert!(max::HCLK.contains(&hclk)); + + let (pclk1, pclk1_tim) = super::util::calc_pclk(hclk, config.apb1_pre); + let (pclk2, pclk2_tim) = super::util::calc_pclk(hclk, config.apb2_pre); + assert!(max::PCLK.contains(&pclk2)); + assert!(max::PCLK.contains(&pclk2)); + + // Configure Core Boost mode ([RM0440] p234 – inverted because setting r1mode to 0 enables boost mode!) + if config.boost { + // RM0440 p235 + // “The sequence to switch from Range1 normal mode to Range1 boost mode is: + // 1. The system clock must be divided by 2 using the AHB prescaler before switching to a higher system frequency. + RCC.cfgr().modify(|w| w.set_hpre(AHBPrescaler::DIV2)); + // 2. Clear the R1MODE bit in the PWR_CR5 register. (enables boost mode) + PWR.cr5().modify(|w| w.set_r1mode(false)); + + // Below: + // 3. Adjust wait states according to new freq target + // 4. Configure and switch to new frequency + } + + let latency = match (config.boost, hclk.0) { + (true, ..=34_000_000) => Latency::WS0, + (true, ..=68_000_000) => Latency::WS1, + (true, ..=102_000_000) => Latency::WS2, + (true, ..=136_000_000) => Latency::WS3, + (true, _) => Latency::WS4, + + (false, ..=36_000_000) => Latency::WS0, + (false, ..=60_000_000) => Latency::WS1, + (false, ..=90_000_000) => Latency::WS2, + (false, ..=120_000_000) => Latency::WS3, + (false, _) => Latency::WS4, + }; + + // Configure flash read access latency based on boost mode and frequency (RM0440 p98) + FLASH.acr().modify(|w| { + w.set_latency(latency); + }); + + // Spin until the effective flash latency is set. + while FLASH.acr().read().latency() != latency {} + + if config.boost { + // 5. Wait for at least 1us and then reconfigure the AHB prescaler to get the needed HCLK clock frequency. + cortex_m::asm::delay(16); + } + + // Now that boost mode and flash read access latency are configured, set up SYSCLK RCC.cfgr().modify(|w| { - w.set_sw(sw); + w.set_sw(config.sys); w.set_hpre(config.ahb_pre); w.set_ppre1(config.apb1_pre); w.set_ppre2(config.apb2_pre); }); - let ahb_freq = sys_clk / config.ahb_pre; - - let (apb1_freq, apb1_tim_freq) = match config.apb1_pre { - APBPrescaler::DIV1 => (ahb_freq, ahb_freq), - pre => { - let freq = ahb_freq / pre; - (freq, freq * 2u32) - } - }; - - let (apb2_freq, apb2_tim_freq) = match config.apb2_pre { - APBPrescaler::DIV1 => (ahb_freq, ahb_freq), - pre => { - let freq = ahb_freq / pre; - (freq, freq * 2u32) - } - }; - - // 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::PLL1_Q - } - Clock48MhzSrc::Hsi48(config) => { - super::init_hsi48(config); - crate::pac::rcc::vals::Clk48sel::HSI48 - } - }; - - RCC.ccipr().modify(|w| w.set_clk48sel(source)); - } - - RCC.ccipr().modify(|w| w.set_adc12sel(config.adc12_clock_source)); - RCC.ccipr().modify(|w| w.set_adc345sel(config.adc345_clock_source)); - - let adc12_ck = match config.adc12_clock_source { - AdcClockSource::DISABLE => None, - AdcClockSource::PLL1_P => pll_freq.as_ref().unwrap().pll_p, - AdcClockSource::SYS => Some(sys_clk), - _ => unreachable!(), - }; - - let adc345_ck = match config.adc345_clock_source { - AdcClockSource::DISABLE => None, - AdcClockSource::PLL1_P => pll_freq.as_ref().unwrap().pll_p, - AdcClockSource::SYS => Some(sys_clk), - _ => unreachable!(), - }; - if config.low_power_run { - assert!(sys_clk <= Hertz(2_000_000)); + assert!(sys <= Hertz(2_000_000)); PWR.cr1().modify(|w| w.set_lpr(true)); } let rtc = config.ls.init(); - set_freqs(Clocks { - sys: sys_clk, - hclk1: ahb_freq, - hclk2: ahb_freq, - hclk3: ahb_freq, - pclk1: apb1_freq, - pclk1_tim: apb1_tim_freq, - pclk2: apb2_freq, - pclk2_tim: apb2_tim_freq, - adc: adc12_ck, - adc34: adc345_ck, - pll1_p: None, - pll1_q: None, // TODO - hse: None, // TODO - rtc, - }); + config.mux.init(); + + set_clocks!( + sys: Some(sys), + hclk1: Some(hclk), + hclk2: Some(hclk), + hclk3: Some(hclk), + pclk1: Some(pclk1), + pclk1_tim: Some(pclk1_tim), + pclk2: Some(pclk2), + pclk2_tim: Some(pclk2_tim), + pll1_p: pll.pll_p, + pll1_q: pll.pll_q, + pll1_r: pll.pll_r, + hsi: hsi, + hse: hse, + hsi48: hsi48, + rtc: rtc, + ); +} + +/// Acceptable Frequency Ranges +/// Currently assuming voltage scaling range 1 boost mode. +/// Where not specified in the generic G4 reference manual (RM0440), values taken from the STM32G474 datasheet. +/// If acceptable ranges for other G4-family chips differ, make additional max modules gated behind cfg attrs. +mod max { + use core::ops::RangeInclusive; + + use crate::time::Hertz; + + /// HSE Frequency Range (RM0440 p280) + pub(crate) const HSE_OSC: RangeInclusive<Hertz> = Hertz(4_000_000)..=Hertz(48_000_000); + + /// External Clock Frequency Range (RM0440 p280) + pub(crate) const HSE_BYP: RangeInclusive<Hertz> = Hertz(0)..=Hertz(48_000_000); + + /// SYSCLK Frequency Range (RM0440 p282) + pub(crate) const SYSCLK: RangeInclusive<Hertz> = Hertz(0)..=Hertz(170_000_000); + + /// PLL Output Frequency Range (RM0440 p281, STM32G474 Datasheet p123, Table 46) + pub(crate) const PCLK: RangeInclusive<Hertz> = Hertz(8)..=Hertz(170_000_000); + + /// HCLK (AHB) Clock Frequency Range (STM32G474 Datasheet) + pub(crate) const HCLK: RangeInclusive<Hertz> = Hertz(0)..=Hertz(170_000_000); + + /// PLL Source Frequency Range (STM32G474 Datasheet p123, Table 46) + pub(crate) const PLL_IN: RangeInclusive<Hertz> = Hertz(2_660_000)..=Hertz(16_000_000); + + /// PLL VCO (internal) Frequency Range (STM32G474 Datasheet p123, Table 46) + pub(crate) const PLL_VCO: RangeInclusive<Hertz> = Hertz(96_000_000)..=Hertz(344_000_000); + pub(crate) const PLL_P: RangeInclusive<Hertz> = Hertz(2_064_500)..=Hertz(170_000_000); + pub(crate) const PLL_Q: RangeInclusive<Hertz> = Hertz(8_000_000)..=Hertz(170_000_000); + pub(crate) const PLL_R: RangeInclusive<Hertz> = Hertz(8_000_000)..=Hertz(170_000_000); } diff --git a/embassy-stm32/src/rcc/h.rs b/embassy-stm32/src/rcc/h.rs index 15b51a398..bab8bb19e 100644 --- a/embassy-stm32/src/rcc/h.rs +++ b/embassy-stm32/src/rcc/h.rs @@ -2,17 +2,11 @@ use core::ops::RangeInclusive; use crate::pac; use crate::pac::pwr::vals::Vos; -#[cfg(stm32h5)] -pub use crate::pac::rcc::vals::Adcdacsel as AdcClockSource; -#[cfg(stm32h7)] -pub use crate::pac::rcc::vals::Adcsel as AdcClockSource; pub use crate::pac::rcc::vals::{ - Ckpersel as PerClockSource, Hsidiv as HSIPrescaler, Plldiv as PllDiv, Pllm as PllPreDiv, Plln as PllMul, - Pllsrc as PllSource, Sw as Sysclk, + Hsidiv as HSIPrescaler, Plldiv as PllDiv, Pllm as PllPreDiv, Plln as PllMul, Pllsrc as PllSource, Sw as Sysclk, }; -use crate::pac::rcc::vals::{Ckpersel, Pllrge, Pllvcosel, Timpre}; +use crate::pac::rcc::vals::{Pllrge, Pllvcosel, Timpre}; use crate::pac::{FLASH, PWR, RCC}; -use crate::rcc::{set_freqs, Clocks}; use crate::time::Hertz; /// HSI speed @@ -171,22 +165,7 @@ pub enum SupplyConfig { /// This is only used in certain power supply configurations: /// SMPSLDO, SMPSExternalLDO, SMPSExternalLDOBypass. #[cfg(any(pwr_h7rm0399, pwr_h7rm0455, pwr_h7rm0468))] -#[derive(PartialEq)] -pub enum SMPSSupplyVoltage { - V1_8, - V2_5, -} - -#[cfg(any(pwr_h7rm0399, pwr_h7rm0455, pwr_h7rm0468))] -impl SMPSSupplyVoltage { - /// Convert SMPSSupplyVoltage to u8 representation. - fn to_u8(&self) -> u8 { - match self { - SMPSSupplyVoltage::V1_8 => 0b01, - SMPSSupplyVoltage::V2_5 => 0b10, - } - } -} +pub use pac::pwr::vals::Sdlevel as SMPSSupplyVoltage; /// Configuration of the core clocks #[non_exhaustive] @@ -210,14 +189,15 @@ pub struct Config { #[cfg(stm32h7)] pub apb4_pre: APBPrescaler, - pub per_clock_source: PerClockSource, - pub adc_clock_source: AdcClockSource, pub timer_prescaler: TimerPrescaler, pub voltage_scale: VoltageScale, pub ls: super::LsConfig, #[cfg(any(pwr_h7rm0399, pwr_h7rm0455, pwr_h7rm0468))] pub supply_config: SupplyConfig, + + /// Per-peripheral kernel clock selection muxes + pub mux: super::mux::ClockMux, } impl Default for Config { @@ -241,19 +221,14 @@ impl Default for Config { #[cfg(stm32h7)] apb4_pre: APBPrescaler::DIV1, - per_clock_source: PerClockSource::HSI, - - #[cfg(stm32h5)] - adc_clock_source: AdcClockSource::HCLK1, - #[cfg(stm32h7)] - adc_clock_source: AdcClockSource::PER, - timer_prescaler: TimerPrescaler::DefaultX2, voltage_scale: VoltageScale::Scale0, ls: Default::default(), #[cfg(any(pwr_h7rm0399, pwr_h7rm0455, pwr_h7rm0468))] supply_config: SupplyConfig::Default, + + mux: Default::default(), } } } @@ -276,7 +251,7 @@ pub(crate) unsafe fn init(config: Config) { match config.supply_config { SupplyConfig::Default => { PWR.cr3().modify(|w| { - w.set_sdlevel(0b00); + w.set_sdlevel(SMPSSupplyVoltage::RESET); w.set_sdexthp(false); w.set_sden(true); w.set_ldoen(true); @@ -298,11 +273,11 @@ pub(crate) unsafe fn init(config: Config) { w.set_bypass(false); }); } - SupplyConfig::SMPSLDO(ref smps_supply_voltage) - | SupplyConfig::SMPSExternalLDO(ref smps_supply_voltage) - | SupplyConfig::SMPSExternalLDOBypass(ref smps_supply_voltage) => { + SupplyConfig::SMPSLDO(smps_supply_voltage) + | SupplyConfig::SMPSExternalLDO(smps_supply_voltage) + | SupplyConfig::SMPSExternalLDOBypass(smps_supply_voltage) => { PWR.cr3().modify(|w| { - w.set_sdlevel(smps_supply_voltage.to_u8()); + w.set_sdlevel(smps_supply_voltage); w.set_sdexthp(matches!( config.supply_config, SupplyConfig::SMPSExternalLDO(_) | SupplyConfig::SMPSExternalLDOBypass(_) @@ -426,7 +401,7 @@ pub(crate) unsafe fn init(config: Config) { }; // Configure HSI48. - let _hsi48 = config.hsi48.map(super::init_hsi48); + let hsi48 = config.hsi48.map(super::init_hsi48); // Configure CSI. RCC.cr().modify(|w| w.set_csion(config.csi)); @@ -516,31 +491,6 @@ pub(crate) unsafe fn init(config: Config) { #[cfg(stm32h7)] assert!(apb4 <= pclk_max); - let _per_ck = match config.per_clock_source { - Ckpersel::HSI => hsi, - Ckpersel::CSI => csi, - Ckpersel::HSE => hse, - _ => unreachable!(), - }; - - #[cfg(stm32h7)] - let adc = match config.adc_clock_source { - AdcClockSource::PLL2_P => pll2.p, - AdcClockSource::PLL3_R => pll3.r, - AdcClockSource::PER => _per_ck, - _ => unreachable!(), - }; - #[cfg(stm32h5)] - let adc = match config.adc_clock_source { - AdcClockSource::HCLK1 => Some(hclk), - AdcClockSource::SYS => Some(sys), - AdcClockSource::PLL2_R => pll2.r, - AdcClockSource::HSE => hse, - AdcClockSource::HSI => hsi, - AdcClockSource::CSI => csi, - _ => unreachable!(), - }; - flash_setup(hclk, config.voltage_scale); let rtc = config.ls.init(); @@ -562,13 +512,6 @@ pub(crate) unsafe fn init(config: Config) { RCC.d3cfgr().modify(|w| { w.set_d3ppre(config.apb4_pre); }); - - RCC.d1ccipr().modify(|w| { - w.set_ckpersel(config.per_clock_source); - }); - RCC.d3ccipr().modify(|w| { - w.set_adcsel(config.adc_clock_source); - }); } #[cfg(stm32h5)] { @@ -582,11 +525,6 @@ pub(crate) unsafe fn init(config: Config) { w.set_ppre2(config.apb2_pre); w.set_ppre3(config.apb3_pre); }); - - RCC.ccipr5().modify(|w| { - w.set_ckpersel(config.per_clock_source); - w.set_adcdacsel(config.adc_clock_source) - }); } RCC.cfgr().modify(|w| w.set_timpre(config.timer_prescaler.into())); @@ -609,45 +547,35 @@ pub(crate) unsafe fn init(config: Config) { while !pac::SYSCFG.cccsr().read().ready() {} } - set_freqs(Clocks { - sys, - hclk1: hclk, - hclk2: hclk, - hclk3: hclk, - hclk4: hclk, - pclk1: apb1, - pclk2: apb2, - pclk3: apb3, + config.mux.init(); + + set_clocks!( + sys: Some(sys), + hclk1: Some(hclk), + hclk2: Some(hclk), + hclk3: Some(hclk), + hclk4: Some(hclk), + pclk1: Some(apb1), + pclk2: Some(apb2), + pclk3: Some(apb3), #[cfg(stm32h7)] - pclk4: apb4, - #[cfg(stm32h5)] - pclk4: Hertz(1), - pclk1_tim: apb1_tim, - pclk2_tim: apb2_tim, - adc, - rtc, + pclk4: Some(apb4), + pclk1_tim: Some(apb1_tim), + pclk2_tim: Some(apb2_tim), + rtc: rtc, - #[cfg(any(stm32h5, stm32h7))] - hsi: None, - #[cfg(stm32h5)] - hsi48: None, - #[cfg(stm32h5)] - lsi: None, - #[cfg(any(stm32h5, stm32h7))] - csi: None, + hsi: hsi, + hsi48: hsi48, + csi: csi, + csi_div_122: csi.map(|c| c / 122u32), + hse: hse, - #[cfg(any(stm32h5, stm32h7))] lse: None, - #[cfg(any(stm32h5, stm32h7))] - hse: None, + lsi: None, - #[cfg(any(stm32h5, stm32h7))] pll1_q: pll1.q, - #[cfg(any(stm32h5, stm32h7))] pll2_p: pll2.p, - #[cfg(any(stm32h5, stm32h7))] pll2_q: pll2.q, - #[cfg(any(stm32h5, stm32h7))] pll2_r: pll2.r, #[cfg(any(rcc_h5, stm32h7))] pll3_p: pll3.p, @@ -665,12 +593,8 @@ pub(crate) unsafe fn init(config: Config) { #[cfg(stm32h5)] audioclk: None, - #[cfg(any(stm32h5, stm32h7))] - per: None, - - #[cfg(stm32h7)] - rcc_pclk_d3: None, - }); + i2s_ckin: None, + ); } struct PllInput { @@ -733,7 +657,7 @@ fn init_pll(num: usize, config: Option<Pll>, input: &PllInput) -> PllOutput { } else if wide_allowed && VCO_WIDE_RANGE.contains(&vco_clk) { Pllvcosel::WIDEVCO } else { - panic!("pll vco_clk out of range: {} mhz", vco_clk.0) + panic!("pll vco_clk out of range: {} hz", vco_clk.0) }; let p = config.divp.map(|div| { diff --git a/embassy-stm32/src/rcc/l.rs b/embassy-stm32/src/rcc/l.rs index 257fd83fe..9079ddd41 100644 --- a/embassy-stm32/src/rcc/l.rs +++ b/embassy-stm32/src/rcc/l.rs @@ -1,15 +1,10 @@ #[cfg(any(stm32l0, stm32l1))] pub use crate::pac::pwr::vals::Vos as VoltageScale; use crate::pac::rcc::regs::Cfgr; -#[cfg(any(stm32l4, stm32l5, stm32wb, stm32wl))] -pub use crate::pac::rcc::vals::Adcsel as AdcClockSource; -#[cfg(any(rcc_l0_v2, stm32l4, stm32l5, stm32wb))] -pub use crate::pac::rcc::vals::Clk48sel as Clk48Src; #[cfg(any(stm32wb, stm32wl))] pub use crate::pac::rcc::vals::Hsepre as HsePrescaler; -pub use crate::pac::rcc::vals::{Hpre as AHBPrescaler, Msirange as MSIRange, Ppre as APBPrescaler, Sw as ClockSrc}; +pub use crate::pac::rcc::vals::{Hpre as AHBPrescaler, Msirange as MSIRange, Ppre as APBPrescaler, Sw as Sysclk}; use crate::pac::{FLASH, RCC}; -use crate::rcc::{set_freqs, Clocks}; use crate::time::Hertz; /// HSI speed @@ -51,7 +46,7 @@ pub struct Config { pub pllsai2: Option<Pll>, // sysclk, buses. - pub mux: ClockSrc, + pub sys: Sysclk, pub ahb_pre: AHBPrescaler, pub apb1_pre: APBPrescaler, pub apb2_pre: APBPrescaler, @@ -60,18 +55,14 @@ pub struct Config { #[cfg(any(stm32wl, stm32wb))] pub shared_ahb_pre: AHBPrescaler, - // muxes - #[cfg(any(rcc_l0_v2, stm32l4, stm32l5, stm32wb))] - pub clk48_src: Clk48Src, - // low speed LSI/LSE/RTC pub ls: super::LsConfig, - #[cfg(any(stm32l4, stm32l5, stm32wb, stm32wl))] - pub adc_clock_source: AdcClockSource, - #[cfg(any(stm32l0, stm32l1))] pub voltage_scale: VoltageScale, + + /// Per-peripheral kernel clock selection muxes + pub mux: super::mux::ClockMux, } impl Default for Config { @@ -81,7 +72,7 @@ impl Default for Config { hse: None, hsi: false, msi: Some(MSIRange::RANGE4M), - mux: ClockSrc::MSI, + sys: Sysclk::MSI, ahb_pre: AHBPrescaler::DIV1, apb1_pre: APBPrescaler::DIV1, apb2_pre: APBPrescaler::DIV1, @@ -96,13 +87,10 @@ impl Default for Config { pllsai2: None, #[cfg(crs)] hsi48: Some(Default::default()), - #[cfg(any(rcc_l0_v2, stm32l4, stm32l5, stm32wb))] - clk48_src: Clk48Src::HSI48, ls: Default::default(), - #[cfg(any(stm32l4, stm32l5, stm32wb, stm32wl))] - adc_clock_source: AdcClockSource::SYS, #[cfg(any(stm32l0, stm32l1))] voltage_scale: VoltageScale::RANGE1, + mux: Default::default(), } } } @@ -114,12 +102,11 @@ pub const WPAN_DEFAULT: Config = Config { mode: HseMode::Oscillator, prescaler: HsePrescaler::DIV1, }), - mux: ClockSrc::PLL1_R, + sys: Sysclk::PLL1_R, #[cfg(crs)] hsi48: Some(super::Hsi48Config { sync_from_usb: false }), msi: None, hsi: false, - clk48_src: Clk48Src::PLL1_Q, ls: super::LsConfig::default_lse(), @@ -138,7 +125,8 @@ pub const WPAN_DEFAULT: Config = Config { shared_ahb_pre: AHBPrescaler::DIV1, apb1_pre: APBPrescaler::DIV1, apb2_pre: APBPrescaler::DIV1, - adc_clock_source: AdcClockSource::SYS, + + mux: super::mux::ClockMux::default(), }; fn msi_enable(range: MSIRange) { @@ -162,11 +150,11 @@ pub(crate) unsafe fn init(config: Config) { // Turn on MSI and configure it to 4MHz. msi_enable(MSIRange::RANGE4M) } - if RCC.cfgr().read().sws() != ClockSrc::MSI { + if RCC.cfgr().read().sws() != Sysclk::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() != ClockSrc::MSI {} + while RCC.cfgr().read().sws() != Sysclk::MSI {} } // Set voltage scale @@ -216,12 +204,9 @@ pub(crate) unsafe fn init(config: Config) { }); #[cfg(crs)] - let _hsi48 = config.hsi48.map(|config| { - // - super::init_hsi48(config) - }); + let hsi48 = config.hsi48.map(|config| super::init_hsi48(config)); #[cfg(not(crs))] - let _hsi48: Option<Hertz> = None; + let hsi48: Option<Hertz> = None; let _plls = [ &config.pll, @@ -262,28 +247,13 @@ pub(crate) unsafe fn init(config: Config) { #[cfg(any(stm32l4, stm32l5, stm32wb))] let pllsai1 = init_pll(PllInstance::Pllsai1, config.pllsai1, &pll_input); #[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))] - let _pllsai2 = init_pll(PllInstance::Pllsai2, config.pllsai2, &pll_input); + let pllsai2 = init_pll(PllInstance::Pllsai2, config.pllsai2, &pll_input); - let sys_clk = match config.mux { - ClockSrc::HSE => hse.unwrap(), - ClockSrc::HSI => hsi.unwrap(), - ClockSrc::MSI => msi.unwrap(), - ClockSrc::PLL1_R => pll.r.unwrap(), - }; - - #[cfg(any(rcc_l0_v2, stm32l4, stm32l5, stm32wb))] - RCC.ccipr().modify(|w| w.set_clk48sel(config.clk48_src)); - #[cfg(any(rcc_l0_v2))] - let _clk48 = match config.clk48_src { - Clk48Src::HSI48 => _hsi48, - Clk48Src::PLL1_VCO_DIV_2 => pll.clk48, - }; - #[cfg(any(stm32l4, stm32l5, stm32wb))] - let _clk48 = match config.clk48_src { - Clk48Src::HSI48 => _hsi48, - Clk48Src::MSI => msi, - Clk48Src::PLLSAI1_Q => pllsai1.q, - Clk48Src::PLL1_Q => pll.q, + let sys_clk = match config.sys { + Sysclk::HSE => hse.unwrap(), + Sysclk::HSI => hsi.unwrap(), + Sysclk::MSI => msi.unwrap(), + Sysclk::PLL1_R => pll.r.unwrap(), }; #[cfg(rcc_l4plus)] @@ -354,15 +324,12 @@ pub(crate) unsafe fn init(config: Config) { while FLASH.acr().read().latency() != latency {} RCC.cfgr().modify(|w| { - w.set_sw(config.mux); + w.set_sw(config.sys); w.set_hpre(config.ahb_pre); w.set_ppre1(config.apb1_pre); w.set_ppre2(config.apb2_pre); }); - while RCC.cfgr().read().sws() != config.mux {} - - #[cfg(any(stm32l4, stm32l5, stm32wb, stm32wl))] - RCC.ccipr().modify(|w| w.set_adcsel(config.adc_clock_source)); + while RCC.cfgr().read().sws() != config.sys {} #[cfg(any(stm32wl, stm32wb))] { @@ -376,37 +343,64 @@ pub(crate) unsafe fn init(config: Config) { while !RCC.extcfgr().read().c2hpref() {} } - set_freqs(Clocks { - sys: sys_clk, - hclk1, + config.mux.init(); + + set_clocks!( + sys: Some(sys_clk), + hclk1: Some(hclk1), #[cfg(any(stm32l4, stm32l5, stm32wb, stm32wl))] - hclk2, + hclk2: Some(hclk2), #[cfg(any(stm32l4, stm32l5, stm32wb, stm32wl))] - hclk3, - pclk1, - pclk2, - pclk1_tim, - pclk2_tim, + hclk3: Some(hclk3), + pclk1: Some(pclk1), + pclk2: Some(pclk2), + pclk1_tim: Some(pclk1_tim), + pclk2_tim: Some(pclk2_tim), #[cfg(stm32wl)] - pclk3: hclk3, - #[cfg(rcc_l4)] - hsi: None, - #[cfg(rcc_l4)] - lse: None, - #[cfg(rcc_l4)] - pllsai1_p: None, - #[cfg(rcc_l4)] + pclk3: Some(hclk3), + hsi: hsi, + hse: hse, + msi: msi, + hsi48: hsi48, + + #[cfg(any(stm32l0, stm32l1))] + pll1_vco_div_2: pll.vco.map(|c| c/2u32), + + #[cfg(not(any(stm32l0, stm32l1)))] + pll1_p: pll.p, + #[cfg(not(any(stm32l0, stm32l1)))] + pll1_q: pll.q, + pll1_r: pll.r, + + #[cfg(any(stm32l4, stm32l5, stm32wb))] + pllsai1_p: pllsai1.p, + #[cfg(any(stm32l4, stm32l5, stm32wb))] + pllsai1_q: pllsai1.q, + #[cfg(any(stm32l4, stm32l5, stm32wb))] + pllsai1_r: pllsai1.r, + + #[cfg(not(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5)))] pllsai2_p: None, - #[cfg(rcc_l4)] - pll1_p: None, - #[cfg(rcc_l4)] - pll1_q: None, - #[cfg(rcc_l4)] + #[cfg(not(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5)))] + pllsai2_q: None, + #[cfg(not(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5)))] + pllsai2_r: None, + + #[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))] + pllsai2_p: pllsai2.p, + #[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))] + pllsai2_q: pllsai2.q, + #[cfg(any(stm32l47x, stm32l48x, stm32l49x, stm32l4ax, rcc_l4plus, stm32l5))] + pllsai2_r: pllsai2.r, + + rtc: rtc, + + // TODO sai1_extclk: None, - #[cfg(rcc_l4)] sai2_extclk: None, - rtc, - }); + lsi: None, + lse: None, + ); } #[cfg(any(stm32l0, stm32l1))] @@ -491,7 +485,7 @@ mod pll { #[derive(Default)] pub(super) struct PllOutput { pub r: Option<Hertz>, - pub clk48: Option<Hertz>, + pub vco: Option<Hertz>, } pub(super) fn init_pll(instance: PllInstance, config: Option<Pll>, input: &PllInput) -> PllOutput { @@ -508,7 +502,6 @@ mod pll { let vco_freq = pll_src * pll.mul; let r = vco_freq / pll.div; - let clk48 = (vco_freq == Hertz(96_000_000)).then_some(Hertz(48_000_000)); assert!(r <= Hertz(32_000_000)); @@ -521,7 +514,10 @@ mod pll { // Enable PLL pll_enable(instance, true); - PllOutput { r: Some(r), clk48 } + PllOutput { + r: Some(r), + vco: Some(vco_freq), + } } } diff --git a/embassy-stm32/src/rcc/mco.rs b/embassy-stm32/src/rcc/mco.rs index eaaf8071c..654943bc1 100644 --- a/embassy-stm32/src/rcc/mco.rs +++ b/embassy-stm32/src/rcc/mco.rs @@ -4,7 +4,7 @@ use embassy_hal_internal::into_ref; use crate::gpio::sealed::AFType; use crate::gpio::Speed; -#[cfg(not(stm32f1))] +#[cfg(not(any(stm32f1, rcc_f0v1, rcc_f3v1, rcc_f37)))] pub use crate::pac::rcc::vals::Mcopre as McoPrescaler; #[cfg(not(any(rcc_f2, rcc_f410, rcc_f4, rcc_f7, rcc_h50, rcc_h5, rcc_h7ab, rcc_h7rm0433, rcc_h7)))] pub use crate::pac::rcc::vals::Mcosel as McoSource; @@ -13,10 +13,16 @@ pub use crate::pac::rcc::vals::{Mco1sel as Mco1Source, Mco2sel as Mco2Source}; use crate::pac::RCC; use crate::{peripherals, Peripheral}; +#[cfg(any(stm32f1, rcc_f0v1, rcc_f3v1, rcc_f37))] +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub enum McoPrescaler { + DIV1, +} + pub(crate) mod sealed { pub trait McoInstance { type Source; - unsafe fn apply_clock_settings(source: Self::Source, #[cfg(not(stm32f1))] prescaler: super::McoPrescaler); + unsafe fn apply_clock_settings(source: Self::Source, prescaler: super::McoPrescaler); } } @@ -29,7 +35,7 @@ macro_rules! impl_peri { impl sealed::McoInstance for peripherals::$peri { type Source = $source; - unsafe fn apply_clock_settings(source: Self::Source, #[cfg(not(stm32f1))] prescaler: McoPrescaler) { + unsafe fn apply_clock_settings(source: Self::Source, _prescaler: McoPrescaler) { #[cfg(not(any(stm32u5, stm32wba)))] let r = RCC.cfgr(); #[cfg(any(stm32u5, stm32wba))] @@ -37,8 +43,8 @@ macro_rules! impl_peri { r.modify(|w| { w.$set_source(source); - #[cfg(not(stm32f1))] - w.$set_prescaler(prescaler); + #[cfg(not(any(stm32f1, rcc_f0v1, rcc_f3v1, rcc_f37)))] + w.$set_prescaler(_prescaler); }); } } @@ -68,16 +74,12 @@ impl<'d, T: McoInstance> Mco<'d, T> { _peri: impl Peripheral<P = T> + 'd, pin: impl Peripheral<P = impl McoPin<T>> + 'd, source: T::Source, - #[cfg(not(stm32f1))] prescaler: McoPrescaler, + prescaler: McoPrescaler, ) -> Self { into_ref!(pin); critical_section::with(|_| unsafe { - T::apply_clock_settings( - source, - #[cfg(not(stm32f1))] - prescaler, - ); + T::apply_clock_settings(source, prescaler); pin.set_as_af(pin.af_num(), AFType::OutputPushPull); pin.set_speed(Speed::VeryHigh); }); diff --git a/embassy-stm32/src/rcc/mod.rs b/embassy-stm32/src/rcc/mod.rs index 240ffc6d2..910ebe205 100644 --- a/embassy-stm32/src/rcc/mod.rs +++ b/embassy-stm32/src/rcc/mod.rs @@ -5,11 +5,12 @@ use core::mem::MaybeUninit; -use crate::time::Hertz; - mod bd; -mod mco; pub use bd::*; + +#[cfg(any(mco, mco1, mco2))] +mod mco; +#[cfg(any(mco, mco1, mco2))] pub use mco::*; #[cfg(crs)] @@ -17,177 +18,20 @@ mod hsi48; #[cfg(crs)] pub use hsi48::*; -#[cfg_attr(rcc_f0, path = "f0.rs")] -#[cfg_attr(any(stm32f1), path = "f1.rs")] -#[cfg_attr(any(stm32f3), path = "f3.rs")] -#[cfg_attr(any(stm32f2, stm32f4, stm32f7), path = "f.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(stm32f0, stm32f1, stm32f3), path = "f013.rs")] +#[cfg_attr(any(stm32f2, stm32f4, stm32f7), path = "f247.rs")] +#[cfg_attr(stm32c0, path = "c0.rs")] +#[cfg_attr(stm32g0, path = "g0.rs")] +#[cfg_attr(stm32g4, path = "g4.rs")] #[cfg_attr(any(stm32h5, stm32h7), path = "h.rs")] #[cfg_attr(any(stm32l0, stm32l1, stm32l4, stm32l5, stm32wb, stm32wl), path = "l.rs")] -#[cfg_attr(rcc_u5, path = "u5.rs")] -#[cfg_attr(rcc_wba, path = "wba.rs")] +#[cfg_attr(stm32u5, path = "u5.rs")] +#[cfg_attr(stm32wba, path = "wba.rs")] mod _version; pub use _version::*; -// Model Clock Configuration -// -// pub struct Clocks { -// hse: Option<Hertz>, -// hsi: bool, -// lse: Option<Hertz>, -// lsi: bool, -// rtc: RtcSource, -// } - -#[derive(Clone, Copy, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub struct Clocks { - pub sys: Hertz, - - // APB - pub pclk1: Hertz, - pub pclk1_tim: Hertz, - #[cfg(not(any(rcc_c0, rcc_g0)))] - pub pclk2: Hertz, - #[cfg(not(any(rcc_c0, rcc_g0)))] - pub pclk2_tim: Hertz, - #[cfg(any(rcc_wl5, rcc_wle, rcc_h5, rcc_h50, rcc_h7, rcc_h7rm0433, rcc_h7ab, rcc_u5))] - pub pclk3: Hertz, - #[cfg(any(rcc_h7, rcc_h7rm0433, rcc_h7ab, stm32h5))] - pub pclk4: Hertz, - #[cfg(any(rcc_wba))] - pub pclk7: Hertz, - - // AHB - pub hclk1: Hertz, - #[cfg(any( - rcc_l4, - rcc_l4plus, - rcc_l5, - rcc_f2, - rcc_f4, - rcc_f410, - rcc_f7, - rcc_h5, - rcc_h50, - rcc_h7, - rcc_h7rm0433, - rcc_h7ab, - rcc_g4, - rcc_u5, - rcc_wb, - rcc_wba, - rcc_wl5, - rcc_wle - ))] - pub hclk2: Hertz, - #[cfg(any( - rcc_l4, - rcc_l4plus, - rcc_l5, - rcc_f2, - rcc_f4, - rcc_f410, - rcc_f7, - rcc_h5, - rcc_h50, - rcc_h7, - rcc_h7rm0433, - rcc_h7ab, - rcc_u5, - rcc_g4, - rcc_wb, - rcc_wl5, - rcc_wle - ))] - pub hclk3: Hertz, - #[cfg(any(rcc_h5, rcc_h50, rcc_h7, rcc_h7rm0433, rcc_h7ab, rcc_wba))] - pub hclk4: Hertz, - - #[cfg(all(rcc_f4, not(stm32f410)))] - pub plli2s1_q: Option<Hertz>, - #[cfg(all(rcc_f4, not(stm32f410)))] - pub plli2s1_r: Option<Hertz>, - - #[cfg(rcc_l4)] - pub pllsai1_p: Option<Hertz>, - #[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479))] - pub pllsai1_q: Option<Hertz>, - #[cfg(any(stm32f427, stm32f429, stm32f437, stm32f439, stm32f446, stm32f469, stm32f479))] - pub pllsai1_r: Option<Hertz>, - #[cfg(rcc_l4)] - pub pllsai2_p: Option<Hertz>, - - #[cfg(any(stm32g0, stm32g4, rcc_l4))] - pub pll1_p: Option<Hertz>, - #[cfg(any(stm32h5, stm32h7, stm32f2, stm32f4, stm32f7, rcc_l4, stm32g0, stm32g4))] - pub pll1_q: Option<Hertz>, - #[cfg(any(stm32h5, stm32h7))] - pub pll2_p: Option<Hertz>, - #[cfg(any(stm32h5, stm32h7))] - pub pll2_q: Option<Hertz>, - #[cfg(any(stm32h5, stm32h7))] - pub pll2_r: Option<Hertz>, - #[cfg(any(stm32h5, stm32h7))] - pub pll3_p: Option<Hertz>, - #[cfg(any(stm32h5, stm32h7))] - pub pll3_q: Option<Hertz>, - #[cfg(any(stm32h5, stm32h7))] - pub pll3_r: Option<Hertz>, - - #[cfg(any( - rcc_f1, - rcc_f100, - rcc_f1cl, - rcc_h5, - rcc_h50, - rcc_h7, - rcc_h7rm0433, - rcc_h7ab, - rcc_f3, - rcc_g4 - ))] - pub adc: Option<Hertz>, - - #[cfg(any(rcc_f3, rcc_g4))] - pub adc34: Option<Hertz>, - - #[cfg(stm32f334)] - pub hrtim: Option<Hertz>, - - pub rtc: Option<Hertz>, - - #[cfg(any(stm32h5, stm32h7, rcc_l4, rcc_c0, stm32g0))] - pub hsi: Option<Hertz>, - #[cfg(any(stm32h5, stm32g0))] - pub hsi48: Option<Hertz>, - #[cfg(stm32g0)] - pub hsi_div_8: Option<Hertz>, - #[cfg(any(stm32g0, stm32h5))] - pub lsi: Option<Hertz>, - #[cfg(any(stm32h5, stm32h7))] - pub csi: Option<Hertz>, - - #[cfg(any(stm32h5, stm32h7, rcc_l4, rcc_c0, stm32g0))] - pub lse: Option<Hertz>, - #[cfg(any(stm32h5, stm32h7, stm32g0, stm32g4))] - pub hse: Option<Hertz>, - - #[cfg(stm32h5)] - pub audioclk: Option<Hertz>, - #[cfg(any(stm32h5, stm32h7))] - pub per: Option<Hertz>, - - #[cfg(stm32h7)] - pub rcc_pclk_d3: Option<Hertz>, - #[cfg(rcc_l4)] - pub sai1_extclk: Option<Hertz>, - #[cfg(rcc_l4)] - pub sai2_extclk: Option<Hertz>, -} +pub use crate::_generated::{mux, Clocks}; #[cfg(feature = "low-power")] /// Must be written within a critical section diff --git a/embassy-stm32/src/rcc/u5.rs b/embassy-stm32/src/rcc/u5.rs index dff08dc9b..9533e16c4 100644 --- a/embassy-stm32/src/rcc/u5.rs +++ b/embassy-stm32/src/rcc/u5.rs @@ -1,135 +1,83 @@ -pub use crate::pac::rcc::vals::{Hpre as AHBPrescaler, Msirange, Plldiv, Pllm, Plln, Ppre as APBPrescaler}; -use crate::pac::rcc::vals::{Msirgsel, Pllmboost, Pllrge, Pllsrc, Sw}; +pub use crate::pac::pwr::vals::Vos as VoltageScale; +pub use crate::pac::rcc::vals::{ + Hpre as AHBPrescaler, Msirange, Msirange as MSIRange, Plldiv as PllDiv, Pllm as PllPreDiv, Plln as PllMul, + Pllsrc as PllSource, Ppre as APBPrescaler, Sw as Sysclk, +}; +use crate::pac::rcc::vals::{Hseext, Msirgsel, Pllmboost, Pllrge}; use crate::pac::{FLASH, PWR, RCC}; -use crate::rcc::{set_freqs, Clocks}; use crate::time::Hertz; /// HSI speed pub const HSI_FREQ: Hertz = Hertz(16_000_000); -pub use crate::pac::pwr::vals::Vos as VoltageScale; - -#[derive(Copy, Clone)] -#[allow(non_camel_case_types)] -pub enum ClockSrc { - /// Use an internal medium speed oscillator (MSIS) as the system clock. - MSI(Msirange), - /// Use the external high speed clock as the system clock. - /// - /// HSE clocks faster than 25 MHz require at least `VoltageScale::RANGE3`, and HSE clocks must - /// never exceed 50 MHz. - HSE(Hertz), - /// Use the 16 MHz internal high speed oscillator as the system clock. - HSI, - /// Use PLL1 as the system clock. - PLL1_R(PllConfig), +#[derive(Clone, Copy, Eq, PartialEq)] +pub enum HseMode { + /// crystal/ceramic oscillator (HSEBYP=0) + Oscillator, + /// external analog clock (low swing) (HSEBYP=1, HSEEXT=0) + Bypass, + /// external digital clock (full swing) (HSEBYP=1, HSEEXT=1) + BypassDigital, } -impl Default for ClockSrc { - fn default() -> Self { - // The default system clock source is MSIS @ 4 MHz, per RM0456 § 11.4.9 - ClockSrc::MSI(Msirange::RANGE_4MHZ) - } +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct Hse { + /// HSE frequency. + pub freq: Hertz, + /// HSE mode. + pub mode: HseMode, } #[derive(Clone, Copy)] -pub struct PllConfig { +pub struct Pll { /// The clock source for the PLL. pub source: PllSource, - /// The PLL prescaler. + /// The PLL pre-divider. /// /// The clock speed of the `source` divided by `m` must be between 4 and 16 MHz. - pub m: Pllm, + pub prediv: PllPreDiv, /// The PLL multiplier. /// /// The multiplied clock – `source` divided by `m` times `n` – must be between 128 and 544 /// MHz. The upper limit may be lower depending on the `Config { voltage_range }`. - pub n: Plln, + pub mul: PllMul, /// The divider for the P output. /// /// The P output is one of several options /// that can be used to feed the SAI/MDF/ADF Clock mux's. - pub p: Plldiv, + pub divp: Option<PllDiv>, /// The divider for the Q output. /// /// The Q ouput is one of severals options that can be used to feed the 48MHz clocks /// and the OCTOSPI clock. It may also be used on the MDF/ADF clock mux's. - pub q: Plldiv, + pub divq: Option<PllDiv>, /// The divider for the R output. /// /// When used to drive the system clock, `source` divided by `m` times `n` divided by `r` /// must not exceed 160 MHz. System clocks above 55 MHz require a non-default /// `Config { voltage_range }`. - pub r: Plldiv, -} - -impl PllConfig { - /// A configuration for HSI / 1 * 10 / 1 = 160 MHz - pub const fn hsi_160mhz() -> Self { - PllConfig { - source: PllSource::HSI, - m: Pllm::DIV1, - n: Plln::MUL10, - p: Plldiv::DIV3, - q: Plldiv::DIV2, - r: Plldiv::DIV1, - } - } - - /// A configuration for MSIS @ 48 MHz / 3 * 10 / 1 = 160 MHz - pub const fn msis_160mhz() -> Self { - PllConfig { - source: PllSource::MSIS(Msirange::RANGE_48MHZ), - m: Pllm::DIV3, - n: Plln::MUL10, - p: Plldiv::DIV3, - q: Plldiv::DIV2, - r: Plldiv::DIV1, - } - } -} - -#[derive(Clone, Copy)] -pub enum PllSource { - /// Use an internal medium speed oscillator as the PLL source. - MSIS(Msirange), - /// Use the external high speed clock as the system PLL source. - /// - /// HSE clocks faster than 25 MHz require at least `VoltageScale::RANGE3`, and HSE clocks must - /// never exceed 50 MHz. - HSE(Hertz), - /// Use the 16 MHz internal high speed oscillator as the PLL source. - HSI, -} - -impl Into<Pllsrc> for PllSource { - fn into(self) -> Pllsrc { - match self { - PllSource::MSIS(..) => Pllsrc::MSIS, - PllSource::HSE(..) => Pllsrc::HSE, - PllSource::HSI => Pllsrc::HSI, - } - } -} - -impl Into<Sw> for ClockSrc { - fn into(self) -> Sw { - match self { - ClockSrc::MSI(..) => Sw::MSIS, - ClockSrc::HSE(..) => Sw::HSE, - ClockSrc::HSI => Sw::HSI, - ClockSrc::PLL1_R(..) => Sw::PLL1_R, - } - } + pub divr: Option<PllDiv>, } pub struct Config { - pub mux: ClockSrc, + // base clock sources + pub msi: Option<MSIRange>, + pub hsi: bool, + pub hse: Option<Hse>, + pub hsi48: Option<super::Hsi48Config>, + + // pll + pub pll1: Option<Pll>, + pub pll2: Option<Pll>, + pub pll3: Option<Pll>, + + // sysclk, buses. + pub sys: Sysclk, pub ahb_pre: AHBPrescaler, pub apb1_pre: APBPrescaler, pub apb2_pre: APBPrescaler, pub apb3_pre: APBPrescaler, - pub hsi48: Option<super::Hsi48Config>, + /// The voltage range influences the maximum clock frequencies for different parts of the /// device. In particular, system clocks exceeding 110 MHz require `RANGE1`, and system clocks /// exceeding 55 MHz require at least `RANGE2`. @@ -137,37 +85,41 @@ pub struct Config { /// See RM0456 § 10.5.4 for a general overview and § 11.4.10 for clock source frequency limits. pub voltage_range: VoltageScale, pub ls: super::LsConfig, + + /// Per-peripheral kernel clock selection muxes + pub mux: super::mux::ClockMux, } -impl Config { - unsafe fn init_hsi(&self) -> Hertz { - RCC.cr().write(|w| w.set_hsion(true)); - while !RCC.cr().read().hsirdy() {} - - HSI_FREQ - } - - unsafe fn init_hse(&self, frequency: Hertz) -> Hertz { - // Check frequency limits per RM456 § 11.4.10 - match self.voltage_range { - VoltageScale::RANGE1 | VoltageScale::RANGE2 | VoltageScale::RANGE3 => { - assert!(frequency.0 <= 50_000_000); - } - VoltageScale::RANGE4 => { - assert!(frequency.0 <= 25_000_000); - } +impl Default for Config { + fn default() -> Self { + Self { + msi: Some(Msirange::RANGE_4MHZ), + hse: None, + hsi: false, + hsi48: Some(Default::default()), + pll1: None, + pll2: None, + pll3: None, + sys: Sysclk::MSIS, + ahb_pre: AHBPrescaler::DIV1, + apb1_pre: APBPrescaler::DIV1, + apb2_pre: APBPrescaler::DIV1, + apb3_pre: APBPrescaler::DIV1, + voltage_range: VoltageScale::RANGE1, + ls: Default::default(), + mux: Default::default(), } - - // Enable HSE, and wait for it to stabilize - RCC.cr().write(|w| w.set_hseon(true)); - while !RCC.cr().read().hserdy() {} - - frequency } +} - unsafe fn init_msis(&self, range: Msirange) -> Hertz { +pub(crate) unsafe fn init(config: Config) { + // Set the requested power mode + PWR.vosr().modify(|w| w.set_vos(config.voltage_range)); + while !PWR.vosr().read().vosrdy() {} + + let msi = config.msi.map(|range| { // Check MSI output per RM0456 § 11.4.10 - match self.voltage_range { + match config.voltage_range { VoltageScale::RANGE4 => { assert!(msirange_to_hertz(range).0 <= 24_000_000); } @@ -192,223 +144,98 @@ impl Config { }); while !RCC.cr().read().msisrdy() {} msirange_to_hertz(range) - } -} - -impl Default for Config { - fn default() -> Self { - Self { - mux: ClockSrc::default(), - ahb_pre: AHBPrescaler::DIV1, - apb1_pre: APBPrescaler::DIV1, - apb2_pre: APBPrescaler::DIV1, - apb3_pre: APBPrescaler::DIV1, - hsi48: Some(Default::default()), - voltage_range: VoltageScale::RANGE3, - ls: Default::default(), - } - } -} - -pub(crate) unsafe fn init(config: Config) { - // Ensure PWR peripheral clock is enabled - RCC.ahb3enr().modify(|w| { - w.set_pwren(true); }); - RCC.ahb3enr().read(); // synchronize - // Set the requested power mode - PWR.vosr().modify(|w| { - w.set_vos(config.voltage_range); + let hsi = config.hsi.then(|| { + RCC.cr().write(|w| w.set_hsion(true)); + while !RCC.cr().read().hsirdy() {} + + HSI_FREQ }); - while !PWR.vosr().read().vosrdy() {} - let sys_clk = match config.mux { - ClockSrc::MSI(range) => config.init_msis(range), - ClockSrc::HSE(freq) => config.init_hse(freq), - ClockSrc::HSI => config.init_hsi(), - ClockSrc::PLL1_R(pll) => { - // Configure the PLL source - let source_clk = match pll.source { - PllSource::MSIS(range) => config.init_msis(range), - PllSource::HSE(hertz) => config.init_hse(hertz), - PllSource::HSI => config.init_hsi(), - }; - - // Calculate the reference clock, which is the source divided by m - let reference_clk = source_clk / pll.m; - - // Check limits per RM0456 § 11.4.6 - assert!(Hertz::mhz(4) <= reference_clk && reference_clk <= Hertz::mhz(16)); - - // Calculate the PLL1 VCO clock and PLL1 R output clock - let pll1_clk = reference_clk * pll.n; - let pll1r_clk = pll1_clk / pll.r; - - // Check system clock per RM0456 § 11.4.9 - assert!(pll1r_clk <= Hertz::mhz(160)); - - // Check PLL clocks per RM0456 § 11.4.10 - match config.voltage_range { - VoltageScale::RANGE1 => { - assert!(pll1_clk >= Hertz::mhz(128) && pll1_clk <= Hertz::mhz(544)); - assert!(pll1r_clk <= Hertz::mhz(208)); - } - VoltageScale::RANGE2 => { - assert!(pll1_clk >= Hertz::mhz(128) && pll1_clk <= Hertz::mhz(544)); - assert!(pll1r_clk <= Hertz::mhz(110)); - } - VoltageScale::RANGE3 => { - assert!(pll1_clk >= Hertz::mhz(128) && pll1_clk <= Hertz::mhz(330)); - assert!(pll1r_clk <= Hertz::mhz(55)); - } - VoltageScale::RANGE4 => { - panic!("PLL is unavailable in voltage range 4"); - } + let hse = config.hse.map(|hse| { + // Check frequency limits per RM456 § 11.4.10 + match config.voltage_range { + VoltageScale::RANGE1 | VoltageScale::RANGE2 | VoltageScale::RANGE3 => { + assert!(hse.freq.0 <= 50_000_000); } - - // § 10.5.4: if we're targeting >= 55 MHz, we must configure PLL1MBOOST to a prescaler - // value that results in an output between 4 and 16 MHz for the PWR EPOD boost - let mboost = if pll1r_clk >= Hertz::mhz(55) { - // source_clk can be up to 50 MHz, so there's just a few cases: - if source_clk > Hertz::mhz(32) { - // Divide by 4, giving EPOD 8-12.5 MHz - Pllmboost::DIV4 - } else if source_clk > Hertz::mhz(16) { - // Divide by 2, giving EPOD 8-16 MHz - Pllmboost::DIV2 - } else { - // Bypass, giving EPOD 4-16 MHz - Pllmboost::DIV1 - } - } else { - // Nothing to do - Pllmboost::DIV1 - }; - - // Disable the PLL, and wait for it to disable - RCC.cr().modify(|w| w.set_pllon(0, false)); - while RCC.cr().read().pllrdy(0) {} - - // Configure the PLL - RCC.pll1cfgr().write(|w| { - // Configure PLL1 source and prescaler - w.set_pllsrc(pll.source.into()); - w.set_pllm(pll.m); - - // Configure PLL1 input frequncy range - let input_range = if reference_clk <= Hertz::mhz(8) { - Pllrge::FREQ_4TO8MHZ - } else { - Pllrge::FREQ_8TO16MHZ - }; - w.set_pllrge(input_range); - - // Set the prescaler for PWR EPOD - w.set_pllmboost(mboost); - - // Enable PLL1_R output - w.set_pllren(true); - }); - - // Configure the PLL divisors - RCC.pll1divr().modify(|w| { - // Set the VCO multiplier - w.set_plln(pll.n); - w.set_pllp(pll.p); - w.set_pllq(pll.q); - // Set the R output divisor - w.set_pllr(pll.r); - }); - - // Do we need the EPOD booster to reach the target clock speed per § 10.5.4? - if pll1r_clk >= Hertz::mhz(55) { - // Enable the booster - PWR.vosr().modify(|w| { - w.set_boosten(true); - }); - while !PWR.vosr().read().boostrdy() {} + VoltageScale::RANGE4 => { + assert!(hse.freq.0 <= 25_000_000); } - - // Enable the PLL - RCC.cr().modify(|w| w.set_pllon(0, true)); - while !RCC.cr().read().pllrdy(0) {} - - pll1r_clk } + + // Enable HSE, and wait for it to stabilize + RCC.cr().write(|w| { + w.set_hseon(true); + w.set_hsebyp(hse.mode != HseMode::Oscillator); + w.set_hseext(match hse.mode { + HseMode::Oscillator | HseMode::Bypass => Hseext::ANALOG, + HseMode::BypassDigital => Hseext::DIGITAL, + }); + }); + while !RCC.cr().read().hserdy() {} + + hse.freq + }); + + let hsi48 = config.hsi48.map(super::init_hsi48); + + let pll_input = PllInput { hse, hsi, msi }; + let pll1 = init_pll(PllInstance::Pll1, config.pll1, &pll_input, config.voltage_range); + let pll2 = init_pll(PllInstance::Pll2, config.pll2, &pll_input, config.voltage_range); + let pll3 = init_pll(PllInstance::Pll3, config.pll3, &pll_input, config.voltage_range); + + let sys_clk = match config.sys { + Sysclk::HSE => hse.unwrap(), + Sysclk::HSI => hsi.unwrap(), + Sysclk::MSIS => msi.unwrap(), + Sysclk::PLL1_R => pll1.r.unwrap(), }; - let _hsi48 = config.hsi48.map(super::init_hsi48); + // Do we need the EPOD booster to reach the target clock speed per § 10.5.4? + if sys_clk >= Hertz::mhz(55) { + // Enable the booster + PWR.vosr().modify(|w| w.set_boosten(true)); + while !PWR.vosr().read().boostrdy() {} + } // The clock source is ready // Calculate and set the flash wait states let wait_states = match config.voltage_range { // VOS 1 range VCORE 1.26V - 1.40V - VoltageScale::RANGE1 => { - if sys_clk.0 < 32_000_000 { - 0 - } else if sys_clk.0 < 64_000_000 { - 1 - } else if sys_clk.0 < 96_000_000 { - 2 - } else if sys_clk.0 < 128_000_000 { - 3 - } else { - 4 - } - } + VoltageScale::RANGE1 => match sys_clk.0 { + ..=32_000_000 => 0, + ..=64_000_000 => 1, + ..=96_000_000 => 2, + ..=128_000_000 => 3, + _ => 4, + }, // VOS 2 range VCORE 1.15V - 1.26V - VoltageScale::RANGE2 => { - if sys_clk.0 < 30_000_000 { - 0 - } else if sys_clk.0 < 60_000_000 { - 1 - } else if sys_clk.0 < 90_000_000 { - 2 - } else { - 3 - } - } + VoltageScale::RANGE2 => match sys_clk.0 { + ..=30_000_000 => 0, + ..=60_000_000 => 1, + ..=90_000_000 => 2, + _ => 3, + }, // VOS 3 range VCORE 1.05V - 1.15V - VoltageScale::RANGE3 => { - if sys_clk.0 < 24_000_000 { - 0 - } else if sys_clk.0 < 48_000_000 { - 1 - } else { - 2 - } - } + VoltageScale::RANGE3 => match sys_clk.0 { + ..=24_000_000 => 0, + ..=48_000_000 => 1, + _ => 2, + }, // VOS 4 range VCORE 0.95V - 1.05V - VoltageScale::RANGE4 => { - if sys_clk.0 < 12_000_000 { - 0 - } else { - 1 - } - } + VoltageScale::RANGE4 => match sys_clk.0 { + ..=12_000_000 => 0, + _ => 1, + }, }; FLASH.acr().modify(|w| { w.set_latency(wait_states); }); // Switch the system clock source - RCC.cfgr1().modify(|w| { - w.set_sw(config.mux.into()); - }); - - // RM0456 § 11.4.9 specifies maximum bus frequencies per voltage range, but the maximum bus - // frequency for each voltage range exactly matches the maximum permitted PLL output frequency. - // Given that: - // - // 1. Any bus frequency can never exceed the system clock frequency; - // 2. We checked the PLL output frequency if we're using it as a system clock; - // 3. The maximum HSE frequencies at each voltage range are lower than the bus limits, and - // we checked the HSE frequency if configured as a system clock; and - // 4. The maximum frequencies from the other clock sources are lower than the lowest bus - // frequency limit - // - // ...then we do not need to perform additional bus-related frequency checks. + RCC.cfgr1().modify(|w| w.set_sw(config.sys)); + while RCC.cfgr1().read().sws() != config.sys {} // Configure the bus prescalers RCC.cfgr2().modify(|w| { @@ -420,46 +247,57 @@ pub(crate) unsafe fn init(config: Config) { w.set_ppre3(config.apb3_pre); }); - let ahb_freq = sys_clk / config.ahb_pre; + let hclk = sys_clk / config.ahb_pre; - let (apb1_freq, apb1_tim_freq) = match config.apb1_pre { - APBPrescaler::DIV1 => (ahb_freq, ahb_freq), - pre => { - let freq = ahb_freq / pre; - (freq, freq * 2u32) - } + let hclk_max = match config.voltage_range { + VoltageScale::RANGE1 => Hertz::mhz(160), + VoltageScale::RANGE2 => Hertz::mhz(110), + VoltageScale::RANGE3 => Hertz::mhz(55), + VoltageScale::RANGE4 => Hertz::mhz(25), }; + assert!(hclk <= hclk_max); - let (apb2_freq, apb2_tim_freq) = match config.apb2_pre { - APBPrescaler::DIV1 => (ahb_freq, ahb_freq), - pre => { - let freq = ahb_freq / pre; - (freq, freq * 2u32) - } - }; - - let (apb3_freq, _apb3_tim_freq) = match config.apb3_pre { - APBPrescaler::DIV1 => (ahb_freq, ahb_freq), - pre => { - let freq = ahb_freq / pre; - (freq, freq * 2u32) - } - }; + let (pclk1, pclk1_tim) = super::util::calc_pclk(hclk, config.apb1_pre); + let (pclk2, pclk2_tim) = super::util::calc_pclk(hclk, config.apb2_pre); + let (pclk3, _) = super::util::calc_pclk(hclk, config.apb3_pre); let rtc = config.ls.init(); - set_freqs(Clocks { - sys: sys_clk, - hclk1: ahb_freq, - hclk2: ahb_freq, - hclk3: ahb_freq, - pclk1: apb1_freq, - pclk2: apb2_freq, - pclk3: apb3_freq, - pclk1_tim: apb1_tim_freq, - pclk2_tim: apb2_tim_freq, - rtc, - }); + config.mux.init(); + + set_clocks!( + sys: Some(sys_clk), + hclk1: Some(hclk), + hclk2: Some(hclk), + hclk3: Some(hclk), + pclk1: Some(pclk1), + pclk2: Some(pclk2), + pclk3: Some(pclk3), + pclk1_tim: Some(pclk1_tim), + pclk2_tim: Some(pclk2_tim), + hsi48: hsi48, + rtc: rtc, + hse: hse, + hsi: hsi, + pll1_p: pll1.p, + pll1_q: pll1.q, + pll1_r: pll1.r, + pll2_p: pll2.p, + pll2_q: pll2.q, + pll2_r: pll2.r, + pll3_p: pll3.p, + pll3_q: pll3.q, + pll3_r: pll3.r, + + // TODO + audioclk: None, + hsi48_div_2: None, + lse: None, + lsi: None, + msik: None, + shsi: None, + shsi_div_2: None, + ); } fn msirange_to_hertz(range: Msirange) -> Hertz { @@ -482,3 +320,126 @@ fn msirange_to_hertz(range: Msirange) -> Hertz { Msirange::RANGE_100KHZ => Hertz(100_000), } } + +pub(super) struct PllInput { + pub hsi: Option<Hertz>, + pub hse: Option<Hertz>, + pub msi: Option<Hertz>, +} + +#[allow(unused)] +#[derive(Default)] +pub(super) struct PllOutput { + pub p: Option<Hertz>, + pub q: Option<Hertz>, + pub r: Option<Hertz>, +} + +#[derive(PartialEq, Eq, Clone, Copy)] +enum PllInstance { + Pll1 = 0, + Pll2 = 1, + Pll3 = 2, +} + +fn pll_enable(instance: PllInstance, enabled: bool) { + RCC.cr().modify(|w| w.set_pllon(instance as _, enabled)); + while RCC.cr().read().pllrdy(instance as _) != enabled {} +} + +fn init_pll(instance: PllInstance, config: Option<Pll>, input: &PllInput, voltage_range: VoltageScale) -> PllOutput { + // Disable PLL + pll_enable(instance, false); + + let Some(pll) = config else { return PllOutput::default() }; + + let src_freq = match pll.source { + PllSource::DISABLE => panic!("must not select PLL source as DISABLE"), + PllSource::HSE => unwrap!(input.hse), + PllSource::HSI => unwrap!(input.hsi), + PllSource::MSIS => unwrap!(input.msi), + }; + + // Calculate the reference clock, which is the source divided by m + let ref_freq = src_freq / pll.prediv; + // Check limits per RM0456 § 11.4.6 + assert!(Hertz::mhz(4) <= ref_freq && ref_freq <= Hertz::mhz(16)); + + // Check PLL clocks per RM0456 § 11.4.10 + let (vco_min, vco_max, out_max) = match voltage_range { + VoltageScale::RANGE1 => (Hertz::mhz(128), Hertz::mhz(544), Hertz::mhz(208)), + VoltageScale::RANGE2 => (Hertz::mhz(128), Hertz::mhz(544), Hertz::mhz(110)), + VoltageScale::RANGE3 => (Hertz::mhz(128), Hertz::mhz(330), Hertz::mhz(55)), + VoltageScale::RANGE4 => panic!("PLL is unavailable in voltage range 4"), + }; + + // Calculate the PLL VCO clock + let vco_freq = ref_freq * pll.mul; + assert!(vco_freq >= vco_min && vco_freq <= vco_max); + + // Calculate output clocks. + let p = pll.divp.map(|div| vco_freq / div); + let q = pll.divq.map(|div| vco_freq / div); + let r = pll.divr.map(|div| vco_freq / div); + for freq in [p, q, r] { + if let Some(freq) = freq { + assert!(freq <= out_max); + } + } + + let divr = match instance { + PllInstance::Pll1 => RCC.pll1divr(), + PllInstance::Pll2 => RCC.pll2divr(), + PllInstance::Pll3 => RCC.pll3divr(), + }; + divr.write(|w| { + w.set_plln(pll.mul); + w.set_pllp(pll.divp.unwrap_or(PllDiv::DIV1)); + w.set_pllq(pll.divq.unwrap_or(PllDiv::DIV1)); + w.set_pllr(pll.divr.unwrap_or(PllDiv::DIV1)); + }); + + let input_range = match ref_freq.0 { + ..=8_000_000 => Pllrge::FREQ_4TO8MHZ, + _ => Pllrge::FREQ_8TO16MHZ, + }; + + macro_rules! write_fields { + ($w:ident) => { + $w.set_pllpen(pll.divp.is_some()); + $w.set_pllqen(pll.divq.is_some()); + $w.set_pllren(pll.divr.is_some()); + $w.set_pllm(pll.prediv); + $w.set_pllsrc(pll.source); + $w.set_pllrge(input_range); + }; + } + + match instance { + PllInstance::Pll1 => RCC.pll1cfgr().write(|w| { + // § 10.5.4: if we're targeting >= 55 MHz, we must configure PLL1MBOOST to a prescaler + // value that results in an output between 4 and 16 MHz for the PWR EPOD boost + if r.unwrap() >= Hertz::mhz(55) { + // source_clk can be up to 50 MHz, so there's just a few cases: + let mboost = match src_freq.0 { + ..=16_000_000 => Pllmboost::DIV1, // Bypass, giving EPOD 4-16 MHz + ..=32_000_000 => Pllmboost::DIV2, // Divide by 2, giving EPOD 8-16 MHz + _ => Pllmboost::DIV4, // Divide by 4, giving EPOD 8-12.5 MHz + }; + w.set_pllmboost(mboost); + } + write_fields!(w); + }), + PllInstance::Pll2 => RCC.pll2cfgr().write(|w| { + write_fields!(w); + }), + PllInstance::Pll3 => RCC.pll3cfgr().write(|w| { + write_fields!(w); + }), + } + + // Enable PLL + pll_enable(instance, true); + + PllOutput { p, q, r } +} diff --git a/embassy-stm32/src/rcc/wba.rs b/embassy-stm32/src/rcc/wba.rs index c0cd91507..8e1779d7c 100644 --- a/embassy-stm32/src/rcc/wba.rs +++ b/embassy-stm32/src/rcc/wba.rs @@ -1,88 +1,117 @@ -use stm32_metapac::rcc::vals::{Pllsrc, Sw}; - +pub use crate::pac::pwr::vals::Vos as VoltageScale; +use crate::pac::rcc::regs::Cfgr1; +pub use crate::pac::rcc::vals::{Hpre as AHBPrescaler, Hsepre as HsePrescaler, Ppre as APBPrescaler, Sw as Sysclk}; use crate::pac::{FLASH, RCC}; -use crate::rcc::{set_freqs, Clocks}; use crate::time::Hertz; /// HSI speed pub const HSI_FREQ: Hertz = Hertz(16_000_000); +// HSE speed +pub const HSE_FREQ: Hertz = Hertz(32_000_000); -pub use crate::pac::pwr::vals::Vos as VoltageScale; -pub use crate::pac::rcc::vals::{Hpre as AHBPrescaler, Ppre as APBPrescaler}; - -#[derive(Copy, Clone)] -pub enum ClockSrc { - HSE(Hertz), - HSI, -} - -#[derive(Clone, Copy, Debug)] -pub enum PllSource { - HSE(Hertz), - HSI, -} - -impl Into<Pllsrc> for PllSource { - fn into(self) -> Pllsrc { - match self { - PllSource::HSE(..) => Pllsrc::HSE, - PllSource::HSI => Pllsrc::HSI, - } - } -} - -impl Into<Sw> for ClockSrc { - fn into(self) -> Sw { - match self { - ClockSrc::HSE(..) => Sw::HSE, - ClockSrc::HSI => Sw::HSI, - } - } +#[derive(Clone, Copy, Eq, PartialEq)] +pub struct Hse { + pub prescaler: HsePrescaler, } +/// Clocks configuration pub struct Config { - pub mux: ClockSrc, + // base clock sources + pub hsi: bool, + pub hse: Option<Hse>, + + // sysclk, buses. + pub sys: Sysclk, pub ahb_pre: AHBPrescaler, pub apb1_pre: APBPrescaler, pub apb2_pre: APBPrescaler, pub apb7_pre: APBPrescaler, + + // low speed LSI/LSE/RTC pub ls: super::LsConfig, + + pub voltage_scale: VoltageScale, + + /// Per-peripheral kernel clock selection muxes + pub mux: super::mux::ClockMux, } impl Default for Config { - fn default() -> Self { - Self { - mux: ClockSrc::HSI, + #[inline] + fn default() -> Config { + Config { + hse: None, + hsi: true, + sys: Sysclk::HSI, ahb_pre: AHBPrescaler::DIV1, apb1_pre: APBPrescaler::DIV1, apb2_pre: APBPrescaler::DIV1, apb7_pre: APBPrescaler::DIV1, ls: Default::default(), + voltage_scale: VoltageScale::RANGE2, + mux: Default::default(), } } } +fn hsi_enable() { + RCC.cr().modify(|w| w.set_hsion(true)); + while !RCC.cr().read().hsirdy() {} +} + pub(crate) unsafe fn init(config: Config) { - let sys_clk = match config.mux { - ClockSrc::HSE(freq) => { - RCC.cr().write(|w| w.set_hseon(true)); - while !RCC.cr().read().hserdy() {} + // Switch to HSI to prevent problems with PLL configuration. + if !RCC.cr().read().hsion() { + hsi_enable() + } + if RCC.cfgr1().read().sws() != Sysclk::HSI { + // Set HSI as a clock source, reset prescalers. + RCC.cfgr1().write_value(Cfgr1::default()); + // Wait for clock switch status bits to change. + while RCC.cfgr1().read().sws() != Sysclk::HSI {} + } - freq - } - ClockSrc::HSI => { - RCC.cr().write(|w| w.set_hsion(true)); - while !RCC.cr().read().hsirdy() {} + // Set voltage scale + crate::pac::PWR.vosr().write(|w| w.set_vos(config.voltage_scale)); + while !crate::pac::PWR.vosr().read().vosrdy() {} - HSI_FREQ - } + let rtc = config.ls.init(); + + let hsi = config.hsi.then(|| { + hsi_enable(); + + HSI_FREQ + }); + + let hse = config.hse.map(|hse| { + RCC.cr().write(|w| { + w.set_hseon(true); + w.set_hsepre(hse.prescaler); + }); + while !RCC.cr().read().hserdy() {} + + HSE_FREQ + }); + + let sys_clk = match config.sys { + Sysclk::HSE => hse.unwrap(), + Sysclk::HSI => hsi.unwrap(), + Sysclk::_RESERVED_1 => unreachable!(), + Sysclk::PLL1_R => todo!(), }; - // TODO make configurable - let power_vos = VoltageScale::RANGE1; + assert!(sys_clk.0 <= 100_000_000); - // states and programming delay - let wait_states = match power_vos { + let hclk1 = sys_clk / config.ahb_pre; + let hclk2 = hclk1; + let hclk4 = hclk1; + // TODO: hclk5 + let (pclk1, pclk1_tim) = super::util::calc_pclk(hclk1, config.apb1_pre); + let (pclk2, pclk2_tim) = super::util::calc_pclk(hclk1, config.apb2_pre); + let (pclk7, _) = super::util::calc_pclk(hclk1, config.apb7_pre); + + // Set flash wait states + let flash_latency = match config.voltage_scale { VoltageScale::RANGE1 => match sys_clk.0 { ..=32_000_000 => 0, ..=64_000_000 => 1, @@ -97,13 +126,24 @@ pub(crate) unsafe fn init(config: Config) { }, }; - FLASH.acr().modify(|w| { - w.set_latency(wait_states); - }); + FLASH.acr().modify(|w| w.set_latency(flash_latency)); + while FLASH.acr().read().latency() != flash_latency {} + + // Set sram wait states + let _sram_latency = match config.voltage_scale { + VoltageScale::RANGE1 => 0, + VoltageScale::RANGE2 => match sys_clk.0 { + ..=12_000_000 => 0, + ..=16_000_000 => 1, + _ => 2, + }, + }; + // TODO: Set the SRAM wait states RCC.cfgr1().modify(|w| { - w.set_sw(config.mux.into()); + w.set_sw(config.sys); }); + while RCC.cfgr1().read().sws() != config.sys {} RCC.cfgr2().modify(|w| { w.set_hpre(config.ahb_pre); @@ -111,45 +151,25 @@ pub(crate) unsafe fn init(config: Config) { w.set_ppre2(config.apb2_pre); }); - RCC.cfgr3().modify(|w| { - w.set_ppre7(config.apb7_pre); - }); + config.mux.init(); - let ahb_freq = sys_clk / config.ahb_pre; - let (apb1_freq, apb1_tim_freq) = match config.apb1_pre { - APBPrescaler::DIV1 => (ahb_freq, ahb_freq), - pre => { - let freq = ahb_freq / pre; - (freq, freq * 2u32) - } - }; - let (apb2_freq, apb2_tim_freq) = match config.apb2_pre { - APBPrescaler::DIV1 => (ahb_freq, ahb_freq), - pre => { - let freq = ahb_freq / pre; - (freq, freq * 2u32) - } - }; - let (apb7_freq, _apb7_tim_freq) = match config.apb7_pre { - APBPrescaler::DIV1 => (ahb_freq, ahb_freq), - pre => { - let freq = ahb_freq / pre; - (freq, freq * 2u32) - } - }; + set_clocks!( + sys: Some(sys_clk), + hclk1: Some(hclk1), + hclk2: Some(hclk2), + hclk4: Some(hclk4), + pclk1: Some(pclk1), + pclk2: Some(pclk2), + pclk7: Some(pclk7), + pclk1_tim: Some(pclk1_tim), + pclk2_tim: Some(pclk2_tim), + rtc: rtc, + hse: hse, + hsi: hsi, - let rtc = config.ls.init(); - - set_freqs(Clocks { - sys: sys_clk, - hclk1: ahb_freq, - hclk2: ahb_freq, - hclk4: ahb_freq, - pclk1: apb1_freq, - pclk2: apb2_freq, - pclk7: apb7_freq, - pclk1_tim: apb1_tim_freq, - pclk2_tim: apb2_tim_freq, - rtc, - }); + // TODO + lse: None, + lsi: None, + pll1_q: None, + ); } diff --git a/embassy-stm32/src/sai/mod.rs b/embassy-stm32/src/sai/mod.rs index 5e647612c..02f96f8a9 100644 --- a/embassy-stm32/src/sai/mod.rs +++ b/embassy-stm32/src/sai/mod.rs @@ -1,5 +1,6 @@ //! Serial Audio Interface (SAI) #![macro_use] +#![cfg_attr(gpdma, allow(unused))] use core::marker::PhantomData; @@ -7,6 +8,7 @@ use embassy_hal_internal::{into_ref, PeripheralRef}; use self::sealed::WhichSubBlock; pub use crate::dma::word; +#[cfg(not(gpdma))] use crate::dma::{ringbuffer, Channel, ReadableRingBuffer, Request, TransferOptions, WritableRingBuffer}; use crate::gpio::sealed::{AFType, Pin as _}; use crate::gpio::AnyPin; @@ -26,6 +28,7 @@ pub enum Error { Overrun, } +#[cfg(not(gpdma))] impl From<ringbuffer::OverrunError> for Error { fn from(_: ringbuffer::OverrunError) -> Self { Self::Overrun @@ -41,7 +44,7 @@ pub enum Mode { } impl Mode { - #[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] + #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] const fn mode(&self, tx_rx: TxRx) -> vals::Mode { match tx_rx { TxRx::Transmitter => match self { @@ -76,7 +79,7 @@ pub enum SlotSize { } impl SlotSize { - #[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] + #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] const fn slotsz(&self) -> vals::Slotsz { match self { SlotSize::DataSize => vals::Slotsz::DATASIZE, @@ -99,7 +102,7 @@ pub enum DataSize { } impl DataSize { - #[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] + #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] const fn ds(&self) -> vals::Ds { match self { DataSize::Data8 => vals::Ds::BIT8, @@ -124,7 +127,7 @@ pub enum FifoThreshold { } impl FifoThreshold { - #[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] + #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] const fn fth(&self) -> vals::Fth { match self { FifoThreshold::Empty => vals::Fth::EMPTY, @@ -145,7 +148,7 @@ pub enum MuteValue { } impl MuteValue { - #[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] + #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] const fn muteval(&self) -> vals::Muteval { match self { MuteValue::Zero => vals::Muteval::SENDZERO, @@ -164,7 +167,7 @@ pub enum Protocol { } impl Protocol { - #[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] + #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] const fn prtcfg(&self) -> vals::Prtcfg { match self { Protocol::Free => vals::Prtcfg::FREE, @@ -183,7 +186,7 @@ pub enum SyncInput { /// Syncs with the other A/B sub-block within the SAI unit Internal, /// Syncs with a sub-block in the other SAI unit - #[cfg(sai_v4)] + #[cfg(any(sai_v4_2pdm, sai_v4_4pdm))] External(SyncInputInstance), } @@ -192,14 +195,14 @@ impl SyncInput { match self { SyncInput::None => vals::Syncen::ASYNCHRONOUS, SyncInput::Internal => vals::Syncen::INTERNAL, - #[cfg(any(sai_v4))] + #[cfg(any(sai_v4_2pdm, sai_v4_4pdm))] SyncInput::External(_) => vals::Syncen::EXTERNAL, } } } /// SAI instance to sync from. -#[cfg(sai_v4)] +#[cfg(any(sai_v4_2pdm, sai_v4_4pdm))] #[derive(Copy, Clone, PartialEq)] #[allow(missing_docs)] pub enum SyncInputInstance { @@ -222,7 +225,7 @@ pub enum StereoMono { } impl StereoMono { - #[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] + #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] const fn mono(&self) -> vals::Mono { match self { StereoMono::Stereo => vals::Mono::STEREO, @@ -241,7 +244,7 @@ pub enum BitOrder { } impl BitOrder { - #[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] + #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] const fn lsbfirst(&self) -> vals::Lsbfirst { match self { BitOrder::LsbFirst => vals::Lsbfirst::LSBFIRST, @@ -260,7 +263,7 @@ pub enum FrameSyncOffset { } impl FrameSyncOffset { - #[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] + #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] const fn fsoff(&self) -> vals::Fsoff { match self { FrameSyncOffset::OnFirstBit => vals::Fsoff::ONFIRST, @@ -279,7 +282,7 @@ pub enum FrameSyncPolarity { } impl FrameSyncPolarity { - #[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] + #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] const fn fspol(&self) -> vals::Fspol { match self { FrameSyncPolarity::ActiveLow => vals::Fspol::FALLINGEDGE, @@ -297,7 +300,7 @@ pub enum FrameSyncDefinition { } impl FrameSyncDefinition { - #[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] + #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] const fn fsdef(&self) -> bool { match self { FrameSyncDefinition::StartOfFrame => false, @@ -315,7 +318,7 @@ pub enum ClockStrobe { } impl ClockStrobe { - #[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] + #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] const fn ckstr(&self) -> vals::Ckstr { match self { ClockStrobe::Falling => vals::Ckstr::FALLINGEDGE, @@ -333,7 +336,7 @@ pub enum ComplementFormat { } impl ComplementFormat { - #[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] + #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] const fn cpl(&self) -> vals::Cpl { match self { ComplementFormat::OnesComplement => vals::Cpl::ONESCOMPLEMENT, @@ -352,7 +355,7 @@ pub enum Companding { } impl Companding { - #[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] + #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] const fn comp(&self) -> vals::Comp { match self { Companding::None => vals::Comp::NOCOMPANDING, @@ -371,7 +374,7 @@ pub enum OutputDrive { } impl OutputDrive { - #[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] + #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] const fn outdriv(&self) -> vals::Outdriv { match self { OutputDrive::OnStart => vals::Outdriv::ONSTART, @@ -404,7 +407,7 @@ pub enum MasterClockDivider { } impl MasterClockDivider { - #[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] + #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] const fn mckdiv(&self) -> u8 { match self { MasterClockDivider::MasterClockDisabled => 0, @@ -501,12 +504,12 @@ impl Config { } } -enum RingBuffer<'d, C: Channel, W: word::Word> { - Writable(WritableRingBuffer<'d, C, W>), - Readable(ReadableRingBuffer<'d, C, W>), +#[cfg(not(gpdma))] +enum RingBuffer<'d, W: word::Word> { + Writable(WritableRingBuffer<'d, W>), + Readable(ReadableRingBuffer<'d, W>), } -#[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] fn dr<W: word::Word>(w: crate::pac::sai::Sai, sub_block: WhichSubBlock) -> *mut W { let ch = w.ch(sub_block as usize); ch.dr().as_ptr() as _ @@ -528,13 +531,14 @@ fn get_af_types(mode: Mode, tx_rx: TxRx) -> (AFType, AFType) { ) } -fn get_ring_buffer<'d, T: Instance, C: Channel, W: word::Word>( - dma: impl Peripheral<P = C> + 'd, +#[cfg(not(gpdma))] +fn get_ring_buffer<'d, T: Instance, W: word::Word>( + dma: impl Peripheral<P = impl Channel> + 'd, dma_buf: &'d mut [W], request: Request, sub_block: WhichSubBlock, tx_rx: TxRx, -) -> RingBuffer<'d, C, W> { +) -> RingBuffer<'d, W> { let opts = TransferOptions { half_transfer_ir: true, //the new_write() and new_read() always use circular mode @@ -554,12 +558,12 @@ fn update_synchronous_config(config: &mut Config) { config.mode = Mode::Slave; config.sync_output = false; - #[cfg(any(sai_v1, sai_v2, sai_v3))] + #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm))] { config.sync_input = SyncInput::Internal; } - #[cfg(any(sai_v4))] + #[cfg(any(sai_v4_2pdm, sai_v4_4pdm))] { //this must either be Internal or External //The asynchronous sub-block on the same SAI needs to enable sync_output @@ -593,17 +597,21 @@ pub fn split_subblocks<'d, T: Instance>(peri: impl Peripheral<P = T> + 'd) -> (S } /// SAI sub-block driver. -pub struct Sai<'d, T: Instance, C: Channel, W: word::Word> { +pub struct Sai<'d, T: Instance, W: word::Word> { _peri: PeripheralRef<'d, T>, sd: Option<PeripheralRef<'d, AnyPin>>, fs: Option<PeripheralRef<'d, AnyPin>>, sck: Option<PeripheralRef<'d, AnyPin>>, mclk: Option<PeripheralRef<'d, AnyPin>>, - ring_buffer: RingBuffer<'d, C, W>, + #[cfg(gpdma)] + ring_buffer: PhantomData<W>, + #[cfg(not(gpdma))] + ring_buffer: RingBuffer<'d, W>, sub_block: WhichSubBlock, } -impl<'d, T: Instance, C: Channel, W: word::Word> Sai<'d, T, C, W> { +#[cfg(not(gpdma))] +impl<'d, T: Instance, W: word::Word> Sai<'d, T, W> { /// Create a new SAI driver in asynchronous mode with MCLK. /// /// You can obtain the [`SubBlock`] with [`split_subblocks`]. @@ -613,13 +621,10 @@ impl<'d, T: Instance, C: Channel, W: word::Word> Sai<'d, T, C, W> { sd: impl Peripheral<P = impl SdPin<T, S>> + 'd, fs: impl Peripheral<P = impl FsPin<T, S>> + 'd, mclk: impl Peripheral<P = impl MclkPin<T, S>> + 'd, - dma: impl Peripheral<P = C> + 'd, + dma: impl Peripheral<P = impl Channel + Dma<T, S>> + 'd, dma_buf: &'d mut [W], mut config: Config, - ) -> Self - where - C: Channel + Dma<T, S>, - { + ) -> Self { into_ref!(mclk); let (_sd_af_type, ck_af_type) = get_af_types(config.mode, config.tx_rx); @@ -642,13 +647,10 @@ impl<'d, T: Instance, C: Channel, W: word::Word> Sai<'d, T, C, W> { sck: impl Peripheral<P = impl SckPin<T, S>> + 'd, sd: impl Peripheral<P = impl SdPin<T, S>> + 'd, fs: impl Peripheral<P = impl FsPin<T, S>> + 'd, - dma: impl Peripheral<P = C> + 'd, + dma: impl Peripheral<P = impl Channel + Dma<T, S>> + 'd, dma_buf: &'d mut [W], config: Config, - ) -> Self - where - C: Channel + Dma<T, S>, - { + ) -> Self { let peri = peri.peri; into_ref!(peri, dma, sck, sd, fs); @@ -671,7 +673,7 @@ impl<'d, T: Instance, C: Channel, W: word::Word> Sai<'d, T, C, W> { None, Some(sd.map_into()), Some(fs.map_into()), - get_ring_buffer::<T, C, W>(dma, dma_buf, request, sub_block, config.tx_rx), + get_ring_buffer::<T, W>(dma, dma_buf, request, sub_block, config.tx_rx), config, ) } @@ -682,13 +684,10 @@ impl<'d, T: Instance, C: Channel, W: word::Word> Sai<'d, T, C, W> { pub fn new_synchronous<S: SubBlockInstance>( peri: SubBlock<'d, T, S>, sd: impl Peripheral<P = impl SdPin<T, S>> + 'd, - dma: impl Peripheral<P = C> + 'd, + dma: impl Peripheral<P = impl Channel + Dma<T, S>> + 'd, dma_buf: &'d mut [W], mut config: Config, - ) -> Self - where - C: Channel + Dma<T, S>, - { + ) -> Self { update_synchronous_config(&mut config); let peri = peri.peri; @@ -709,7 +708,7 @@ impl<'d, T: Instance, C: Channel, W: word::Word> Sai<'d, T, C, W> { None, Some(sd.map_into()), None, - get_ring_buffer::<T, C, W>(dma, dma_buf, request, sub_block, config.tx_rx), + get_ring_buffer::<T, W>(dma, dma_buf, request, sub_block, config.tx_rx), config, ) } @@ -721,16 +720,16 @@ impl<'d, T: Instance, C: Channel, W: word::Word> Sai<'d, T, C, W> { mclk: Option<PeripheralRef<'d, AnyPin>>, sd: Option<PeripheralRef<'d, AnyPin>>, fs: Option<PeripheralRef<'d, AnyPin>>, - ring_buffer: RingBuffer<'d, C, W>, + ring_buffer: RingBuffer<'d, W>, config: Config, ) -> Self { - #[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] + #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] { let ch = T::REGS.ch(sub_block as usize); ch.cr1().modify(|w| w.set_saien(false)); } - #[cfg(any(sai_v4))] + #[cfg(any(sai_v4_2pdm, sai_v4_4pdm))] { if let SyncInput::External(i) = config.sync_input { T::REGS.gcr().modify(|w| { @@ -749,7 +748,7 @@ impl<'d, T: Instance, C: Channel, W: word::Word> Sai<'d, T, C, W> { } } - #[cfg(any(sai_v1, sai_v2, sai_v3, sai_v4))] + #[cfg(any(sai_v1, sai_v2, sai_v3_2pdm, sai_v3_4pdm, sai_v4_2pdm, sai_v4_4pdm))] { let ch = T::REGS.ch(sub_block as usize); ch.cr1().modify(|w| { @@ -830,7 +829,7 @@ impl<'d, T: Instance, C: Channel, W: word::Word> Sai<'d, T, C, W> { } } - fn is_transmitter(ring_buffer: &RingBuffer<C, W>) -> bool { + fn is_transmitter(ring_buffer: &RingBuffer<W>) -> bool { match ring_buffer { RingBuffer::Writable(_) => true, _ => false, @@ -889,7 +888,7 @@ impl<'d, T: Instance, C: Channel, W: word::Word> Sai<'d, T, C, W> { } } -impl<'d, T: Instance, C: Channel, W: word::Word> Drop for Sai<'d, T, C, W> { +impl<'d, T: Instance, W: word::Word> Drop for Sai<'d, T, W> { fn drop(&mut self) { let ch = T::REGS.ch(self.sub_block as usize); ch.cr1().modify(|w| w.set_saien(false)); diff --git a/embassy-stm32/src/sdmmc/mod.rs b/embassy-stm32/src/sdmmc/mod.rs index debe26c88..bf1d2ca9b 100644 --- a/embassy-stm32/src/sdmmc/mod.rs +++ b/embassy-stm32/src/sdmmc/mod.rs @@ -228,10 +228,10 @@ fn clk_div(ker_ck: Hertz, sdmmc_ck: u32) -> Result<(bool, u16, Hertz), Error> { } #[cfg(sdmmc_v1)] -type Transfer<'a, C> = crate::dma::Transfer<'a, C>; +type Transfer<'a> = crate::dma::Transfer<'a>; #[cfg(sdmmc_v2)] -struct Transfer<'a, C> { - _dummy: core::marker::PhantomData<&'a mut C>, +struct Transfer<'a> { + _dummy: PhantomData<&'a ()>, } #[cfg(all(sdmmc_v1, dma))] @@ -548,7 +548,7 @@ impl<'d, T: Instance, Dma: SdmmcDma<T> + 'd> Sdmmc<'d, T, Dma> { buffer: &'a mut [u32], length_bytes: u32, block_size: u8, - ) -> Transfer<'a, Dma> { + ) -> Transfer<'a> { assert!(block_size <= 14, "Block size up to 2^14 bytes"); let regs = T::regs(); @@ -596,12 +596,7 @@ impl<'d, T: Instance, Dma: SdmmcDma<T> + 'd> Sdmmc<'d, T, Dma> { /// # Safety /// /// `buffer` must be valid for the whole transfer and word aligned - fn prepare_datapath_write<'a>( - &'a mut self, - buffer: &'a [u32], - length_bytes: u32, - block_size: u8, - ) -> Transfer<'a, Dma> { + fn prepare_datapath_write<'a>(&'a mut self, buffer: &'a [u32], length_bytes: u32, block_size: u8) -> Transfer<'a> { assert!(block_size <= 14, "Block size up to 2^14 bytes"); let regs = T::regs(); @@ -670,7 +665,7 @@ impl<'d, T: Instance, Dma: SdmmcDma<T> + 'd> Sdmmc<'d, T, Dma> { _ => panic!("Invalid Bus Width"), }; - let ker_ck = T::kernel_clk(); + let ker_ck = T::frequency(); let (_bypass, clkdiv, new_clock) = clk_div(ker_ck, freq)?; // Enforce AHB and SDMMC_CK clock relation. See RM0433 Rev 7 @@ -1023,7 +1018,7 @@ impl<'d, T: Instance, Dma: SdmmcDma<T> + 'd> Sdmmc<'d, T, Dma> { /// specified frequency. pub async fn init_card(&mut self, freq: Hertz) -> Result<(), Error> { let regs = T::regs(); - let ker_ck = T::kernel_clk(); + let ker_ck = T::frequency(); let bus_width = match self.d3.is_some() { true => BusWidth::Four, @@ -1429,7 +1424,6 @@ pub(crate) mod sealed { fn regs() -> RegBlock; fn state() -> &'static AtomicWaker; - fn kernel_clk() -> Hertz; } pub trait Pins<T: Instance> {} @@ -1461,61 +1455,6 @@ pub trait SdmmcDma<T: Instance> {} #[cfg(sdmmc_v2)] impl<T: Instance> SdmmcDma<T> for NoDma {} -cfg_if::cfg_if! { - // 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) => { - <peripherals::$inst as crate::rcc::sealed::RccPeripheral>::frequency() - } - } - } else if #[cfg(any(stm32f2, stm32f4))] { - // F2, F4 always use pll48 - macro_rules! kernel_clk { - ($inst:ident) => { - critical_section::with(|_| unsafe { - unwrap!(crate::rcc::get_freqs().pll1_q) - }) - } - } - } 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::SYS { - crate::rcc::get_freqs().sys - } else { - unwrap!(crate::rcc::get_freqs().pll1_q) - } - }) - }; - (SDMMC2) => { - critical_section::with(|_| unsafe { - let sdmmcsel = crate::pac::RCC.dckcfgr2().read().sdmmc2sel(); - if sdmmcsel == crate::pac::rcc::vals::Sdmmcsel::SYS { - crate::rcc::get_freqs().sys - } else { - unwrap!(crate::rcc::get_freqs().pll1_q) - } - }) - }; - } - } else { - // Use default peripheral clock and hope it works - macro_rules! kernel_clk { - ($inst:ident) => { - <peripherals::$inst as crate::rcc::sealed::RccPeripheral>::frequency() - } - } - } -} - foreach_peripheral!( (sdmmc, $inst:ident) => { impl sealed::Instance for peripherals::$inst { @@ -1529,10 +1468,6 @@ foreach_peripheral!( static WAKER: ::embassy_sync::waitqueue::AtomicWaker = ::embassy_sync::waitqueue::AtomicWaker::new(); &WAKER } - - fn kernel_clk() -> Hertz { - kernel_clk!($inst) - } } impl Instance for peripherals::$inst {} diff --git a/embassy-stm32/src/spi/mod.rs b/embassy-stm32/src/spi/mod.rs index 23f027e70..172bc8112 100644 --- a/embassy-stm32/src/spi/mod.rs +++ b/embassy-stm32/src/spi/mod.rs @@ -1005,8 +1005,8 @@ mod word_impl { pub type Config = vals::Dff; - impl_word!(u8, vals::Dff::EIGHTBIT); - impl_word!(u16, vals::Dff::SIXTEENBIT); + impl_word!(u8, vals::Dff::BITS8); + impl_word!(u16, vals::Dff::BITS16); } #[cfg(spi_v2)] @@ -1015,19 +1015,19 @@ mod word_impl { 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)); + impl_word!(word::U4, (vals::Ds::BITS4, vals::Frxth::QUARTER)); + impl_word!(word::U5, (vals::Ds::BITS5, vals::Frxth::QUARTER)); + impl_word!(word::U6, (vals::Ds::BITS6, vals::Frxth::QUARTER)); + impl_word!(word::U7, (vals::Ds::BITS7, vals::Frxth::QUARTER)); + impl_word!(u8, (vals::Ds::BITS8, vals::Frxth::QUARTER)); + impl_word!(word::U9, (vals::Ds::BITS9, vals::Frxth::HALF)); + impl_word!(word::U10, (vals::Ds::BITS10, vals::Frxth::HALF)); + impl_word!(word::U11, (vals::Ds::BITS11, vals::Frxth::HALF)); + impl_word!(word::U12, (vals::Ds::BITS12, vals::Frxth::HALF)); + impl_word!(word::U13, (vals::Ds::BITS13, vals::Frxth::HALF)); + impl_word!(word::U14, (vals::Ds::BITS14, vals::Frxth::HALF)); + impl_word!(word::U15, (vals::Ds::BITS15, vals::Frxth::HALF)); + impl_word!(u16, (vals::Ds::BITS16, vals::Frxth::HALF)); } #[cfg(any(spi_v3, spi_v4, spi_v5))] diff --git a/embassy-stm32/src/time_driver.rs b/embassy-stm32/src/time_driver.rs index 320b29ddb..37b2e7526 100644 --- a/embassy-stm32/src/time_driver.rs +++ b/embassy-stm32/src/time_driver.rs @@ -1,3 +1,5 @@ +#![allow(non_snake_case)] + use core::cell::Cell; use core::convert::TryInto; use core::sync::atomic::{compiler_fence, AtomicU32, AtomicU8, Ordering}; @@ -14,7 +16,9 @@ use crate::pac::timer::vals; use crate::rcc::sealed::RccPeripheral; #[cfg(feature = "low-power")] use crate::rtc::Rtc; -use crate::timer::sealed::{Basic16bitInstance as BasicInstance, GeneralPurpose16bitInstance as Instance}; +#[cfg(any(time_driver_tim1, time_driver_tim8, time_driver_tim20))] +use crate::timer::sealed::AdvancedControlInstance; +use crate::timer::sealed::{CoreInstance, GeneralPurpose16bitInstance as Instance}; use crate::{interrupt, peripherals}; // NOTE regarding ALARM_COUNT: @@ -22,18 +26,22 @@ use crate::{interrupt, peripherals}; // As of 2023-12-04, this driver is implemented using CC1 as the halfway rollover interrupt, and any // additional CC capabilities to provide timer alarms to embassy-time. embassy-time requires AT LEAST // one alarm to be allocatable, which means timers that only have CC1, such as TIM16/TIM17, are not -// candidates for use as an embassy-time driver provider. +// candidates for use as an embassy-time driver provider. (a.k.a 1CH and 1CH_CMP are not, others are good.) // // The values of ALARM_COUNT below are not the TOTAL CC registers available, but rather the number // available after reserving CC1 for regular time keeping. For example, TIM2 has four CC registers: // CC1, CC2, CC3, and CC4, so it can provide ALARM_COUNT = 3. -#[cfg(not(any(time_driver_tim12, time_driver_tim15, time_driver_tim21, time_driver_tim22)))] -const ALARM_COUNT: usize = 3; - -#[cfg(any(time_driver_tim12, time_driver_tim15, time_driver_tim21, time_driver_tim22))] -const ALARM_COUNT: usize = 1; +cfg_if::cfg_if! { + if #[cfg(any(time_driver_tim9, time_driver_tim12, time_driver_tim15, time_driver_tim21, time_driver_tim22))] { + const ALARM_COUNT: usize = 1; + } else { + const ALARM_COUNT: usize = 3; + } +} +#[cfg(time_driver_tim1)] +type T = peripherals::TIM1; #[cfg(time_driver_tim2)] type T = peripherals::TIM2; #[cfg(time_driver_tim3)] @@ -42,20 +50,42 @@ type T = peripherals::TIM3; type T = peripherals::TIM4; #[cfg(time_driver_tim5)] type T = peripherals::TIM5; +#[cfg(time_driver_tim8)] +type T = peripherals::TIM8; #[cfg(time_driver_tim9)] type T = peripherals::TIM9; -#[cfg(time_driver_tim11)] -type T = peripherals::TIM11; #[cfg(time_driver_tim12)] type T = peripherals::TIM12; #[cfg(time_driver_tim15)] type T = peripherals::TIM15; +#[cfg(time_driver_tim20)] +type T = peripherals::TIM20; #[cfg(time_driver_tim21)] type T = peripherals::TIM21; #[cfg(time_driver_tim22)] type T = peripherals::TIM22; +#[cfg(time_driver_tim23)] +type T = peripherals::TIM23; +#[cfg(time_driver_tim24)] +type T = peripherals::TIM24; foreach_interrupt! { + (TIM1, timer, $block:ident, UP, $irq:ident) => { + #[cfg(time_driver_tim1)] + #[cfg(feature = "rt")] + #[interrupt] + fn $irq() { + DRIVER.on_interrupt() + } + }; + (TIM1, timer, $block:ident, CC, $irq:ident) => { + #[cfg(time_driver_tim1)] + #[cfg(feature = "rt")] + #[interrupt] + fn $irq() { + DRIVER.on_interrupt() + } + }; (TIM2, timer, $block:ident, UP, $irq:ident) => { #[cfg(time_driver_tim2)] #[cfg(feature = "rt")] @@ -88,16 +118,24 @@ foreach_interrupt! { DRIVER.on_interrupt() } }; - (TIM9, timer, $block:ident, UP, $irq:ident) => { - #[cfg(time_driver_tim9)] + (TIM8, timer, $block:ident, UP, $irq:ident) => { + #[cfg(time_driver_tim8)] #[cfg(feature = "rt")] #[interrupt] fn $irq() { DRIVER.on_interrupt() } }; - (TIM11, timer, $block:ident, UP, $irq:ident) => { - #[cfg(time_driver_tim11)] + (TIM8, timer, $block:ident, CC, $irq:ident) => { + #[cfg(time_driver_tim8)] + #[cfg(feature = "rt")] + #[interrupt] + fn $irq() { + DRIVER.on_interrupt() + } + }; + (TIM9, timer, $block:ident, UP, $irq:ident) => { + #[cfg(time_driver_tim9)] #[cfg(feature = "rt")] #[interrupt] fn $irq() { @@ -120,6 +158,22 @@ foreach_interrupt! { DRIVER.on_interrupt() } }; + (TIM20, timer, $block:ident, UP, $irq:ident) => { + #[cfg(time_driver_tim20)] + #[cfg(feature = "rt")] + #[interrupt] + fn $irq() { + DRIVER.on_interrupt() + } + }; + (TIM20, timer, $block:ident, CC, $irq:ident) => { + #[cfg(time_driver_tim20)] + #[cfg(feature = "rt")] + #[interrupt] + fn $irq() { + DRIVER.on_interrupt() + } + }; (TIM21, timer, $block:ident, UP, $irq:ident) => { #[cfg(time_driver_tim21)] #[cfg(feature = "rt")] @@ -136,6 +190,22 @@ foreach_interrupt! { DRIVER.on_interrupt() } }; + (TIM23, timer, $block:ident, UP, $irq:ident) => { + #[cfg(time_driver_tim23)] + #[cfg(feature = "rt")] + #[interrupt] + fn $irq() { + DRIVER.on_interrupt() + } + }; + (TIM24, timer, $block:ident, UP, $irq:ident) => { + #[cfg(time_driver_tim24)] + #[cfg(feature = "rt")] + #[interrupt] + fn $irq() { + DRIVER.on_interrupt() + } + }; } // Clock timekeeping works with something we call "periods", which are time intervals @@ -234,8 +304,16 @@ impl RtcDriver { w.set_ccie(0, true); }); - <T as BasicInstance>::Interrupt::unpend(); - unsafe { <T as BasicInstance>::Interrupt::enable() }; + <T as CoreInstance>::Interrupt::unpend(); + unsafe { <T as CoreInstance>::Interrupt::enable() }; + + #[cfg(any(time_driver_tim1, time_driver_tim8, time_driver_tim20))] + { + <T as AdvancedControlInstance>::CaptureCompareInterrupt::unpend(); + unsafe { + <T as AdvancedControlInstance>::CaptureCompareInterrupt::enable(); + } + } r.cr1().modify(|w| w.set_cen(true)); } @@ -251,7 +329,7 @@ impl RtcDriver { // Clear all interrupt flags. Bits in SR are "write 0 to clear", so write the bitwise NOT. // Other approaches such as writing all zeros, or RMWing won't work, they can // miss interrupts. - r.sr().write_value(regs::SrGp(!sr.0)); + r.sr().write_value(regs::SrGp16(!sr.0)); // Overflow if sr.uif() { diff --git a/embassy-stm32/src/timer/complementary_pwm.rs b/embassy-stm32/src/timer/complementary_pwm.rs index 71d7110b5..72f1ec864 100644 --- a/embassy-stm32/src/timer/complementary_pwm.rs +++ b/embassy-stm32/src/timer/complementary_pwm.rs @@ -23,7 +23,7 @@ pub struct ComplementaryPwmPin<'d, T, C> { macro_rules! complementary_channel_impl { ($new_chx:ident, $channel:ident, $pin_trait:ident) => { - impl<'d, T: CaptureCompare16bitInstance> ComplementaryPwmPin<'d, T, $channel> { + impl<'d, T: ComplementaryCaptureCompare16bitInstance> ComplementaryPwmPin<'d, T, $channel> { #[doc = concat!("Create a new ", stringify!($channel), " complementary PWM pin instance.")] pub fn $new_chx(pin: impl Peripheral<P = impl $pin_trait<T>> + 'd, output_type: OutputType) -> Self { into_ref!(pin); @@ -54,6 +54,7 @@ pub struct ComplementaryPwm<'d, T> { impl<'d, T: ComplementaryCaptureCompare16bitInstance> ComplementaryPwm<'d, T> { /// Create a new complementary PWM driver. + #[allow(clippy::too_many_arguments)] pub fn new( tim: impl Peripheral<P = T> + 'd, _ch1: Option<PwmPin<'d, T, Ch1>>, @@ -83,14 +84,13 @@ impl<'d, T: ComplementaryCaptureCompare16bitInstance> ComplementaryPwm<'d, T> { this.inner.enable_outputs(); - 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); + [Channel::Ch1, Channel::Ch2, Channel::Ch3, Channel::Ch4] + .iter() + .for_each(|&channel| { + this.inner.set_output_compare_mode(channel, OutputCompareMode::PwmMode1); + this.inner.set_output_compare_preload(channel, true); + }); + this } @@ -165,7 +165,7 @@ impl<'d, T: ComplementaryCaptureCompare16bitInstance> embedded_hal_02::Pwm for C } fn get_period(&self) -> Self::Time { - self.inner.get_frequency().into() + self.inner.get_frequency() } fn get_duty(&self, channel: Self::Channel) -> Self::Duty { diff --git a/embassy-stm32/src/timer/mod.rs b/embassy-stm32/src/timer/mod.rs index d07fd2776..8530c5229 100644 --- a/embassy-stm32/src/timer/mod.rs +++ b/embassy-stm32/src/timer/mod.rs @@ -1,5 +1,37 @@ //! Timers, PWM, quadrature decoder. +//! +//! Timer inheritance +//! + +// sealed: +// +// Core -------------------------> 1CH -------------------------> 1CH_CMP +// | | ^ | +// +--> Basic_NoCr2 --> Basic +--> 2CH --> GP16 --> GP32 | +--> 2CH_CMP --> ADV +// | | | ^ | | ^ ^ +// | | +------|--|--------------|-----------+ | +// | +--------------------+ +--------------|-----------|---------+ +// | | | | +// | +--------------------------------------|-----------+ +// +----------------------------------------------------+ + +//! ```text +//! BasicInstance --> CaptureCompare16bitInstance --+--> ComplementaryCaptureCompare16bitInstance +//! | +//! +--> CaptureCompare32bitInstance +//! ``` +//! +//! Mapping: +//! +//! | trait | timer | +//! | :----------------------------------------: | ------------------------------------------------------------------------------------------------- | +//! | [BasicInstance] | Basic Timer | +//! | [CaptureCompare16bitInstance] | 1-channel Timer, 2-channel Timer, General Purpose 16-bit Timer | +//! | [CaptureCompare32bitInstance] | General Purpose 32-bit Timer | +//! | [ComplementaryCaptureCompare16bitInstance] | 1-channel with one complentary Timer, 2-channel with one complentary Timer, Advance Control Timer | + +#[cfg(not(stm32l0))] pub mod complementary_pwm; pub mod qei; pub mod simple_pwm; @@ -19,32 +51,32 @@ pub mod low_level { pub(crate) mod sealed { use super::*; - /// Basic 16-bit timer instance. - pub trait Basic16bitInstance: RccPeripheral { + /// Virtual Core 16-bit timer instance. + pub trait CoreInstance: RccPeripheral { /// Interrupt for this timer. type Interrupt: interrupt::typelevel::Interrupt; - /// Get access to the basic 16bit timer registers. + /// Get access to the virutal core 16bit timer registers. /// /// Note: This works even if the timer is more capable, because registers /// for the less capable timers are a subset. This allows writing a driver /// for a given set of capabilities, and having it transparently work with /// more capable timers. - fn regs() -> crate::pac::timer::TimBasic; + fn regs_core() -> crate::pac::timer::TimCore; /// Start the timer. - fn start(&mut self) { - Self::regs().cr1().modify(|r| r.set_cen(true)); + fn start(&self) { + Self::regs_core().cr1().modify(|r| r.set_cen(true)); } /// Stop the timer. - fn stop(&mut self) { - Self::regs().cr1().modify(|r| r.set_cen(false)); + fn stop(&self) { + Self::regs_core().cr1().modify(|r| r.set_cen(false)); } /// Reset the counter value to 0 - fn reset(&mut self) { - Self::regs().cnt().write(|r| r.set_cnt(0)); + fn reset(&self) { + Self::regs_core().cnt().write(|r| r.set_cnt(0)); } /// Set the frequency of how many times per second the timer counts up to the max value or down to 0. @@ -53,7 +85,7 @@ pub(crate) mod sealed { /// the timer counter will wrap around at the same frequency as is being set. /// In center-aligned mode (which not all timers support), the wrap-around frequency is effectively halved /// because it needs to count up and down. - fn set_frequency(&mut self, frequency: Hertz) { + fn set_frequency(&self, frequency: Hertz) { let f = frequency.0; let timer_f = Self::frequency().0; assert!(f > 0); @@ -64,7 +96,7 @@ pub(crate) mod sealed { // the timer counts `0..=arr`, we want it to count `0..divide_by` let arr = unwrap!(u16::try_from(divide_by - 1)); - let regs = Self::regs(); + let regs = Self::regs_core(); regs.psc().write(|r| r.set_psc(psc)); regs.arr().write(|r| r.set_arr(arr)); @@ -76,8 +108,8 @@ pub(crate) mod sealed { /// Clear update interrupt. /// /// Returns whether the update interrupt flag was set. - fn clear_update_interrupt(&mut self) -> bool { - let regs = Self::regs(); + fn clear_update_interrupt(&self) -> bool { + let regs = Self::regs_core(); let sr = regs.sr().read(); if sr.uif() { regs.sr().modify(|r| { @@ -90,30 +122,20 @@ pub(crate) mod sealed { } /// Enable/disable the update interrupt. - fn enable_update_interrupt(&mut self, enable: bool) { - Self::regs().dier().modify(|r| r.set_uie(enable)); - } - - /// Enable/disable the update dma. - fn enable_update_dma(&mut self, enable: bool) { - Self::regs().dier().modify(|r| r.set_ude(enable)); - } - - /// Get the update dma enable/disable state. - fn get_update_dma_state(&self) -> bool { - Self::regs().dier().read().ude() + fn enable_update_interrupt(&self, enable: bool) { + Self::regs_core().dier().modify(|r| r.set_uie(enable)); } /// Enable/disable autoreload preload. - fn set_autoreload_preload(&mut self, enable: bool) { - Self::regs().cr1().modify(|r| r.set_arpe(enable)); + fn set_autoreload_preload(&self, enable: bool) { + Self::regs_core().cr1().modify(|r| r.set_arpe(enable)); } /// Get the timer frequency. fn get_frequency(&self) -> Hertz { let timer_f = Self::frequency(); - let regs = Self::regs(); + let regs = Self::regs_core(); let arr = regs.arr().read().arr(); let psc = regs.psc().read().psc(); @@ -121,8 +143,72 @@ pub(crate) mod sealed { } } + /// Virtual Basic without CR2 16-bit timer instance. + pub trait BasicNoCr2Instance: CoreInstance { + /// Get access to the Baisc 16bit timer registers. + /// + /// Note: This works even if the timer is more capable, because registers + /// for the less capable timers are a subset. This allows writing a driver + /// for a given set of capabilities, and having it transparently work with + /// more capable timers. + fn regs_basic_no_cr2() -> crate::pac::timer::TimBasicNoCr2; + + /// Enable/disable the update dma. + fn enable_update_dma(&self, enable: bool) { + Self::regs_basic_no_cr2().dier().modify(|r| r.set_ude(enable)); + } + + /// Get the update dma enable/disable state. + fn get_update_dma_state(&self) -> bool { + Self::regs_basic_no_cr2().dier().read().ude() + } + } + + /// Basic 16-bit timer instance. + pub trait BasicInstance: BasicNoCr2Instance { + /// Get access to the Baisc 16bit timer registers. + /// + /// Note: This works even if the timer is more capable, because registers + /// for the less capable timers are a subset. This allows writing a driver + /// for a given set of capabilities, and having it transparently work with + /// more capable timers. + fn regs_basic() -> crate::pac::timer::TimBasic; + } + + /// Gneral-purpose 1 channel 16-bit timer instance. + pub trait GeneralPurpose1ChannelInstance: CoreInstance { + /// Get access to the general purpose 1 channel 16bit timer registers. + /// + /// Note: This works even if the timer is more capable, because registers + /// for the less capable timers are a subset. This allows writing a driver + /// for a given set of capabilities, and having it transparently work with + /// more capable timers. + fn regs_1ch() -> crate::pac::timer::Tim1ch; + + /// Set clock divider. + fn set_clock_division(&self, ckd: vals::Ckd) { + Self::regs_1ch().cr1().modify(|r| r.set_ckd(ckd)); + } + + /// Get max compare value. This depends on the timer frequency and the clock frequency from RCC. + fn get_max_compare_value(&self) -> u16 { + Self::regs_1ch().arr().read().arr() + } + } + + /// Gneral-purpose 1 channel 16-bit timer instance. + pub trait GeneralPurpose2ChannelInstance: GeneralPurpose1ChannelInstance { + /// Get access to the general purpose 2 channel 16bit timer registers. + /// + /// Note: This works even if the timer is more capable, because registers + /// for the less capable timers are a subset. This allows writing a driver + /// for a given set of capabilities, and having it transparently work with + /// more capable timers. + fn regs_2ch() -> crate::pac::timer::Tim2ch; + } + /// Gneral-purpose 16-bit timer instance. - pub trait GeneralPurpose16bitInstance: Basic16bitInstance { + pub trait GeneralPurpose16bitInstance: BasicInstance + GeneralPurpose2ChannelInstance { /// Get access to the general purpose 16bit timer registers. /// /// Note: This works even if the timer is more capable, because registers @@ -132,10 +218,10 @@ pub(crate) mod sealed { fn regs_gp16() -> crate::pac::timer::TimGp16; /// Set counting mode. - fn set_counting_mode(&mut self, mode: CountingMode) { + fn set_counting_mode(&self, mode: CountingMode) { let (cms, dir) = mode.into(); - let timer_enabled = Self::regs().cr1().read().cen(); + let timer_enabled = Self::regs_core().cr1().read().cen(); // Changing from edge aligned to center aligned (and vice versa) is not allowed while the timer is running. // Changing direction is discouraged while the timer is running. assert!(!timer_enabled); @@ -150,62 +236,8 @@ pub(crate) mod sealed { (cr1.cms(), cr1.dir()).into() } - /// Set clock divider. - fn set_clock_division(&mut self, ckd: vals::Ckd) { - Self::regs_gp16().cr1().modify(|r| r.set_ckd(ckd)); - } - } - - /// Gneral-purpose 32-bit timer instance. - pub trait GeneralPurpose32bitInstance: GeneralPurpose16bitInstance { - /// Get access to the general purpose 32bit timer registers. - /// - /// Note: This works even if the timer is more capable, because registers - /// for the less capable timers are a subset. This allows writing a driver - /// for a given set of capabilities, and having it transparently work with - /// more capable timers. - fn regs_gp32() -> crate::pac::timer::TimGp32; - - /// Set timer frequency. - fn set_frequency(&mut self, frequency: Hertz) { - let f = frequency.0; - assert!(f > 0); - let timer_f = Self::frequency().0; - let pclk_ticks_per_timer_period = (timer_f / f) as u64; - let psc: u16 = unwrap!(((pclk_ticks_per_timer_period - 1) / (1 << 32)).try_into()); - let arr: u32 = unwrap!((pclk_ticks_per_timer_period / (psc as u64 + 1)).try_into()); - - let regs = Self::regs_gp32(); - 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)); - } - - /// Get timer frequency. - fn get_frequency(&self) -> Hertz { - let timer_f = Self::frequency(); - - let regs = Self::regs_gp32(); - let arr = regs.arr().read().arr(); - let psc = regs.psc().read().psc(); - - timer_f / arr / (psc + 1) - } - } - - /// Advanced control timer instance. - pub trait AdvancedControlInstance: GeneralPurpose16bitInstance { - /// Get access to the advanced timer registers. - fn regs_advanced() -> crate::pac::timer::TimAdv; - } - - /// Capture/Compare 16-bit timer instance. - pub trait CaptureCompare16bitInstance: GeneralPurpose16bitInstance { /// Set input capture filter. - fn set_input_capture_filter(&mut self, channel: Channel, icf: vals::Icf) { + fn set_input_capture_filter(&self, channel: Channel, icf: vals::FilterValue) { let raw_channel = channel.index(); Self::regs_gp16() .ccmr_input(raw_channel / 2) @@ -213,17 +245,17 @@ pub(crate) mod sealed { } /// Clear input interrupt. - fn clear_input_interrupt(&mut self, channel: Channel) { + fn clear_input_interrupt(&self, channel: Channel) { Self::regs_gp16().sr().modify(|r| r.set_ccif(channel.index(), false)); } /// Enable input interrupt. - fn enable_input_interrupt(&mut self, channel: Channel, enable: bool) { + fn enable_input_interrupt(&self, channel: Channel, enable: bool) { Self::regs_gp16().dier().modify(|r| r.set_ccie(channel.index(), enable)); } /// Set input capture prescaler. - fn set_input_capture_prescaler(&mut self, channel: Channel, factor: u8) { + fn set_input_capture_prescaler(&self, channel: Channel, factor: u8) { let raw_channel = channel.index(); Self::regs_gp16() .ccmr_input(raw_channel / 2) @@ -231,7 +263,7 @@ pub(crate) mod sealed { } /// Set input TI selection. - fn set_input_ti_selection(&mut self, channel: Channel, tisel: InputTISelection) { + fn set_input_ti_selection(&self, channel: Channel, tisel: InputTISelection) { let raw_channel = channel.index(); Self::regs_gp16() .ccmr_input(raw_channel / 2) @@ -239,7 +271,7 @@ pub(crate) mod sealed { } /// Set input capture mode. - fn set_input_capture_mode(&mut self, channel: Channel, mode: InputCaptureMode) { + fn set_input_capture_mode(&self, channel: Channel, mode: InputCaptureMode) { Self::regs_gp16().ccer().modify(|r| match mode { InputCaptureMode::Rising => { r.set_ccnp(channel.index(), false); @@ -256,26 +288,23 @@ pub(crate) mod sealed { }); } - /// Enable timer outputs. - fn enable_outputs(&mut self); - /// Set output compare mode. - fn set_output_compare_mode(&mut self, channel: Channel, mode: OutputCompareMode) { - let r = Self::regs_gp16(); + fn set_output_compare_mode(&self, channel: Channel, mode: OutputCompareMode) { let raw_channel: usize = channel.index(); - r.ccmr_output(raw_channel / 2) + Self::regs_gp16() + .ccmr_output(raw_channel / 2) .modify(|w| w.set_ocm(raw_channel % 2, mode.into())); } /// Set output polarity. - fn set_output_polarity(&mut self, channel: Channel, polarity: OutputPolarity) { + fn set_output_polarity(&self, channel: Channel, polarity: OutputPolarity) { Self::regs_gp16() .ccer() .modify(|w| w.set_ccp(channel.index(), polarity.into())); } /// Enable/disable a channel. - fn enable_channel(&mut self, channel: Channel, enable: bool) { + fn enable_channel(&self, channel: Channel, enable: bool) { Self::regs_gp16().ccer().modify(|w| w.set_cce(channel.index(), enable)); } @@ -285,83 +314,176 @@ pub(crate) mod sealed { } /// Set compare value for a channel. - fn set_compare_value(&mut self, channel: Channel, value: u16) { + fn set_compare_value(&self, channel: Channel, value: u16) { Self::regs_gp16().ccr(channel.index()).modify(|w| w.set_ccr(value)); } /// Get capture value for a channel. - fn get_capture_value(&mut self, channel: Channel) -> u16 { + fn get_capture_value(&self, channel: Channel) -> u16 { Self::regs_gp16().ccr(channel.index()).read().ccr() } - /// Get max compare value. This depends on the timer frequency and the clock frequency from RCC. - fn get_max_compare_value(&self) -> u16 { - Self::regs_gp16().arr().read().arr() - } - /// Get compare value for a channel. fn get_compare_value(&self, channel: Channel) -> u16 { Self::regs_gp16().ccr(channel.index()).read().ccr() } /// Set output compare preload. - fn set_output_compare_preload(&mut self, channel: Channel, preload: bool) { + fn set_output_compare_preload(&self, channel: Channel, preload: bool) { let channel_index = channel.index(); Self::regs_gp16() .ccmr_output(channel_index / 2) .modify(|w| w.set_ocpe(channel_index % 2, preload)); } + + /// Get capture compare DMA selection + fn get_cc_dma_selection(&self) -> super::vals::Ccds { + Self::regs_gp16().cr2().read().ccds() + } + + /// Set capture compare DMA selection + fn set_cc_dma_selection(&self, ccds: super::vals::Ccds) { + Self::regs_gp16().cr2().modify(|w| w.set_ccds(ccds)) + } + + /// Get capture compare DMA enable state + fn get_cc_dma_enable_state(&self, channel: Channel) -> bool { + Self::regs_gp16().dier().read().ccde(channel.index()) + } + + /// Set capture compare DMA enable state + fn set_cc_dma_enable_state(&self, channel: Channel, ccde: bool) { + Self::regs_gp16().dier().modify(|w| w.set_ccde(channel.index(), ccde)) + } } - /// Capture/Compare 16-bit timer instance with complementary pin support. - pub trait ComplementaryCaptureCompare16bitInstance: CaptureCompare16bitInstance + AdvancedControlInstance { + #[cfg(not(stm32l0))] + /// Gneral-purpose 32-bit timer instance. + pub trait GeneralPurpose32bitInstance: GeneralPurpose16bitInstance { + /// Get access to the general purpose 32bit timer registers. + /// + /// Note: This works even if the timer is more capable, because registers + /// for the less capable timers are a subset. This allows writing a driver + /// for a given set of capabilities, and having it transparently work with + /// more capable timers. + fn regs_gp32() -> crate::pac::timer::TimGp32; + + /// Set timer frequency. + fn set_frequency(&self, frequency: Hertz) { + let f = frequency.0; + assert!(f > 0); + let timer_f = Self::frequency().0; + let pclk_ticks_per_timer_period = (timer_f / f) as u64; + let psc: u16 = unwrap!(((pclk_ticks_per_timer_period - 1) / (1 << 32)).try_into()); + let arr: u32 = unwrap!((pclk_ticks_per_timer_period / (psc as u64 + 1)).try_into()); + + let regs = Self::regs_gp32(); + regs.psc().write(|r| r.set_psc(psc)); + regs.arr().write_value(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)); + } + + /// Get timer frequency. + fn get_frequency(&self) -> Hertz { + let timer_f = Self::frequency(); + + let regs = Self::regs_gp32(); + let arr = regs.arr().read(); + let psc = regs.psc().read().psc(); + + timer_f / arr / (psc + 1) + } + + /// Set comapre value for a channel. + fn set_compare_value(&self, channel: Channel, value: u32) { + Self::regs_gp32().ccr(channel.index()).write_value(value); + } + + /// Get capture value for a channel. + fn get_capture_value(&self, channel: Channel) -> u32 { + Self::regs_gp32().ccr(channel.index()).read() + } + + /// Get max compare value. This depends on the timer frequency and the clock frequency from RCC. + fn get_max_compare_value(&self) -> u32 { + Self::regs_gp32().arr().read() + } + + /// Get compare value for a channel. + fn get_compare_value(&self, channel: Channel) -> u32 { + Self::regs_gp32().ccr(channel.index()).read() + } + } + + #[cfg(not(stm32l0))] + /// Gneral-purpose 1 channel with one complementary 16-bit timer instance. + pub trait GeneralPurpose1ChannelComplementaryInstance: BasicNoCr2Instance + GeneralPurpose1ChannelInstance { + /// Get access to the general purpose 1 channel with one complementary 16bit timer registers. + /// + /// Note: This works even if the timer is more capable, because registers + /// for the less capable timers are a subset. This allows writing a driver + /// for a given set of capabilities, and having it transparently work with + /// more capable timers. + fn regs_1ch_cmp() -> crate::pac::timer::Tim1chCmp; + + /// Set clock divider for the dead time. + fn set_dead_time_clock_division(&self, value: vals::Ckd) { + Self::regs_1ch_cmp().cr1().modify(|w| w.set_ckd(value)); + } + + /// Set dead time, as a fraction of the max duty value. + fn set_dead_time_value(&self, value: u8) { + Self::regs_1ch_cmp().bdtr().modify(|w| w.set_dtg(value)); + } + + /// Enable timer outputs. + fn enable_outputs(&self) { + Self::regs_1ch_cmp().bdtr().modify(|w| w.set_moe(true)); + } + } + + #[cfg(not(stm32l0))] + /// Gneral-purpose 2 channel with one complementary 16-bit timer instance. + pub trait GeneralPurpose2ChannelComplementaryInstance: + BasicInstance + GeneralPurpose2ChannelInstance + GeneralPurpose1ChannelComplementaryInstance + { + /// Get access to the general purpose 2 channel with one complementary 16bit timer registers. + /// + /// Note: This works even if the timer is more capable, because registers + /// for the less capable timers are a subset. This allows writing a driver + /// for a given set of capabilities, and having it transparently work with + /// more capable timers. + fn regs_2ch_cmp() -> crate::pac::timer::Tim2chCmp; + } + + #[cfg(not(stm32l0))] + /// Advanced control timer instance. + pub trait AdvancedControlInstance: + GeneralPurpose2ChannelComplementaryInstance + GeneralPurpose16bitInstance + { + /// Capture compare interrupt for this timer. + type CaptureCompareInterrupt: interrupt::typelevel::Interrupt; + + /// Get access to the advanced timer registers. + fn regs_advanced() -> crate::pac::timer::TimAdv; + /// Set complementary output polarity. - fn set_complementary_output_polarity(&mut self, channel: Channel, polarity: OutputPolarity) { + fn set_complementary_output_polarity(&self, channel: Channel, polarity: OutputPolarity) { Self::regs_advanced() .ccer() .modify(|w| w.set_ccnp(channel.index(), polarity.into())); } - /// Set clock divider for the dead time. - fn set_dead_time_clock_division(&mut self, value: vals::Ckd) { - Self::regs_advanced().cr1().modify(|w| w.set_ckd(value)); - } - - /// Set dead time, as a fraction of the max duty value. - fn set_dead_time_value(&mut self, value: u8) { - Self::regs_advanced().bdtr().modify(|w| w.set_dtg(value)); - } - /// Enable/disable a complementary channel. - fn enable_complementary_channel(&mut self, channel: Channel, enable: bool) { + fn enable_complementary_channel(&self, channel: Channel, enable: bool) { Self::regs_advanced() .ccer() .modify(|w| w.set_ccne(channel.index(), enable)); } } - - /// Capture/Compare 32-bit timer instance. - pub trait CaptureCompare32bitInstance: GeneralPurpose32bitInstance + CaptureCompare16bitInstance { - /// Set comapre value for a channel. - fn set_compare_value(&mut self, channel: Channel, value: u32) { - Self::regs_gp32().ccr(channel.index()).modify(|w| w.set_ccr(value)); - } - - /// Get capture value for a channel. - fn get_capture_value(&mut self, channel: Channel) -> u32 { - Self::regs_gp32().ccr(channel.index()).read().ccr() - } - - /// Get max compare value. This depends on the timer frequency and the clock frequency from RCC. - fn get_max_compare_value(&self) -> u32 { - Self::regs_gp32().arr().read().arr() - } - - /// Get compare value for a channel. - fn get_compare_value(&self, channel: Channel) -> u32 { - Self::regs_gp32().ccr(channel.index()).read().ccr() - } - } } /// Timer channel. @@ -450,20 +572,17 @@ pub enum CountingMode { impl CountingMode { /// Return whether this mode is edge-aligned (up or down). pub fn is_edge_aligned(&self) -> bool { - match self { - CountingMode::EdgeAlignedUp | CountingMode::EdgeAlignedDown => true, - _ => false, - } + matches!(self, CountingMode::EdgeAlignedUp | CountingMode::EdgeAlignedDown) } /// Return whether this mode is center-aligned. pub fn is_center_aligned(&self) -> bool { - match self { + matches!( + self, CountingMode::CenterAlignedDownInterrupts - | CountingMode::CenterAlignedUpInterrupts - | CountingMode::CenterAlignedBothInterrupts => true, - _ => false, - } + | CountingMode::CenterAlignedUpInterrupts + | CountingMode::CenterAlignedBothInterrupts + ) } } @@ -555,61 +674,92 @@ impl From<OutputPolarity> for bool { } /// Basic 16-bit timer instance. -pub trait Basic16bitInstance: sealed::Basic16bitInstance + 'static {} +pub trait BasicInstance: sealed::BasicInstance + sealed::BasicNoCr2Instance + sealed::CoreInstance + 'static {} -/// Gneral-purpose 16-bit timer instance. -pub trait GeneralPurpose16bitInstance: sealed::GeneralPurpose16bitInstance + Basic16bitInstance + 'static {} - -/// Gneral-purpose 32-bit timer instance. -pub trait GeneralPurpose32bitInstance: - sealed::GeneralPurpose32bitInstance + GeneralPurpose16bitInstance + 'static -{ -} - -/// Advanced control timer instance. -pub trait AdvancedControlInstance: sealed::AdvancedControlInstance + GeneralPurpose16bitInstance + 'static {} - -/// Capture/Compare 16-bit timer instance. +// It's just a General-purpose 16-bit timer instance. +/// Capture Compare timer instance. pub trait CaptureCompare16bitInstance: - sealed::CaptureCompare16bitInstance + GeneralPurpose16bitInstance + 'static + BasicInstance + + sealed::GeneralPurpose2ChannelInstance + + sealed::GeneralPurpose1ChannelInstance + + sealed::GeneralPurpose16bitInstance + + 'static { } -/// Capture/Compare 16-bit timer instance with complementary pin support. -pub trait ComplementaryCaptureCompare16bitInstance: - sealed::ComplementaryCaptureCompare16bitInstance + CaptureCompare16bitInstance + AdvancedControlInstance + 'static -{ -} - -/// Capture/Compare 32-bit timer instance. +#[cfg(not(stm32l0))] +// It's just a General-purpose 32-bit timer instance. +/// Capture Compare 32-bit timer instance. pub trait CaptureCompare32bitInstance: - sealed::CaptureCompare32bitInstance + CaptureCompare16bitInstance + GeneralPurpose32bitInstance + 'static + CaptureCompare16bitInstance + sealed::GeneralPurpose32bitInstance + 'static +{ +} + +#[cfg(not(stm32l0))] +// It's just a Advanced Control timer instance. +/// Complementary Capture Compare 32-bit timer instance. +pub trait ComplementaryCaptureCompare16bitInstance: + CaptureCompare16bitInstance + + sealed::GeneralPurpose1ChannelComplementaryInstance + + sealed::GeneralPurpose2ChannelComplementaryInstance + + sealed::AdvancedControlInstance + + 'static { } pin_trait!(Channel1Pin, CaptureCompare16bitInstance); -pin_trait!(Channel1ComplementaryPin, CaptureCompare16bitInstance); pin_trait!(Channel2Pin, CaptureCompare16bitInstance); -pin_trait!(Channel2ComplementaryPin, CaptureCompare16bitInstance); pin_trait!(Channel3Pin, CaptureCompare16bitInstance); -pin_trait!(Channel3ComplementaryPin, CaptureCompare16bitInstance); pin_trait!(Channel4Pin, CaptureCompare16bitInstance); -pin_trait!(Channel4ComplementaryPin, CaptureCompare16bitInstance); pin_trait!(ExternalTriggerPin, CaptureCompare16bitInstance); -pin_trait!(BreakInputPin, CaptureCompare16bitInstance); -pin_trait!(BreakInputComparator1Pin, CaptureCompare16bitInstance); -pin_trait!(BreakInputComparator2Pin, CaptureCompare16bitInstance); -pin_trait!(BreakInput2Pin, CaptureCompare16bitInstance); -pin_trait!(BreakInput2Comparator1Pin, CaptureCompare16bitInstance); -pin_trait!(BreakInput2Comparator2Pin, CaptureCompare16bitInstance); + +cfg_if::cfg_if! { + if #[cfg(not(stm32l0))] { + pin_trait!(Channel1ComplementaryPin, ComplementaryCaptureCompare16bitInstance); + pin_trait!(Channel2ComplementaryPin, ComplementaryCaptureCompare16bitInstance); + pin_trait!(Channel3ComplementaryPin, ComplementaryCaptureCompare16bitInstance); + pin_trait!(Channel4ComplementaryPin, ComplementaryCaptureCompare16bitInstance); + + pin_trait!(BreakInputPin, ComplementaryCaptureCompare16bitInstance); + pin_trait!(BreakInput2Pin, ComplementaryCaptureCompare16bitInstance); + + pin_trait!(BreakInputComparator1Pin, ComplementaryCaptureCompare16bitInstance); + pin_trait!(BreakInputComparator2Pin, ComplementaryCaptureCompare16bitInstance); + + pin_trait!(BreakInput2Comparator1Pin, ComplementaryCaptureCompare16bitInstance); + pin_trait!(BreakInput2Comparator2Pin, ComplementaryCaptureCompare16bitInstance); + } +} #[allow(unused)] -macro_rules! impl_basic_16bit_timer { +macro_rules! impl_core_timer { ($inst:ident, $irq:ident) => { - impl sealed::Basic16bitInstance for crate::peripherals::$inst { + impl sealed::CoreInstance for crate::peripherals::$inst { type Interrupt = crate::interrupt::typelevel::$irq; - fn regs() -> crate::pac::timer::TimBasic { + fn regs_core() -> crate::pac::timer::TimCore { + unsafe { crate::pac::timer::TimCore::from_ptr(crate::pac::$inst.as_ptr()) } + } + } + }; +} + +#[allow(unused)] +macro_rules! impl_basic_no_cr2_timer { + ($inst:ident) => { + impl sealed::BasicNoCr2Instance for crate::peripherals::$inst { + fn regs_basic_no_cr2() -> crate::pac::timer::TimBasicNoCr2 { + unsafe { crate::pac::timer::TimBasicNoCr2::from_ptr(crate::pac::$inst.as_ptr()) } + } + } + }; +} + +#[allow(unused)] +macro_rules! impl_basic_timer { + ($inst:ident) => { + impl sealed::BasicInstance for crate::peripherals::$inst { + fn regs_basic() -> crate::pac::timer::TimBasic { unsafe { crate::pac::timer::TimBasic::from_ptr(crate::pac::$inst.as_ptr()) } } } @@ -617,7 +767,40 @@ macro_rules! impl_basic_16bit_timer { } #[allow(unused)] -macro_rules! impl_32bit_timer { +macro_rules! impl_1ch_timer { + ($inst:ident) => { + impl sealed::GeneralPurpose1ChannelInstance for crate::peripherals::$inst { + fn regs_1ch() -> crate::pac::timer::Tim1ch { + unsafe { crate::pac::timer::Tim1ch::from_ptr(crate::pac::$inst.as_ptr()) } + } + } + }; +} + +#[allow(unused)] +macro_rules! impl_2ch_timer { + ($inst:ident) => { + impl sealed::GeneralPurpose2ChannelInstance for crate::peripherals::$inst { + fn regs_2ch() -> crate::pac::timer::Tim2ch { + unsafe { crate::pac::timer::Tim2ch::from_ptr(crate::pac::$inst.as_ptr()) } + } + } + }; +} + +#[allow(unused)] +macro_rules! impl_gp16_timer { + ($inst:ident) => { + impl sealed::GeneralPurpose16bitInstance for crate::peripherals::$inst { + fn regs_gp16() -> crate::pac::timer::TimGp16 { + unsafe { crate::pac::timer::TimGp16::from_ptr(crate::pac::$inst.as_ptr()) } + } + } + }; +} + +#[allow(unused)] +macro_rules! impl_gp32_timer { ($inst:ident) => { impl sealed::GeneralPurpose32bitInstance for crate::peripherals::$inst { fn regs_gp32() -> crate::pac::timer::TimGp32 { @@ -628,80 +811,154 @@ macro_rules! impl_32bit_timer { } #[allow(unused)] -macro_rules! impl_compare_capable_16bit { +macro_rules! impl_1ch_cmp_timer { ($inst:ident) => { - impl sealed::CaptureCompare16bitInstance for crate::peripherals::$inst { - fn enable_outputs(&mut self) {} + impl sealed::GeneralPurpose1ChannelComplementaryInstance for crate::peripherals::$inst { + fn regs_1ch_cmp() -> crate::pac::timer::Tim1chCmp { + unsafe { crate::pac::timer::Tim1chCmp::from_ptr(crate::pac::$inst.as_ptr()) } + } + } + }; +} + +#[allow(unused)] +macro_rules! impl_2ch_cmp_timer { + ($inst:ident) => { + impl sealed::GeneralPurpose2ChannelComplementaryInstance for crate::peripherals::$inst { + fn regs_2ch_cmp() -> crate::pac::timer::Tim2chCmp { + unsafe { crate::pac::timer::Tim2chCmp::from_ptr(crate::pac::$inst.as_ptr()) } + } + } + }; +} + +#[allow(unused)] +macro_rules! impl_adv_timer { + ($inst:ident, $irq:ident) => { + impl sealed::AdvancedControlInstance for crate::peripherals::$inst { + type CaptureCompareInterrupt = crate::interrupt::typelevel::$irq; + + fn regs_advanced() -> crate::pac::timer::TimAdv { + unsafe { crate::pac::timer::TimAdv::from_ptr(crate::pac::$inst.as_ptr()) } + } } }; } foreach_interrupt! { - ($inst:ident, timer, TIM_BASIC, UP, $irq:ident) => { - impl_basic_16bit_timer!($inst, $irq); - impl Basic16bitInstance for crate::peripherals::$inst {} - }; - ($inst:ident, timer, TIM_GP16, UP, $irq:ident) => { - impl_basic_16bit_timer!($inst, $irq); - impl_compare_capable_16bit!($inst); - impl Basic16bitInstance for crate::peripherals::$inst {} - impl GeneralPurpose16bitInstance for crate::peripherals::$inst {} - impl CaptureCompare16bitInstance for crate::peripherals::$inst {} - impl sealed::GeneralPurpose16bitInstance for crate::peripherals::$inst { - fn regs_gp16() -> crate::pac::timer::TimGp16 { - crate::pac::$inst - } - } + ($inst:ident, timer, TIM_BASIC, UP, $irq:ident) => { + impl_core_timer!($inst, $irq); + impl_basic_no_cr2_timer!($inst); + impl_basic_timer!($inst); + impl BasicInstance for crate::peripherals::$inst {} + }; + + ($inst:ident, timer, TIM_1CH, UP, $irq:ident) => { + impl_core_timer!($inst, $irq); + impl_basic_no_cr2_timer!($inst); + impl_basic_timer!($inst); + impl_1ch_timer!($inst); + impl_2ch_timer!($inst); + impl_gp16_timer!($inst); + impl BasicInstance for crate::peripherals::$inst {} + impl CaptureCompare16bitInstance for crate::peripherals::$inst {} + }; + + + ($inst:ident, timer, TIM_2CH, UP, $irq:ident) => { + impl_core_timer!($inst, $irq); + impl_basic_no_cr2_timer!($inst); + impl_basic_timer!($inst); + impl_1ch_timer!($inst); + impl_2ch_timer!($inst); + impl_gp16_timer!($inst); + impl BasicInstance for crate::peripherals::$inst {} + impl CaptureCompare16bitInstance for crate::peripherals::$inst {} + }; + + ($inst:ident, timer, TIM_GP16, UP, $irq:ident) => { + impl_core_timer!($inst, $irq); + impl_basic_no_cr2_timer!($inst); + impl_basic_timer!($inst); + impl_1ch_timer!($inst); + impl_2ch_timer!($inst); + impl_gp16_timer!($inst); + impl BasicInstance for crate::peripherals::$inst {} + impl CaptureCompare16bitInstance for crate::peripherals::$inst {} }; ($inst:ident, timer, TIM_GP32, UP, $irq:ident) => { - impl_basic_16bit_timer!($inst, $irq); - impl_32bit_timer!($inst); - impl_compare_capable_16bit!($inst); - impl Basic16bitInstance for crate::peripherals::$inst {} + impl_core_timer!($inst, $irq); + impl_basic_no_cr2_timer!($inst); + impl_basic_timer!($inst); + impl_1ch_timer!($inst); + impl_2ch_timer!($inst); + impl_gp16_timer!($inst); + impl_gp32_timer!($inst); + impl BasicInstance for crate::peripherals::$inst {} impl CaptureCompare16bitInstance for crate::peripherals::$inst {} impl CaptureCompare32bitInstance for crate::peripherals::$inst {} - impl GeneralPurpose16bitInstance for crate::peripherals::$inst {} - impl GeneralPurpose32bitInstance for crate::peripherals::$inst {} - impl sealed::CaptureCompare32bitInstance for crate::peripherals::$inst {} - - impl sealed::GeneralPurpose16bitInstance for crate::peripherals::$inst { - fn regs_gp16() -> crate::pac::timer::TimGp16 { - unsafe { crate::pac::timer::TimGp16::from_ptr(crate::pac::$inst.as_ptr()) } - } - } }; - ($inst:ident, timer, TIM_ADV, UP, $irq:ident) => { - impl_basic_16bit_timer!($inst, $irq); - - impl Basic16bitInstance for crate::peripherals::$inst {} - impl GeneralPurpose16bitInstance for crate::peripherals::$inst {} + ($inst:ident, timer, TIM_1CH_CMP, UP, $irq:ident) => { + impl_core_timer!($inst, $irq); + impl_basic_no_cr2_timer!($inst); + impl_basic_timer!($inst); + impl_1ch_timer!($inst); + impl_2ch_timer!($inst); + impl_gp16_timer!($inst); + impl_1ch_cmp_timer!($inst); + impl_2ch_cmp_timer!($inst); + impl BasicInstance for crate::peripherals::$inst {} impl CaptureCompare16bitInstance for crate::peripherals::$inst {} impl ComplementaryCaptureCompare16bitInstance for crate::peripherals::$inst {} - impl AdvancedControlInstance for crate::peripherals::$inst {} - impl sealed::CaptureCompare16bitInstance for crate::peripherals::$inst { - fn enable_outputs(&mut self) { - use crate::timer::sealed::AdvancedControlInstance; - let r = Self::regs_advanced(); - r.bdtr().modify(|w| w.set_moe(true)); - } - } - impl sealed::ComplementaryCaptureCompare16bitInstance for crate::peripherals::$inst {} - impl sealed::GeneralPurpose16bitInstance for crate::peripherals::$inst { - fn regs_gp16() -> crate::pac::timer::TimGp16 { - unsafe { crate::pac::timer::TimGp16::from_ptr(crate::pac::$inst.as_ptr()) } - } - } + }; + ($inst:ident, timer, TIM_1CH_CMP, CC, $irq:ident) => { + impl_adv_timer!($inst, $irq); + }; - impl sealed::AdvancedControlInstance for crate::peripherals::$inst { - fn regs_advanced() -> crate::pac::timer::TimAdv { - crate::pac::$inst - } - } + + ($inst:ident, timer, TIM_2CH_CMP, UP, $irq:ident) => { + impl_core_timer!($inst, $irq); + impl_basic_no_cr2_timer!($inst); + impl_basic_timer!($inst); + impl_1ch_timer!($inst); + impl_2ch_timer!($inst); + impl_gp16_timer!($inst); + impl_1ch_cmp_timer!($inst); + impl_2ch_cmp_timer!($inst); + impl BasicInstance for crate::peripherals::$inst {} + impl CaptureCompare16bitInstance for crate::peripherals::$inst {} + impl ComplementaryCaptureCompare16bitInstance for crate::peripherals::$inst {} + }; + ($inst:ident, timer, TIM_2CH_CMP, CC, $irq:ident) => { + impl_adv_timer!($inst, $irq); + }; + + + ($inst:ident, timer, TIM_ADV, UP, $irq:ident) => { + impl_core_timer!($inst, $irq); + impl_basic_no_cr2_timer!($inst); + impl_basic_timer!($inst); + impl_1ch_timer!($inst); + impl_2ch_timer!($inst); + impl_gp16_timer!($inst); + impl_1ch_cmp_timer!($inst); + impl_2ch_cmp_timer!($inst); + impl BasicInstance for crate::peripherals::$inst {} + impl CaptureCompare16bitInstance for crate::peripherals::$inst {} + impl ComplementaryCaptureCompare16bitInstance for crate::peripherals::$inst {} + }; + ($inst:ident, timer, TIM_ADV, CC, $irq:ident) => { + impl_adv_timer!($inst, $irq); }; } // Update Event trigger DMA for every timer -dma_trait!(UpDma, Basic16bitInstance); +dma_trait!(UpDma, BasicInstance); + +dma_trait!(Ch1Dma, CaptureCompare16bitInstance); +dma_trait!(Ch2Dma, CaptureCompare16bitInstance); +dma_trait!(Ch3Dma, CaptureCompare16bitInstance); +dma_trait!(Ch4Dma, CaptureCompare16bitInstance); diff --git a/embassy-stm32/src/timer/simple_pwm.rs b/embassy-stm32/src/timer/simple_pwm.rs index 80f10424c..1acba504e 100644 --- a/embassy-stm32/src/timer/simple_pwm.rs +++ b/embassy-stm32/src/timer/simple_pwm.rs @@ -84,13 +84,12 @@ impl<'d, T: CaptureCompare16bitInstance> SimplePwm<'d, T> { this.set_frequency(freq); this.inner.start(); - this.inner.enable_outputs(); - [Channel::Ch1, Channel::Ch2, Channel::Ch3, Channel::Ch4] .iter() .for_each(|&channel| { this.inner.set_output_compare_mode(channel, OutputCompareMode::PwmMode1); - this.inner.set_output_compare_preload(channel, true) + + this.inner.set_output_compare_preload(channel, true); }); this @@ -151,11 +150,16 @@ impl<'d, T: CaptureCompare16bitInstance> SimplePwm<'d, T> { self.inner.set_output_polarity(channel, polarity); } + /// Set the output compare mode for a given channel. + pub fn set_output_compare_mode(&mut self, channel: Channel, mode: OutputCompareMode) { + self.inner.set_output_compare_mode(channel, mode); + } + /// Generate a sequence of PWM waveform /// /// Note: /// you will need to provide corresponding TIMx_UP DMA channel to use this method. - pub async fn gen_waveform( + pub async fn waveform_up( &mut self, dma: impl Peripheral<P = impl super::UpDma<T>>, channel: Channel, @@ -197,7 +201,7 @@ impl<'d, T: CaptureCompare16bitInstance> SimplePwm<'d, T> { &mut dma, req, duty, - T::regs_gp16().ccr(channel.index()).as_ptr() as *mut _, + T::regs_1ch().ccr(channel.index()).as_ptr() as *mut _, dma_transfer_option, ) .await @@ -221,6 +225,95 @@ impl<'d, T: CaptureCompare16bitInstance> SimplePwm<'d, T> { } } +macro_rules! impl_waveform_chx { + ($fn_name:ident, $dma_ch:ident, $cc_ch:ident) => { + impl<'d, T: CaptureCompare16bitInstance> SimplePwm<'d, T> { + /// Generate a sequence of PWM waveform + /// + /// Note: + /// you will need to provide corresponding TIMx_CHy DMA channel to use this method. + pub async fn $fn_name(&mut self, dma: impl Peripheral<P = impl super::$dma_ch<T>>, duty: &[u16]) { + use super::vals::Ccds; + + assert!(duty.iter().all(|v| *v <= self.get_max_duty())); + + into_ref!(dma); + + #[allow(clippy::let_unit_value)] // eg. stm32f334 + let req = dma.request(); + + let cc_channel = super::Channel::$cc_ch; + + let original_duty_state = self.get_duty(cc_channel); + let original_enable_state = self.is_enabled(cc_channel); + let original_cc_dma_on_update = self.inner.get_cc_dma_selection() == Ccds::ONUPDATE; + let original_cc_dma_enabled = self.inner.get_cc_dma_enable_state(cc_channel); + + // redirect CC DMA request onto Update Event + if !original_cc_dma_on_update { + self.inner.set_cc_dma_selection(Ccds::ONUPDATE) + } + + if !original_cc_dma_enabled { + self.inner.set_cc_dma_enable_state(cc_channel, true); + } + + if !original_enable_state { + self.enable(cc_channel); + } + + unsafe { + #[cfg(not(any(bdma, gpdma)))] + use crate::dma::{Burst, FifoThreshold}; + use crate::dma::{Transfer, TransferOptions}; + + let dma_transfer_option = TransferOptions { + #[cfg(not(any(bdma, gpdma)))] + fifo_threshold: Some(FifoThreshold::Full), + #[cfg(not(any(bdma, gpdma)))] + mburst: Burst::Incr8, + ..Default::default() + }; + + Transfer::new_write( + &mut dma, + req, + duty, + T::regs_gp16().ccr(cc_channel.index()).as_ptr() as *mut _, + dma_transfer_option, + ) + .await + }; + + // restore output compare state + if !original_enable_state { + self.disable(cc_channel); + } + + self.set_duty(cc_channel, original_duty_state); + + // Since DMA is closed before timer Capture Compare Event trigger DMA is turn off, + // this can almost always trigger a DMA FIFO error. + // + // optional TODO: + // clean FEIF after disable UDE + if !original_cc_dma_enabled { + self.inner.set_cc_dma_enable_state(cc_channel, false); + } + + if !original_cc_dma_on_update { + self.inner.set_cc_dma_selection(Ccds::ONCOMPARE) + } + } + } + }; +} + +impl_waveform_chx!(waveform_ch1, Ch1Dma, Ch1); +impl_waveform_chx!(waveform_ch2, Ch2Dma, Ch2); +impl_waveform_chx!(waveform_ch3, Ch3Dma, Ch3); +impl_waveform_chx!(waveform_ch4, Ch4Dma, Ch4); + impl<'d, T: CaptureCompare16bitInstance> embedded_hal_02::Pwm for SimplePwm<'d, T> { type Channel = Channel; type Time = Hertz; @@ -235,7 +328,7 @@ impl<'d, T: CaptureCompare16bitInstance> embedded_hal_02::Pwm for SimplePwm<'d, } fn get_period(&self) -> Self::Time { - self.inner.get_frequency().into() + self.inner.get_frequency() } fn get_duty(&self, channel: Self::Channel) -> Self::Duty { diff --git a/embassy-stm32/src/usart/buffered.rs b/embassy-stm32/src/usart/buffered.rs index 962547bd7..c11e3382f 100644 --- a/embassy-stm32/src/usart/buffered.rs +++ b/embassy-stm32/src/usart/buffered.rs @@ -1,5 +1,6 @@ use core::future::poll_fn; use core::slice; +use core::sync::atomic::{AtomicBool, Ordering}; use core::task::Poll; use embassy_hal_internal::atomic_ring_buffer::RingBuffer; @@ -46,8 +47,10 @@ impl<T: BasicInstance> interrupt::typelevel::Handler<T::Interrupt> for Interrupt 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); + if let Some(byte) = dr { + buf[0] = byte; + rx_writer.push_done(1); + } } else { // FIXME: Should we disable any further RX interrupts when the buffer becomes full. } @@ -61,6 +64,22 @@ impl<T: BasicInstance> interrupt::typelevel::Handler<T::Interrupt> for Interrupt state.rx_waker.wake(); } + // With `usart_v4` hardware FIFO is enabled and Transmission complete (TC) + // indicates that all bytes are pushed out from the FIFO. + // For other usart variants it shows that last byte from the buffer was just sent. + if sr_val.tc() { + // For others it is cleared above with `clear_interrupt_flags`. + #[cfg(any(usart_v1, usart_v2))] + sr(r).modify(|w| w.set_tc(false)); + + r.cr1().modify(|w| { + w.set_tcie(false); + }); + + state.tx_done.store(true, Ordering::Release); + state.tx_waker.wake(); + } + // TX if sr(r).read().txe() { let mut tx_reader = state.tx_buf.reader(); @@ -69,11 +88,18 @@ impl<T: BasicInstance> interrupt::typelevel::Handler<T::Interrupt> for Interrupt r.cr1().modify(|w| { w.set_txeie(true); }); + + // Enable transmission complete interrupt when last byte is going to be sent out. + if buf.len() == 1 { + r.cr1().modify(|w| { + w.set_tcie(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 + // Disable interrupt until we have something to transmit again. r.cr1().modify(|w| { w.set_txeie(false); }); @@ -82,23 +108,27 @@ impl<T: BasicInstance> interrupt::typelevel::Handler<T::Interrupt> for Interrupt } } -/// Buffered UART State -pub struct State { - rx_waker: AtomicWaker, - rx_buf: RingBuffer, +pub(crate) use sealed::State; +pub(crate) mod sealed { + use super::*; + pub struct State { + pub(crate) rx_waker: AtomicWaker, + pub(crate) rx_buf: RingBuffer, + pub(crate) tx_waker: AtomicWaker, + pub(crate) tx_buf: RingBuffer, + pub(crate) tx_done: AtomicBool, + } - tx_waker: AtomicWaker, - tx_buf: RingBuffer, -} - -impl State { - /// Create new state - pub const fn new() -> Self { - Self { - rx_buf: RingBuffer::new(), - tx_buf: RingBuffer::new(), - rx_waker: AtomicWaker::new(), - tx_waker: AtomicWaker::new(), + impl State { + /// Create new state + pub const fn new() -> Self { + Self { + rx_buf: RingBuffer::new(), + tx_buf: RingBuffer::new(), + rx_waker: AtomicWaker::new(), + tx_waker: AtomicWaker::new(), + tx_done: AtomicBool::new(true), + } } } } @@ -110,11 +140,15 @@ pub struct BufferedUart<'d, T: BasicInstance> { } /// Tx-only buffered UART +/// +/// Created with [BufferedUart::split] pub struct BufferedUartTx<'d, T: BasicInstance> { phantom: PhantomData<&'d mut T>, } /// Rx-only buffered UART +/// +/// Created with [BufferedUart::split] pub struct BufferedUartRx<'d, T: BasicInstance> { phantom: PhantomData<&'d mut T>, } @@ -364,6 +398,8 @@ impl<'d, T: BasicInstance> BufferedUartTx<'d, T> { async fn write(&self, buf: &[u8]) -> Result<usize, Error> { poll_fn(move |cx| { let state = T::buffered_state(); + state.tx_done.store(false, Ordering::Release); + let empty = state.tx_buf.is_empty(); let mut tx_writer = unsafe { state.tx_buf.writer() }; @@ -389,7 +425,8 @@ impl<'d, T: BasicInstance> BufferedUartTx<'d, T> { async fn flush(&self) -> Result<(), Error> { poll_fn(move |cx| { let state = T::buffered_state(); - if !state.tx_buf.is_empty() { + + if !state.tx_done.load(Ordering::Acquire) { state.tx_waker.register(cx.waker()); return Poll::Pending; } diff --git a/embassy-stm32/src/usart/ringbuffered.rs b/embassy-stm32/src/usart/ringbuffered.rs index 4391bfef7..b852f0176 100644 --- a/embassy-stm32/src/usart/ringbuffered.rs +++ b/embassy-stm32/src/usart/ringbuffered.rs @@ -7,17 +7,19 @@ use embassy_embedded_hal::SetConfig; use embassy_hal_internal::PeripheralRef; use futures::future::{select, Either}; -use super::{clear_interrupt_flags, rdr, reconfigure, sr, BasicInstance, Config, ConfigError, Error, RxDma, UartRx}; +use super::{clear_interrupt_flags, rdr, reconfigure, sr, BasicInstance, Config, ConfigError, Error, UartRx}; use crate::dma::ReadableRingBuffer; use crate::usart::{Regs, Sr}; /// Rx-only Ring-buffered UART Driver -pub struct RingBufferedUartRx<'d, T: BasicInstance, RxDma: super::RxDma<T>> { +/// +/// Created with [UartRx::into_ring_buffered] +pub struct RingBufferedUartRx<'d, T: BasicInstance> { _peri: PeripheralRef<'d, T>, - ring_buf: ReadableRingBuffer<'d, RxDma, u8>, + ring_buf: ReadableRingBuffer<'d, u8>, } -impl<'d, T: BasicInstance, RxDma: super::RxDma<T>> SetConfig for RingBufferedUartRx<'d, T, RxDma> { +impl<'d, T: BasicInstance> SetConfig for RingBufferedUartRx<'d, T> { type Config = Config; type ConfigError = ConfigError; @@ -30,7 +32,7 @@ impl<'d, T: BasicInstance, RxDma: super::RxDma<T>> UartRx<'d, T, RxDma> { /// Turn the `UartRx` into a buffered uart which can continously receive in the background /// without the possibility of losing bytes. The `dma_buf` is a buffer registered to the /// DMA controller, and must be large enough to prevent overflows. - pub fn into_ring_buffered(self, dma_buf: &'d mut [u8]) -> RingBufferedUartRx<'d, T, RxDma> { + pub fn into_ring_buffered(self, dma_buf: &'d mut [u8]) -> RingBufferedUartRx<'d, T> { assert!(!dma_buf.is_empty() && dma_buf.len() <= 0xFFFF); let request = self.rx_dma.request(); @@ -49,7 +51,7 @@ impl<'d, T: BasicInstance, RxDma: super::RxDma<T>> UartRx<'d, T, RxDma> { } } -impl<'d, T: BasicInstance, RxDma: super::RxDma<T>> RingBufferedUartRx<'d, T, RxDma> { +impl<'d, T: BasicInstance> RingBufferedUartRx<'d, T> { /// Clear the ring buffer and start receiving in the background pub fn start(&mut self) -> Result<(), Error> { // Clear the ring buffer so that it is ready to receive data @@ -206,7 +208,7 @@ impl<'d, T: BasicInstance, RxDma: super::RxDma<T>> RingBufferedUartRx<'d, T, RxD } } -impl<T: BasicInstance, RxDma: super::RxDma<T>> Drop for RingBufferedUartRx<'_, T, RxDma> { +impl<T: BasicInstance> Drop for RingBufferedUartRx<'_, T> { fn drop(&mut self) { self.teardown_uart(); @@ -243,18 +245,16 @@ fn clear_idle_flag(r: Regs) -> Sr { sr } -impl<T, Rx> embedded_io_async::ErrorType for RingBufferedUartRx<'_, T, Rx> +impl<T> embedded_io_async::ErrorType for RingBufferedUartRx<'_, T> where T: BasicInstance, - Rx: RxDma<T>, { type Error = Error; } -impl<T, Rx> embedded_io_async::Read for RingBufferedUartRx<'_, T, Rx> +impl<T> embedded_io_async::Read for RingBufferedUartRx<'_, T> where T: BasicInstance, - Rx: RxDma<T>, { async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> { self.read(buf).await diff --git a/embassy-stm32/src/usb/usb.rs b/embassy-stm32/src/usb/usb.rs index f39915906..be321a19b 100644 --- a/embassy-stm32/src/usb/usb.rs +++ b/embassy-stm32/src/usb/usb.rs @@ -264,7 +264,7 @@ impl<'d, T: Instance> Driver<'d, T> { let regs = T::regs(); - #[cfg(any(stm32l5, stm32wb))] + #[cfg(any(stm32l4, stm32l5, stm32wb))] crate::pac::PWR.cr2().modify(|w| w.set_usv(true)); #[cfg(pwr_h5)] @@ -280,7 +280,7 @@ impl<'d, T: Instance> Driver<'d, T> { #[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); + cortex_m::asm::delay(unsafe { crate::rcc::get_freqs() }.sys.unwrap().0 / 10); #[cfg(not(usb_v4))] regs.btable().write(|w| w.set_btable(0)); diff --git a/embassy-stm32/src/usb_otg/usb.rs b/embassy-stm32/src/usb_otg/usb.rs index 190fb274f..373697ec8 100644 --- a/embassy-stm32/src/usb_otg/usb.rs +++ b/embassy-stm32/src/usb_otg/usb.rs @@ -606,13 +606,6 @@ impl<'d, T: Instance> Bus<'d, T> { // 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(|_| { @@ -645,13 +638,6 @@ impl<'d, T: Instance> Bus<'d, T> { // 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); - }) - }); } <T as RccPeripheral>::enable_and_reset(); diff --git a/embassy-stm32/src/wdg/mod.rs b/embassy-stm32/src/wdg/mod.rs index dc701ef64..2ff0db09e 100644 --- a/embassy-stm32/src/wdg/mod.rs +++ b/embassy-stm32/src/wdg/mod.rs @@ -42,9 +42,13 @@ impl<'d, T: Instance> IndependentWatchdog<'d, T> { // Prescaler value let psc = 2u16.pow(psc_power); + #[cfg(not(iwdg_v3))] + assert!(psc <= 256, "IWDG prescaler should be no more than 256"); + #[cfg(iwdg_v3)] // H5, U5, WBA + assert!(psc <= 1024, "IWDG prescaler should be no more than 1024"); + // Convert prescaler power to PR register value let pr = psc_power as u8 - 2; - assert!(pr <= 0b110); // Reload value let rl = reload_value(psc, timeout_us); diff --git a/embassy-sync/src/channel.rs b/embassy-sync/src/channel.rs index ff7129303..01db0d09a 100644 --- a/embassy-sync/src/channel.rs +++ b/embassy-sync/src/channel.rs @@ -507,6 +507,16 @@ where Receiver { channel: self } } + /// Get a sender for this channel using dynamic dispatch. + pub fn dyn_sender(&self) -> DynamicSender<'_, T> { + DynamicSender { channel: self } + } + + /// Get a receiver for this channel using dynamic dispatch. + pub fn dyn_receiver(&self) -> DynamicReceiver<'_, T> { + DynamicReceiver { channel: self } + } + /// Send a value, waiting until there is capacity. /// /// Sending completes when the value has been pushed to the channel's queue. @@ -648,7 +658,7 @@ mod tests { } #[test] - fn dynamic_dispatch() { + fn dynamic_dispatch_into() { let c = Channel::<NoopRawMutex, u32, 3>::new(); let s: DynamicSender<'_, u32> = c.sender().into(); let r: DynamicReceiver<'_, u32> = c.receiver().into(); @@ -657,6 +667,16 @@ mod tests { assert_eq!(r.try_receive().unwrap(), 1); } + #[test] + fn dynamic_dispatch_constructor() { + let c = Channel::<NoopRawMutex, u32, 3>::new(); + let s = c.dyn_sender(); + let r = c.dyn_receiver(); + + assert!(s.try_send(1).is_ok()); + assert_eq!(r.try_receive().unwrap(), 1); + } + #[futures_test::test] async fn receiver_receives_given_try_send_async() { let executor = ThreadPool::new().unwrap(); diff --git a/embassy-sync/src/priority_channel.rs b/embassy-sync/src/priority_channel.rs index bd75c0135..e77678c24 100644 --- a/embassy-sync/src/priority_channel.rs +++ b/embassy-sync/src/priority_channel.rs @@ -325,7 +325,7 @@ where /// /// Sent data may be reordered based on their priorty within the channel. /// For example, in a [`Max`](heapless::binary_heap::Max) [`PriorityChannel`] -/// containing `u32`'s, data sent in the following order `[1, 2, 3]` will be recieved as `[3, 2, 1]`. +/// containing `u32`'s, data sent in the following order `[1, 2, 3]` will be received as `[3, 2, 1]`. pub struct PriorityChannel<M, T, K, const N: usize> where T: Ord, diff --git a/embassy-sync/src/ring_buffer.rs b/embassy-sync/src/ring_buffer.rs index d95ffa7c9..81e60c42b 100644 --- a/embassy-sync/src/ring_buffer.rs +++ b/embassy-sync/src/ring_buffer.rs @@ -3,7 +3,7 @@ use core::ops::Range; pub struct RingBuffer<const N: usize> { start: usize, end: usize, - empty: bool, + full: bool, } impl<const N: usize> RingBuffer<N> { @@ -11,13 +11,13 @@ impl<const N: usize> RingBuffer<N> { Self { start: 0, end: 0, - empty: true, + full: false, } } pub fn push_buf(&mut self) -> Range<usize> { - if self.start == self.end && !self.empty { - trace!(" ringbuf: push_buf empty"); + if self.is_full() { + trace!(" ringbuf: push_buf full"); return 0..0; } @@ -38,11 +38,11 @@ impl<const N: usize> RingBuffer<N> { } self.end = self.wrap(self.end + n); - self.empty = false; + self.full = self.start == self.end; } pub fn pop_buf(&mut self) -> Range<usize> { - if self.empty { + if self.is_empty() { trace!(" ringbuf: pop_buf empty"); return 0..0; } @@ -64,20 +64,20 @@ impl<const N: usize> RingBuffer<N> { } self.start = self.wrap(self.start + n); - self.empty = self.start == self.end; + self.full = false; } pub fn is_full(&self) -> bool { - self.start == self.end && !self.empty + self.full } pub fn is_empty(&self) -> bool { - self.empty + self.start == self.end && !self.full } #[allow(unused)] pub fn len(&self) -> usize { - if self.empty { + if self.is_empty() { 0 } else if self.start < self.end { self.end - self.start @@ -89,7 +89,7 @@ impl<const N: usize> RingBuffer<N> { pub fn clear(&mut self) { self.start = 0; self.end = 0; - self.empty = true; + self.full = false; } fn wrap(&self, n: usize) -> usize { diff --git a/embassy-sync/src/zerocopy_channel.rs b/embassy-sync/src/zerocopy_channel.rs index f704cbd5d..cfce9a571 100644 --- a/embassy-sync/src/zerocopy_channel.rs +++ b/embassy-sync/src/zerocopy_channel.rs @@ -1,10 +1,7 @@ //! A zero-copy queue for sending values between asynchronous tasks. //! -//! It can be used concurrently by multiple producers (senders) and multiple -//! consumers (receivers), i.e. it is an "MPMC channel". -//! -//! Receivers are competing for messages. So a message that is received by -//! one receiver is not received by any other. +//! It can be used concurrently by a producer (sender) and a +//! consumer (receiver), i.e. it is an "SPSC channel". //! //! This queue takes a Mutex type so that various //! targets can be attained. For example, a ThreadModeMutex can be used diff --git a/embassy-time/src/delay.rs b/embassy-time/src/delay.rs index 7ef5961f0..f77859d4a 100644 --- a/embassy-time/src/delay.rs +++ b/embassy-time/src/delay.rs @@ -13,6 +13,7 @@ pub fn block_for(duration: Duration) { /// the amount provided, but accuracy can be affected by many factors, including interrupt usage. /// Make sure to use a suitable tick rate for your use case. The tick rate is defined by the currently /// active driver. +#[derive(Clone)] pub struct Delay; impl embedded_hal_1::delay::DelayNs for Delay { diff --git a/embassy-time/src/lib.rs b/embassy-time/src/lib.rs index d27eb92f6..3c8575ee9 100644 --- a/embassy-time/src/lib.rs +++ b/embassy-time/src/lib.rs @@ -32,7 +32,7 @@ pub use delay::{block_for, Delay}; pub use duration::Duration; pub use embassy_time_driver::TICK_HZ; pub use instant::Instant; -pub use timer::{with_timeout, Ticker, TimeoutError, Timer}; +pub use timer::{with_deadline, with_timeout, Ticker, TimeoutError, Timer}; const fn gcd(a: u64, b: u64) -> u64 { if b == 0 { diff --git a/embassy-time/src/timer.rs b/embassy-time/src/timer.rs index 565a65cb8..daa4c1699 100644 --- a/embassy-time/src/timer.rs +++ b/embassy-time/src/timer.rs @@ -8,7 +8,7 @@ use futures_util::{pin_mut, Stream}; use crate::{Duration, Instant}; -/// Error returned by [`with_timeout`] on timeout. +/// Error returned by [`with_timeout`] and [`with_deadline`] on timeout. #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct TimeoutError; @@ -26,6 +26,19 @@ pub async fn with_timeout<F: Future>(timeout: Duration, fut: F) -> Result<F::Out } } +/// Runs a given future with a deadline time. +/// +/// If the future completes before the deadline, its output is returned. Otherwise, on timeout, +/// work on the future is stopped (`poll` is no longer called), the future is dropped and `Err(TimeoutError)` is returned. +pub async fn with_deadline<F: Future>(at: Instant, fut: F) -> Result<F::Output, TimeoutError> { + let timeout_fut = Timer::at(at); + pin_mut!(fut); + match select(fut, timeout_fut).await { + Either::Left((r, _)) => Ok(r), + Either::Right(_) => Err(TimeoutError), + } +} + /// A future that completes at a specified [Instant](struct.Instant.html). #[must_use = "futures do nothing unless you `.await` or poll them"] pub struct Timer { diff --git a/embassy-usb-dfu/Cargo.toml b/embassy-usb-dfu/Cargo.toml index 1ca5fea42..4d6ffeb5f 100644 --- a/embassy-usb-dfu/Cargo.toml +++ b/embassy-usb-dfu/Cargo.toml @@ -12,14 +12,24 @@ categories = [ "asynchronous" ] -# 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-v$VERSION/embassy-usb-dfu/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-usb-dfu/src/" +features = ["defmt", "cortex-m"] +target = "thumbv7em-none-eabi" +flavors = [ + { name = "dfu", features = [ "dfu" ] }, + { name = "application", features = [ "application" ] }, +] + +[package.metadata.docs.rs] +features = ["defmt", "cortex-m", "dfu"] [dependencies] bitflags = "2.4.1" cortex-m = { version = "0.7.7", features = ["inline-asm"], optional = true } defmt = { version = "0.3.5", optional = true } embassy-boot = { version = "0.2.0", path = "../embassy-boot" } -# embassy-embedded-hal = { version = "0.1.0", path = "../embassy-embedded-hal" } embassy-futures = { version = "0.1.1", path = "../embassy-futures" } embassy-sync = { version = "0.5.0", path = "../embassy-sync" } embassy-time = { version = "0.3.0", path = "../embassy-time" } diff --git a/embassy-usb-logger/src/lib.rs b/embassy-usb-logger/src/lib.rs index 45d780bf8..da5ff0f36 100644 --- a/embassy-usb-logger/src/lib.rs +++ b/embassy-usb-logger/src/lib.rs @@ -6,7 +6,7 @@ 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::class::cdc_acm::{CdcAcmClass, Receiver, Sender, State}; use embassy_usb::driver::Driver; use embassy_usb::{Builder, Config}; use log::{Metadata, Record}; @@ -37,6 +37,9 @@ impl<'d> LoggerState<'d> { } } +/// The packet size used in the usb logger, to be used with `create_future_from_class` +pub const MAX_PACKET_SIZE: u8 = 64; + /// The logger handle, which contains a pipe with configurable size for buffering log messages. pub struct UsbLogger<const N: usize> { buffer: Pipe<CS, N>, @@ -54,7 +57,6 @@ impl<const N: usize> UsbLogger<N> { 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"); @@ -87,22 +89,46 @@ impl<const N: usize> UsbLogger<N> { 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]; - sender.wait_connection().await; - loop { - let len = self.buffer.read(&mut rx[..]).await; - let _ = sender.write_packet(&rx[..len]).await; + let class_fut = self.run_logger_class(&mut sender, &mut receiver); + join(run_fut, class_fut).await; + } + } + + async fn run_logger_class<'d, D>(&self, sender: &mut Sender<'d, D>, receiver: &mut Receiver<'d, D>) + where + D: Driver<'d>, + { + let log_fut = async { + let mut rx: [u8; MAX_PACKET_SIZE as usize] = [0; MAX_PACKET_SIZE as usize]; + sender.wait_connection().await; + loop { + let len = self.buffer.read(&mut rx[..]).await; + let _ = sender.write_packet(&rx[..len]).await; + if len as u8 == MAX_PACKET_SIZE { + let _ = sender.write_packet(&[]).await; } - }; - let discard_fut = async { - let mut discard_buf: [u8; MAX_PACKET_SIZE as usize] = [0; MAX_PACKET_SIZE as usize]; - receiver.wait_connection().await; - loop { - let _ = receiver.read_packet(&mut discard_buf).await; - } - }; - join(run_fut, join(log_fut, discard_fut)).await; + } + }; + let discard_fut = async { + let mut discard_buf: [u8; MAX_PACKET_SIZE as usize] = [0; MAX_PACKET_SIZE as usize]; + receiver.wait_connection().await; + loop { + let _ = receiver.read_packet(&mut discard_buf).await; + } + }; + + join(log_fut, discard_fut).await; + } + + /// Creates the futures needed for the logger from a given class + /// This can be used in cases where the usb device is already in use for another connection + pub async fn create_future_from_class<'d, D>(&'d self, class: CdcAcmClass<'d, D>) + where + D: Driver<'d>, + { + let (mut sender, mut receiver) = class.split(); + loop { + self.run_logger_class(&mut sender, &mut receiver).await; } } } @@ -153,3 +179,27 @@ macro_rules! run { let _ = LOGGER.run(&mut ::embassy_usb_logger::LoggerState::new(), $p).await; }; } + +/// Initialize the USB serial logger from a serial class and return the future to run it. +/// +/// Arguments specify the buffer size, log level and the serial class, respectively. +/// +/// # Usage +/// +/// ``` +/// embassy_usb_logger::with_class!(1024, log::LevelFilter::Info, class); +/// ``` +/// +/// # Safety +/// +/// This macro should only be invoked only once since it is setting the global logging state of the application. +#[macro_export] +macro_rules! with_class { + ( $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)); + } + LOGGER.create_future_from_class($p) + }}; +} diff --git a/embassy-usb/Cargo.toml b/embassy-usb/Cargo.toml index 1b31b6145..fe5e36b32 100644 --- a/embassy-usb/Cargo.toml +++ b/embassy-usb/Cargo.toml @@ -56,5 +56,5 @@ log = { version = "0.4.14", optional = true } heapless = "0.8" # for HID -usbd-hid = { version = "0.6.0", optional = true } +usbd-hid = { version = "0.7.0", optional = true } ssmarshal = { version = "1.0", default-features = false, optional = true } diff --git a/embassy-usb/README.md b/embassy-usb/README.md index 7411fcf52..d2adae4f5 100644 --- a/embassy-usb/README.md +++ b/embassy-usb/README.md @@ -9,7 +9,7 @@ Async USB device stack for embedded devices in Rust. - Suspend/resume, remote wakeup. - USB composite devices. - Ergonomic descriptor builder. -- Ready-to-use implementations for a few USB classes (note you can still implement any class yourself oustide the crate). +- Ready-to-use implementations for a few USB classes (note you can still implement any class yourself outside the crate). - Serial ports (CDC ACM) - Ethernet (CDC NCM) - Human Interface Devices (HID) diff --git a/examples/boot/.cargo/config.toml b/examples/boot/.cargo/config.toml index de3a814f7..be1b73e45 100644 --- a/examples/boot/.cargo/config.toml +++ b/examples/boot/.cargo/config.toml @@ -1,6 +1,6 @@ [unstable] -build-std = ["core"] -build-std-features = ["panic_immediate_abort"] +#build-std = ["core"] +#build-std-features = ["panic_immediate_abort"] [build] target = "thumbv7em-none-eabi" diff --git a/examples/boot/application/nrf/src/bin/a.rs b/examples/boot/application/nrf/src/bin/a.rs index f3abfddbc..851a3d721 100644 --- a/examples/boot/application/nrf/src/bin/a.rs +++ b/examples/boot/application/nrf/src/bin/a.rs @@ -50,7 +50,7 @@ async fn main(_spawner: Spawner) { let nvmc = Nvmc::new(p.NVMC); let nvmc = Mutex::new(BlockingAsync::new(nvmc)); - let config = FirmwareUpdaterConfig::from_linkerfile(&nvmc); + let config = FirmwareUpdaterConfig::from_linkerfile(&nvmc, &nvmc); let mut magic = [0; 4]; let mut updater = FirmwareUpdater::new(config, &mut magic); loop { diff --git a/examples/boot/application/rp/.cargo/config.toml b/examples/boot/application/rp/.cargo/config.toml index cd8d1ef02..22ab3a5c1 100644 --- a/examples/boot/application/rp/.cargo/config.toml +++ b/examples/boot/application/rp/.cargo/config.toml @@ -1,6 +1,6 @@ [unstable] -build-std = ["core"] -build-std-features = ["panic_immediate_abort"] +#build-std = ["core"] +#build-std-features = ["panic_immediate_abort"] [target.'cfg(all(target_arch = "arm", target_os = "none"))'] runner = "probe-rs run --chip RP2040" diff --git a/examples/boot/application/rp/src/bin/a.rs b/examples/boot/application/rp/src/bin/a.rs index 3f0bf90e2..ede0c07da 100644 --- a/examples/boot/application/rp/src/bin/a.rs +++ b/examples/boot/application/rp/src/bin/a.rs @@ -36,7 +36,7 @@ async fn main(_s: Spawner) { let flash = Flash::<_, _, FLASH_SIZE>::new_blocking(p.FLASH); let flash = Mutex::new(RefCell::new(flash)); - let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash); + let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash, &flash); let mut aligned = AlignedBuffer([0; 1]); let mut updater = BlockingFirmwareUpdater::new(config, &mut aligned.0); diff --git a/examples/boot/application/stm32f3/memory.x b/examples/boot/application/stm32f3/memory.x index f51875766..02ebe3ecf 100644 --- a/examples/boot/application/stm32f3/memory.x +++ b/examples/boot/application/stm32f3/memory.x @@ -3,8 +3,8 @@ MEMORY /* NOTE 1 K = 1 KiBi = 1024 bytes */ BOOTLOADER : ORIGIN = 0x08000000, LENGTH = 24K BOOTLOADER_STATE : ORIGIN = 0x08006000, LENGTH = 4K - FLASH : ORIGIN = 0x08008000, LENGTH = 32K - DFU : ORIGIN = 0x08010000, LENGTH = 36K + FLASH : ORIGIN = 0x08008000, LENGTH = 64K + DFU : ORIGIN = 0x08018000, LENGTH = 66K RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32K } diff --git a/examples/boot/application/stm32f3/src/bin/a.rs b/examples/boot/application/stm32f3/src/bin/a.rs index 96ae5c47b..8858ae3da 100644 --- a/examples/boot/application/stm32f3/src/bin/a.rs +++ b/examples/boot/application/stm32f3/src/bin/a.rs @@ -8,7 +8,7 @@ use embassy_embedded_hal::adapter::BlockingAsync; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; use embassy_stm32::flash::{Flash, WRITE_SIZE}; -use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; +use embassy_stm32::gpio::{Level, Output, Pull, Speed}; use embassy_sync::mutex::Mutex; use panic_reset as _; @@ -23,13 +23,12 @@ async fn main(_spawner: Spawner) { 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); + let mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::Up); let mut led = Output::new(p.PA5, Level::Low, Speed::Low); led.set_high(); - let config = FirmwareUpdaterConfig::from_linkerfile(&flash); + let config = FirmwareUpdaterConfig::from_linkerfile(&flash, &flash); let mut magic = AlignedBuffer([0; WRITE_SIZE]); let mut updater = FirmwareUpdater::new(config, &mut magic.0); button.wait_for_falling_edge().await; diff --git a/examples/boot/application/stm32f7/src/bin/a.rs b/examples/boot/application/stm32f7/src/bin/a.rs index a6107386a..d3df11fe4 100644 --- a/examples/boot/application/stm32f7/src/bin/a.rs +++ b/examples/boot/application/stm32f7/src/bin/a.rs @@ -9,7 +9,7 @@ use embassy_boot_stm32::{AlignedBuffer, BlockingFirmwareUpdater, FirmwareUpdater use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; use embassy_stm32::flash::{Flash, WRITE_SIZE}; -use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; +use embassy_stm32::gpio::{Level, Output, Pull, Speed}; use embassy_sync::blocking_mutex::Mutex; use embedded_storage::nor_flash::NorFlash; use panic_reset as _; @@ -25,13 +25,12 @@ async fn main(_spawner: Spawner) { 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); + let mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::Down); let mut led = Output::new(p.PB7, Level::Low, Speed::Low); led.set_high(); - let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash); + let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash, &flash); let mut magic = AlignedBuffer([0; WRITE_SIZE]); let mut updater = BlockingFirmwareUpdater::new(config, &mut magic.0); let writer = updater.prepare_update().unwrap(); diff --git a/examples/boot/application/stm32h7/src/bin/a.rs b/examples/boot/application/stm32h7/src/bin/a.rs index b73506cf3..f61ac1f71 100644 --- a/examples/boot/application/stm32h7/src/bin/a.rs +++ b/examples/boot/application/stm32h7/src/bin/a.rs @@ -9,7 +9,7 @@ use embassy_boot_stm32::{AlignedBuffer, BlockingFirmwareUpdater, FirmwareUpdater use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; use embassy_stm32::flash::{Flash, WRITE_SIZE}; -use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; +use embassy_stm32::gpio::{Level, Output, Pull, Speed}; use embassy_sync::blocking_mutex::Mutex; use embedded_storage::nor_flash::NorFlash; use panic_reset as _; @@ -25,13 +25,12 @@ async fn main(_spawner: Spawner) { 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); + let mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::Down); let mut led = Output::new(p.PB14, Level::Low, Speed::Low); led.set_high(); - let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash); + let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash, &flash); let mut magic = AlignedBuffer([0; WRITE_SIZE]); let mut updater = BlockingFirmwareUpdater::new(config, &mut magic.0); let writer = updater.prepare_update().unwrap(); diff --git a/examples/boot/application/stm32l0/memory.x b/examples/boot/application/stm32l0/memory.x index a99330145..8866506a8 100644 --- a/examples/boot/application/stm32l0/memory.x +++ b/examples/boot/application/stm32l0/memory.x @@ -3,8 +3,8 @@ MEMORY /* NOTE 1 K = 1 KiBi = 1024 bytes */ BOOTLOADER : ORIGIN = 0x08000000, LENGTH = 24K BOOTLOADER_STATE : ORIGIN = 0x08006000, LENGTH = 4K - FLASH : ORIGIN = 0x08008000, LENGTH = 32K - DFU : ORIGIN = 0x08010000, LENGTH = 36K + FLASH : ORIGIN = 0x08008000, LENGTH = 64K + DFU : ORIGIN = 0x08018000, LENGTH = 66K RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 16K } diff --git a/examples/boot/application/stm32l0/src/bin/a.rs b/examples/boot/application/stm32l0/src/bin/a.rs index 02f74bdef..f066c1139 100644 --- a/examples/boot/application/stm32l0/src/bin/a.rs +++ b/examples/boot/application/stm32l0/src/bin/a.rs @@ -8,7 +8,7 @@ use embassy_embedded_hal::adapter::BlockingAsync; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; use embassy_stm32::flash::{Flash, WRITE_SIZE}; -use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; +use embassy_stm32::gpio::{Level, Output, Pull, Speed}; use embassy_sync::mutex::Mutex; use embassy_time::Timer; use panic_reset as _; @@ -24,14 +24,13 @@ async fn main(_spawner: Spawner) { 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); + let mut button = ExtiInput::new(p.PB2, p.EXTI2, Pull::Up); let mut led = Output::new(p.PB5, Level::Low, Speed::Low); led.set_high(); - let config = FirmwareUpdaterConfig::from_linkerfile(&flash); + let config = FirmwareUpdaterConfig::from_linkerfile(&flash, &flash); let mut magic = AlignedBuffer([0; WRITE_SIZE]); let mut updater = FirmwareUpdater::new(config, &mut magic.0); button.wait_for_falling_edge().await; diff --git a/examples/boot/application/stm32l1/memory.x b/examples/boot/application/stm32l1/memory.x index a99330145..caa525278 100644 --- a/examples/boot/application/stm32l1/memory.x +++ b/examples/boot/application/stm32l1/memory.x @@ -3,8 +3,8 @@ MEMORY /* NOTE 1 K = 1 KiBi = 1024 bytes */ BOOTLOADER : ORIGIN = 0x08000000, LENGTH = 24K BOOTLOADER_STATE : ORIGIN = 0x08006000, LENGTH = 4K - FLASH : ORIGIN = 0x08008000, LENGTH = 32K - DFU : ORIGIN = 0x08010000, LENGTH = 36K + FLASH : ORIGIN = 0x08008000, LENGTH = 46K + DFU : ORIGIN = 0x08013800, LENGTH = 54K RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 16K } diff --git a/examples/boot/application/stm32l1/src/bin/a.rs b/examples/boot/application/stm32l1/src/bin/a.rs index 02f74bdef..f066c1139 100644 --- a/examples/boot/application/stm32l1/src/bin/a.rs +++ b/examples/boot/application/stm32l1/src/bin/a.rs @@ -8,7 +8,7 @@ use embassy_embedded_hal::adapter::BlockingAsync; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; use embassy_stm32::flash::{Flash, WRITE_SIZE}; -use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; +use embassy_stm32::gpio::{Level, Output, Pull, Speed}; use embassy_sync::mutex::Mutex; use embassy_time::Timer; use panic_reset as _; @@ -24,14 +24,13 @@ async fn main(_spawner: Spawner) { 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); + let mut button = ExtiInput::new(p.PB2, p.EXTI2, Pull::Up); let mut led = Output::new(p.PB5, Level::Low, Speed::Low); led.set_high(); - let config = FirmwareUpdaterConfig::from_linkerfile(&flash); + let config = FirmwareUpdaterConfig::from_linkerfile(&flash, &flash); let mut magic = AlignedBuffer([0; WRITE_SIZE]); let mut updater = FirmwareUpdater::new(config, &mut magic.0); button.wait_for_falling_edge().await; diff --git a/examples/boot/application/stm32l4/memory.x b/examples/boot/application/stm32l4/memory.x index f51875766..e1d4e7fa8 100644 --- a/examples/boot/application/stm32l4/memory.x +++ b/examples/boot/application/stm32l4/memory.x @@ -3,8 +3,8 @@ MEMORY /* NOTE 1 K = 1 KiBi = 1024 bytes */ BOOTLOADER : ORIGIN = 0x08000000, LENGTH = 24K BOOTLOADER_STATE : ORIGIN = 0x08006000, LENGTH = 4K - FLASH : ORIGIN = 0x08008000, LENGTH = 32K - DFU : ORIGIN = 0x08010000, LENGTH = 36K + FLASH : ORIGIN = 0x08008000, LENGTH = 64K + DFU : ORIGIN = 0x08018000, LENGTH = 68K RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32K } diff --git a/examples/boot/application/stm32l4/src/bin/a.rs b/examples/boot/application/stm32l4/src/bin/a.rs index 892446968..a0079ee33 100644 --- a/examples/boot/application/stm32l4/src/bin/a.rs +++ b/examples/boot/application/stm32l4/src/bin/a.rs @@ -8,7 +8,7 @@ use embassy_embedded_hal::adapter::BlockingAsync; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; use embassy_stm32::flash::{Flash, WRITE_SIZE}; -use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; +use embassy_stm32::gpio::{Level, Output, Pull, Speed}; use embassy_sync::mutex::Mutex; use panic_reset as _; @@ -23,13 +23,12 @@ async fn main(_spawner: Spawner) { 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); + let mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::Up); let mut led = Output::new(p.PB14, Level::Low, Speed::Low); led.set_high(); - let config = FirmwareUpdaterConfig::from_linkerfile(&flash); + let config = FirmwareUpdaterConfig::from_linkerfile(&flash, &flash); let mut magic = AlignedBuffer([0; WRITE_SIZE]); let mut updater = FirmwareUpdater::new(config, &mut magic.0); button.wait_for_falling_edge().await; diff --git a/examples/boot/application/stm32wb-dfu/README.md b/examples/boot/application/stm32wb-dfu/README.md index c8dce0387..7f656cde6 100644 --- a/examples/boot/application/stm32wb-dfu/README.md +++ b/examples/boot/application/stm32wb-dfu/README.md @@ -1,29 +1,9 @@ # Examples using bootloader -Example for STM32WL 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. - - -## Prerequisites - -* `cargo-binutils` -* `cargo-flash` -* `embassy-boot-stm32` +Example for STM32WB demonstrating the USB DFU application. ## Usage ``` -# Flash bootloader -cargo flash --manifest-path ../../bootloader/stm32/Cargo.toml --release --features embassy-stm32/stm32wl55jc-cm4 --chip STM32WLE5JCIx -# 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 STM32WLE5JCIx +cargo flash --release --chip STM32WB55RGVx ``` diff --git a/examples/boot/application/stm32wb-dfu/src/main.rs b/examples/boot/application/stm32wb-dfu/src/main.rs index b2ccb9e1a..37c3d7d90 100644 --- a/examples/boot/application/stm32wb-dfu/src/main.rs +++ b/examples/boot/application/stm32wb-dfu/src/main.rs @@ -30,7 +30,7 @@ async fn main(_spawner: Spawner) { let flash = Flash::new_blocking(p.FLASH); let flash = Mutex::new(RefCell::new(flash)); - let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash); + let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash, &flash); let mut magic = AlignedBuffer([0; WRITE_SIZE]); let mut firmware_state = BlockingFirmwareState::from_config(config, &mut magic.0); firmware_state.mark_booted().expect("Failed to mark booted"); diff --git a/examples/boot/application/stm32wl/memory.x b/examples/boot/application/stm32wl/memory.x index f51875766..e1d4e7fa8 100644 --- a/examples/boot/application/stm32wl/memory.x +++ b/examples/boot/application/stm32wl/memory.x @@ -3,8 +3,8 @@ MEMORY /* NOTE 1 K = 1 KiBi = 1024 bytes */ BOOTLOADER : ORIGIN = 0x08000000, LENGTH = 24K BOOTLOADER_STATE : ORIGIN = 0x08006000, LENGTH = 4K - FLASH : ORIGIN = 0x08008000, LENGTH = 32K - DFU : ORIGIN = 0x08010000, LENGTH = 36K + FLASH : ORIGIN = 0x08008000, LENGTH = 64K + DFU : ORIGIN = 0x08018000, LENGTH = 68K RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32K } diff --git a/examples/boot/application/stm32wl/src/bin/a.rs b/examples/boot/application/stm32wl/src/bin/a.rs index d9665e6ee..2fb16bdc4 100644 --- a/examples/boot/application/stm32wl/src/bin/a.rs +++ b/examples/boot/application/stm32wl/src/bin/a.rs @@ -8,7 +8,7 @@ use embassy_embedded_hal::adapter::BlockingAsync; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; use embassy_stm32::flash::{Flash, WRITE_SIZE}; -use embassy_stm32::gpio::{Input, Level, Output, Pull, Speed}; +use embassy_stm32::gpio::{Level, Output, Pull, Speed}; use embassy_sync::mutex::Mutex; use panic_reset as _; @@ -23,13 +23,12 @@ async fn main(_spawner: Spawner) { 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); + let mut button = ExtiInput::new(p.PA0, p.EXTI0, Pull::Up); let mut led = Output::new(p.PB9, Level::Low, Speed::Low); led.set_high(); - let config = FirmwareUpdaterConfig::from_linkerfile(&flash); + let config = FirmwareUpdaterConfig::from_linkerfile(&flash, &flash); let mut magic = AlignedBuffer([0; WRITE_SIZE]); let mut updater = FirmwareUpdater::new(config, &mut magic.0); button.wait_for_falling_edge().await; diff --git a/examples/boot/bootloader/nrf/.cargo/config.toml b/examples/boot/bootloader/nrf/.cargo/config.toml index c292846aa..58acd1a49 100644 --- a/examples/boot/bootloader/nrf/.cargo/config.toml +++ b/examples/boot/bootloader/nrf/.cargo/config.toml @@ -1,6 +1,6 @@ [unstable] -build-std = ["core"] -build-std-features = ["panic_immediate_abort"] +#build-std = ["core"] +#build-std-features = ["panic_immediate_abort"] [target.'cfg(all(target_arch = "arm", target_os = "none"))'] #runner = "./fruitrunner" @@ -8,7 +8,7 @@ runner = "probe-rs run --chip nrf52840_xxAA" rustflags = [ # Code-size optimizations. - "-Z", "trap-unreachable=no", + #"-Z", "trap-unreachable=no", #"-C", "no-vectorize-loops", "-C", "force-frame-pointers=yes", ] diff --git a/examples/boot/bootloader/nrf/src/main.rs b/examples/boot/bootloader/nrf/src/main.rs index 74e2e293f..67c700437 100644 --- a/examples/boot/bootloader/nrf/src/main.rs +++ b/examples/boot/bootloader/nrf/src/main.rs @@ -31,7 +31,7 @@ fn main() -> ! { 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 config = BootLoaderConfig::from_linkerfile_blocking(&flash, &flash, &flash); let active_offset = config.active.offset(); let bl: BootLoader = BootLoader::prepare(config); diff --git a/examples/boot/bootloader/rp/src/main.rs b/examples/boot/bootloader/rp/src/main.rs index c0e75d1ea..25b1657b8 100644 --- a/examples/boot/bootloader/rp/src/main.rs +++ b/examples/boot/bootloader/rp/src/main.rs @@ -27,7 +27,7 @@ fn main() -> ! { let flash = WatchdogFlash::<FLASH_SIZE>::start(p.FLASH, p.WATCHDOG, Duration::from_secs(8)); let flash = Mutex::new(RefCell::new(flash)); - let config = BootLoaderConfig::from_linkerfile_blocking(&flash); + let config = BootLoaderConfig::from_linkerfile_blocking(&flash, &flash, &flash); let active_offset = config.active.offset(); let bl: BootLoader = BootLoader::prepare(config); diff --git a/examples/boot/bootloader/stm32-dual-bank/Cargo.toml b/examples/boot/bootloader/stm32-dual-bank/Cargo.toml new file mode 100644 index 000000000..313187adc --- /dev/null +++ b/examples/boot/bootloader/stm32-dual-bank/Cargo.toml @@ -0,0 +1,57 @@ +[package] +edition = "2021" +name = "stm32-bootloader-dual-bank-flash-example" +version = "0.1.0" +description = "Example bootloader for dual-bank flash STM32 chips" +license = "MIT OR Apache-2.0" + +[dependencies] +defmt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } + +embassy-stm32 = { path = "../../../../embassy-stm32", features = [] } +embassy-boot-stm32 = { path = "../../../../embassy-boot-stm32" } +cortex-m = { version = "0.7.6", features = [ + "inline-asm", + "critical-section-single-core", +] } +embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" } +cortex-m-rt = { version = "0.7" } +embedded-storage = "0.3.1" +embedded-storage-async = "0.4.0" +cfg-if = "1.0.0" + +[features] +defmt = ["dep:defmt", "embassy-boot-stm32/defmt", "embassy-stm32/defmt"] +debug = ["defmt-rtt", "defmt"] + +[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/examples/boot/bootloader/stm32-dual-bank/README.md b/examples/boot/bootloader/stm32-dual-bank/README.md new file mode 100644 index 000000000..3de3171cd --- /dev/null +++ b/examples/boot/bootloader/stm32-dual-bank/README.md @@ -0,0 +1,44 @@ +# STM32 dual-bank flash Bootloader + +## Overview + +This bootloader leverages `embassy-boot` to interact with the flash. +This example targets STM32 devices with dual-bank flash memory, with a primary focus on the STM32H747XI series. +Users must modify the `memory.x` configuration file to match with the memory layout of their specific STM32 device. + +Additionally, this example can be extended to utilize external flash memory, such as QSPI, for storing partitions. + +## Memory Configuration + +In this example's `memory.x` file, various symbols are defined to assist in effective memory management within the bootloader environment. +For dual-bank STM32 devices, it's crucial to assign these symbols correctly to their respective memory banks. + +### Symbol Definitions + +The bootloader's state and active symbols are anchored to the flash origin of **bank 1**: + +- `__bootloader_state_start` and `__bootloader_state_end` +- `__bootloader_active_start` and `__bootloader_active_end` + +In contrast, the Device Firmware Upgrade (DFU) symbols are aligned with the DFU flash origin in **bank 2**: + +- `__bootloader_dfu_start` and `__bootloader_dfu_end` + +```rust +__bootloader_state_start = ORIGIN(BOOTLOADER_STATE) - ORIGIN(**FLASH**); +__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE) - ORIGIN(**FLASH**); + +__bootloader_active_start = ORIGIN(ACTIVE) - ORIGIN(**FLASH**); +__bootloader_active_end = ORIGIN(ACTIVE) + LENGTH(ACTIVE) - ORIGIN(**FLASH**); + +__bootloader_dfu_start = ORIGIN(DFU) - ORIGIN(**DFU**); +__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU) - ORIGIN(**DFU**); +``` + +## Flashing the Bootloader + +To flash the bootloader onto your STM32H747XI device, use the following command: + +```bash +cargo flash --features embassy-stm32/stm32h747xi-cm7 --release --chip STM32H747XIHx +``` diff --git a/examples/boot/bootloader/stm32-dual-bank/build.rs b/examples/boot/bootloader/stm32-dual-bank/build.rs new file mode 100644 index 000000000..fd605991f --- /dev/null +++ b/examples/boot/bootloader/stm32-dual-bank/build.rs @@ -0,0 +1,27 @@ +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"); + if env::var("CARGO_FEATURE_DEFMT").is_ok() { + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + } +} diff --git a/examples/boot/bootloader/stm32-dual-bank/memory.x b/examples/boot/bootloader/stm32-dual-bank/memory.x new file mode 100644 index 000000000..665da7139 --- /dev/null +++ b/examples/boot/bootloader/stm32-dual-bank/memory.x @@ -0,0 +1,18 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + FLASH : ORIGIN = 0x08000000, LENGTH = 128K + BOOTLOADER_STATE : ORIGIN = 0x08020000, LENGTH = 128K + ACTIVE : ORIGIN = 0x08040000, LENGTH = 512K + DFU : ORIGIN = 0x08100000, LENGTH = 640K + RAM (rwx) : ORIGIN = 0x24000000, LENGTH = 512K +} + +__bootloader_state_start = ORIGIN(BOOTLOADER_STATE) - ORIGIN(FLASH); +__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE) - ORIGIN(FLASH); + +__bootloader_active_start = ORIGIN(ACTIVE) - ORIGIN(FLASH); +__bootloader_active_end = ORIGIN(ACTIVE) + LENGTH(ACTIVE) - ORIGIN(FLASH); + +__bootloader_dfu_start = ORIGIN(DFU) - ORIGIN(DFU); +__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU) - ORIGIN(DFU); diff --git a/examples/boot/bootloader/stm32-dual-bank/src/main.rs b/examples/boot/bootloader/stm32-dual-bank/src/main.rs new file mode 100644 index 000000000..4d2e82d26 --- /dev/null +++ b/examples/boot/bootloader/stm32-dual-bank/src/main.rs @@ -0,0 +1,53 @@ +#![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, BANK1_REGION}; +use embassy_sync::blocking_mutex::Mutex; + +#[entry] +fn main() -> ! { + let p = embassy_stm32::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 layout = Flash::new_blocking(p.FLASH).into_blocking_regions(); + let flash_bank1 = Mutex::new(RefCell::new(layout.bank1_region)); + let flash_bank2 = Mutex::new(RefCell::new(layout.bank2_region)); + + let config = BootLoaderConfig::from_linkerfile_blocking(&flash_bank1, &flash_bank2, &flash_bank1); + let active_offset = config.active.offset(); + let bl = BootLoader::prepare::<_, _, _, 2048>(config); + + unsafe { bl.load(BANK1_REGION.base + 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/src/main.rs b/examples/boot/bootloader/stm32/src/main.rs index 5fd9ea588..99a7a6a6b 100644 --- a/examples/boot/bootloader/stm32/src/main.rs +++ b/examples/boot/bootloader/stm32/src/main.rs @@ -25,7 +25,7 @@ fn main() -> ! { 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 config = BootLoaderConfig::from_linkerfile_blocking(&flash, &flash, &flash); let active_offset = config.active.offset(); let bl = BootLoader::prepare::<_, _, _, 2048>(config); diff --git a/examples/boot/bootloader/stm32wb-dfu/Cargo.toml b/examples/boot/bootloader/stm32wb-dfu/Cargo.toml index 96635afa2..854f94d85 100644 --- a/examples/boot/bootloader/stm32wb-dfu/Cargo.toml +++ b/examples/boot/bootloader/stm32wb-dfu/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0" defmt = { version = "0.3", optional = true } defmt-rtt = { version = "0.4", optional = true } -embassy-stm32 = { path = "../../../../embassy-stm32", features = ["stm32wb55rg"] } +embassy-stm32 = { path = "../../../../embassy-stm32", features = [] } embassy-boot-stm32 = { path = "../../../../embassy-boot-stm32" } cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" } diff --git a/examples/boot/bootloader/stm32wb-dfu/README.md b/examples/boot/bootloader/stm32wb-dfu/README.md index a82b730b9..d5c6ea57c 100644 --- a/examples/boot/bootloader/stm32wb-dfu/README.md +++ b/examples/boot/bootloader/stm32wb-dfu/README.md @@ -7,5 +7,5 @@ The bootloader uses `embassy-boot` to interact with the flash. Flash the bootloader ``` -cargo flash --features embassy-stm32/stm32wl55jc-cm4 --release --chip STM32WLE5JCIx +cargo flash --features embassy-stm32/stm32wb55rg --release --chip STM32WB55RGVx ``` diff --git a/examples/boot/bootloader/stm32wb-dfu/src/main.rs b/examples/boot/bootloader/stm32wb-dfu/src/main.rs index a7ab813b6..d989fbfdf 100644 --- a/examples/boot/bootloader/stm32wb-dfu/src/main.rs +++ b/examples/boot/bootloader/stm32wb-dfu/src/main.rs @@ -35,7 +35,7 @@ fn main() -> ! { 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 config = BootLoaderConfig::from_linkerfile_blocking(&flash, &flash, &flash); let active_offset = config.active.offset(); let bl = BootLoader::prepare::<_, _, _, 2048>(config); if bl.state == State::DfuDetach { @@ -45,7 +45,7 @@ fn main() -> ! { config.product = Some("USB-DFU Bootloader example"); config.serial_number = Some("1235678"); - let fw_config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash); + let fw_config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash, &flash); let mut buffer = AlignedBuffer([0; WRITE_SIZE]); let updater = BlockingFirmwareUpdater::new(fw_config, &mut buffer.0[..]); diff --git a/examples/nrf51/.cargo/config.toml b/examples/nrf51/.cargo/config.toml new file mode 100644 index 000000000..1671f5db1 --- /dev/null +++ b/examples/nrf51/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace nRF51422_xxAA with your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip nRF51422_xxAA" + +[build] +target = "thumbv6m-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/examples/nrf51/Cargo.toml b/examples/nrf51/Cargo.toml new file mode 100644 index 000000000..06c3d20cb --- /dev/null +++ b/examples/nrf51/Cargo.toml @@ -0,0 +1,20 @@ +[package] +edition = "2021" +name = "embassy-nrf51-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["task-arena-size-4096", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } +embassy-time = { version = "0.3.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf51", "gpiote", "time-driver-rtc1", "unstable-pac", "time", "rt"] } + +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" +panic-probe = { version = "0.3", features = ["print-defmt"] } + +[profile.release] +debug = 2 diff --git a/examples/nrf51/build.rs b/examples/nrf51/build.rs new file mode 100644 index 000000000..30691aa97 --- /dev/null +++ b/examples/nrf51/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/nrf51/memory.x b/examples/nrf51/memory.x new file mode 100644 index 000000000..98b3c792f --- /dev/null +++ b/examples/nrf51/memory.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x00000000, LENGTH = 128K + RAM : ORIGIN = 0x20000000, LENGTH = 16K +} diff --git a/examples/nrf51/src/bin/blinky.rs b/examples/nrf51/src/bin/blinky.rs new file mode 100644 index 000000000..7c12ffcbc --- /dev/null +++ b/examples/nrf51/src/bin/blinky.rs @@ -0,0 +1,20 @@ +#![no_std] +#![no_main] + +use embassy_executor::Spawner; +use embassy_nrf::gpio::{Level, Output, OutputDrive}; +use embassy_time::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_21, Level::Low, OutputDrive::Standard); + + loop { + led.set_high(); + Timer::after_millis(300).await; + led.set_low(); + Timer::after_millis(300).await; + } +} diff --git a/examples/nrf52840/Cargo.toml b/examples/nrf52840/Cargo.toml index abb995be6..4ab5c7b7c 100644 --- a/examples/nrf52840/Cargo.toml +++ b/examples/nrf52840/Cargo.toml @@ -28,7 +28,7 @@ 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.1" -usbd-hid = "0.6.0" +usbd-hid = "0.7.0" serde = { version = "1.0.136", default-features = false } embedded-hal = { version = "1.0" } embedded-hal-async = { version = "1.0" } diff --git a/examples/nrf52840/src/bin/ethernet_enc28j60.rs b/examples/nrf52840/src/bin/ethernet_enc28j60.rs index a8e64b38a..279f32edc 100644 --- a/examples/nrf52840/src/bin/ethernet_enc28j60.rs +++ b/examples/nrf52840/src/bin/ethernet_enc28j60.rs @@ -24,10 +24,7 @@ bind_interrupts!(struct Irqs { #[embassy_executor::task] async fn net_task( stack: &'static Stack< - Enc28j60< - ExclusiveDevice<Spim<'static, peripherals::SPI3>, Output<'static, peripherals::P0_15>, Delay>, - Output<'static, peripherals::P0_13>, - >, + Enc28j60<ExclusiveDevice<Spim<'static, peripherals::SPI3>, Output<'static>, Delay>, Output<'static>>, >, ) -> ! { stack.run().await @@ -71,12 +68,7 @@ async fn main(spawner: Spawner) { // Init network stack static RESOURCES: StaticCell<StackResources<2>> = StaticCell::new(); static STACK: StaticCell< - Stack< - Enc28j60< - ExclusiveDevice<Spim<'static, peripherals::SPI3>, Output<'static, peripherals::P0_15>, Delay>, - Output<'static, peripherals::P0_13>, - >, - >, + Stack<Enc28j60<ExclusiveDevice<Spim<'static, peripherals::SPI3>, Output<'static>, Delay>, Output<'static>>>, > = StaticCell::new(); let stack = STACK.init(Stack::new( device, diff --git a/examples/nrf52840/src/bin/gpiote_port.rs b/examples/nrf52840/src/bin/gpiote_port.rs index c1afe2f20..0dddb1a97 100644 --- a/examples/nrf52840/src/bin/gpiote_port.rs +++ b/examples/nrf52840/src/bin/gpiote_port.rs @@ -3,11 +3,11 @@ use defmt::{info, unwrap}; use embassy_executor::Spawner; -use embassy_nrf::gpio::{AnyPin, Input, Pin as _, Pull}; +use embassy_nrf::gpio::{Input, Pull}; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::task(pool_size = 4)] -async fn button_task(n: usize, mut pin: Input<'static, AnyPin>) { +async fn button_task(n: usize, mut pin: Input<'static>) { loop { pin.wait_for_low().await; info!("Button {:?} pressed!", n); @@ -21,10 +21,10 @@ async fn main(spawner: Spawner) { let p = embassy_nrf::init(Default::default()); info!("Starting!"); - let btn1 = Input::new(p.P0_11.degrade(), Pull::Up); - let btn2 = Input::new(p.P0_12.degrade(), Pull::Up); - let btn3 = Input::new(p.P0_24.degrade(), Pull::Up); - let btn4 = Input::new(p.P0_25.degrade(), Pull::Up); + let btn1 = Input::new(p.P0_11, Pull::Up); + let btn2 = Input::new(p.P0_12, Pull::Up); + let btn3 = Input::new(p.P0_24, Pull::Up); + let btn4 = Input::new(p.P0_25, Pull::Up); unwrap!(spawner.spawn(button_task(1, btn1))); unwrap!(spawner.spawn(button_task(2, btn2))); diff --git a/examples/nrf52840/src/bin/usb_hid_keyboard.rs b/examples/nrf52840/src/bin/usb_hid_keyboard.rs index 45850b4a4..3e86590c4 100644 --- a/examples/nrf52840/src/bin/usb_hid_keyboard.rs +++ b/examples/nrf52840/src/bin/usb_hid_keyboard.rs @@ -8,7 +8,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_futures::join::join; use embassy_futures::select::{select, Either}; -use embassy_nrf::gpio::{Input, Pin, Pull}; +use embassy_nrf::gpio::{Input, Pull}; use embassy_nrf::usb::vbus_detect::HardwareVbusDetect; use embassy_nrf::usb::Driver; use embassy_nrf::{bind_interrupts, pac, peripherals, usb}; @@ -97,7 +97,7 @@ async fn main(_spawner: Spawner) { } }; - let mut button = Input::new(p.P0_11.degrade(), Pull::Up); + let mut button = Input::new(p.P0_11, Pull::Up); let (reader, mut writer) = hid.split(); diff --git a/examples/nrf52840/src/bin/wifi_esp_hosted.rs b/examples/nrf52840/src/bin/wifi_esp_hosted.rs index fc2086f75..00bd50081 100644 --- a/examples/nrf52840/src/bin/wifi_esp_hosted.rs +++ b/examples/nrf52840/src/bin/wifi_esp_hosted.rs @@ -5,7 +5,7 @@ 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::gpio::{Input, Level, Output, OutputDrive, Pull}; use embassy_nrf::rng::Rng; use embassy_nrf::spim::{self, Spim}; use embassy_nrf::{bind_interrupts, peripherals}; @@ -27,9 +27,9 @@ bind_interrupts!(struct Irqs { async fn wifi_task( runner: hosted::Runner< 'static, - ExclusiveDevice<Spim<'static, peripherals::SPI3>, Output<'static, peripherals::P0_31>, Delay>, - Input<'static, AnyPin>, - Output<'static, peripherals::P1_05>, + ExclusiveDevice<Spim<'static, peripherals::SPI3>, Output<'static>, Delay>, + Input<'static>, + Output<'static>, >, ) -> ! { runner.run().await @@ -50,8 +50,8 @@ async fn main(spawner: Spawner) { 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 handshake = Input::new(p.P1_01, Pull::Up); + let ready = Input::new(p.P1_04, Pull::None); let reset = Output::new(p.P1_05, Level::Low, OutputDrive::Standard); let mut config = spim::Config::default(); diff --git a/examples/nrf5340/Cargo.toml b/examples/nrf5340/Cargo.toml index 56b9c8018..24aa560d5 100644 --- a/examples/nrf5340/Cargo.toml +++ b/examples/nrf5340/Cargo.toml @@ -24,7 +24,7 @@ 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.1" -usbd-hid = "0.6.0" +usbd-hid = "0.7.0" serde = { version = "1.0.136", default-features = false } [profile.release] diff --git a/examples/rp/Cargo.toml b/examples/rp/Cargo.toml index e1092dba4..585349506 100644 --- a/examples/rp/Cargo.toml +++ b/examples/rp/Cargo.toml @@ -36,7 +36,7 @@ display-interface = "0.4.1" byte-slice-cast = { version = "1.2.0", default-features = false } smart-leds = "0.3.0" heapless = "0.8" -usbd-hid = "0.6.1" +usbd-hid = "0.7.0" embedded-hal-1 = { package = "embedded-hal", version = "1.0" } embedded-hal-async = "1.0" diff --git a/examples/rp/src/bin/blinky_two_tasks.rs b/examples/rp/src/bin/blinky_two_tasks.rs index a03f3a592..a57b513d6 100644 --- a/examples/rp/src/bin/blinky_two_tasks.rs +++ b/examples/rp/src/bin/blinky_two_tasks.rs @@ -14,7 +14,7 @@ use embassy_time::{Duration, Ticker}; use gpio::{AnyPin, Level, Output}; use {defmt_rtt as _, panic_probe as _}; -type LedType = Mutex<ThreadModeRawMutex, Option<Output<'static, AnyPin>>>; +type LedType = Mutex<ThreadModeRawMutex, Option<Output<'static>>>; static LED: LedType = Mutex::new(None); #[embassy_executor::main] diff --git a/examples/rp/src/bin/debounce.rs b/examples/rp/src/bin/debounce.rs new file mode 100644 index 000000000..0077f19fc --- /dev/null +++ b/examples/rp/src/bin/debounce.rs @@ -0,0 +1,80 @@ +//! This example shows the ease of debouncing a button with async rust. +//! Hook up a button or switch between pin 9 and ground. + +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_rp::gpio::{Input, Level, Pull}; +use embassy_time::{with_deadline, Duration, Instant, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +pub struct Debouncer<'a> { + input: Input<'a>, + debounce: Duration, +} + +impl<'a> Debouncer<'a> { + pub fn new(input: Input<'a>, debounce: Duration) -> Self { + Self { input, debounce } + } + + pub async fn debounce(&mut self) -> Level { + loop { + let l1 = self.input.get_level(); + + self.input.wait_for_any_edge().await; + + Timer::after(self.debounce).await; + + let l2 = self.input.get_level(); + if l1 != l2 { + break l2; + } + } + } +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_rp::init(Default::default()); + let mut btn = Debouncer::new(Input::new(p.PIN_9, Pull::Up), Duration::from_millis(20)); + + info!("Debounce Demo"); + + loop { + // button pressed + btn.debounce().await; + let start = Instant::now(); + info!("Button Press"); + + match with_deadline(start + Duration::from_secs(1), btn.debounce()).await { + // Button Released < 1s + Ok(_) => { + info!("Button pressed for: {}ms", start.elapsed().as_millis()); + continue; + } + // button held for > 1s + Err(_) => { + info!("Button Held"); + } + } + + match with_deadline(start + Duration::from_secs(5), btn.debounce()).await { + // Button released <5s + Ok(_) => { + info!("Button pressed for: {}ms", start.elapsed().as_millis()); + continue; + } + // button held for > >5s + Err(_) => { + info!("Button Long Held"); + } + } + + // wait for button release before handling another press + btn.debounce().await; + info!("Button pressed for: {}ms", start.elapsed().as_millis()); + } +} diff --git a/examples/rp/src/bin/ethernet_w5500_multisocket.rs b/examples/rp/src/bin/ethernet_w5500_multisocket.rs index a16ea0007..bd52cadca 100644 --- a/examples/rp/src/bin/ethernet_w5500_multisocket.rs +++ b/examples/rp/src/bin/ethernet_w5500_multisocket.rs @@ -13,7 +13,7 @@ use embassy_net_wiznet::chip::W5500; use embassy_net_wiznet::*; 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::peripherals::SPI0; use embassy_rp::spi::{Async, Config as SpiConfig, Spi}; use embassy_time::{Delay, Duration}; use embedded_hal_bus::spi::ExclusiveDevice; @@ -27,9 +27,9 @@ async fn ethernet_task( runner: Runner< 'static, W5500, - ExclusiveDevice<Spi<'static, SPI0, Async>, Output<'static, PIN_17>, Delay>, - Input<'static, PIN_21>, - Output<'static, PIN_20>, + ExclusiveDevice<Spi<'static, SPI0, Async>, Output<'static>, Delay>, + Input<'static>, + Output<'static>, >, ) -> ! { runner.run().await diff --git a/examples/rp/src/bin/ethernet_w5500_tcp_client.rs b/examples/rp/src/bin/ethernet_w5500_tcp_client.rs index 975b3d385..3e4fbd2e6 100644 --- a/examples/rp/src/bin/ethernet_w5500_tcp_client.rs +++ b/examples/rp/src/bin/ethernet_w5500_tcp_client.rs @@ -15,7 +15,7 @@ use embassy_net_wiznet::chip::W5500; use embassy_net_wiznet::*; 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::peripherals::SPI0; use embassy_rp::spi::{Async, Config as SpiConfig, Spi}; use embassy_time::{Delay, Duration, Timer}; use embedded_hal_bus::spi::ExclusiveDevice; @@ -29,9 +29,9 @@ async fn ethernet_task( runner: Runner< 'static, W5500, - ExclusiveDevice<Spi<'static, SPI0, Async>, Output<'static, PIN_17>, Delay>, - Input<'static, PIN_21>, - Output<'static, PIN_20>, + ExclusiveDevice<Spi<'static, SPI0, Async>, Output<'static>, Delay>, + Input<'static>, + Output<'static>, >, ) -> ! { runner.run().await diff --git a/examples/rp/src/bin/ethernet_w5500_tcp_server.rs b/examples/rp/src/bin/ethernet_w5500_tcp_server.rs index 489af2c76..5532851f3 100644 --- a/examples/rp/src/bin/ethernet_w5500_tcp_server.rs +++ b/examples/rp/src/bin/ethernet_w5500_tcp_server.rs @@ -14,7 +14,7 @@ use embassy_net_wiznet::chip::W5500; use embassy_net_wiznet::*; 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::peripherals::SPI0; use embassy_rp::spi::{Async, Config as SpiConfig, Spi}; use embassy_time::{Delay, Duration}; use embedded_hal_bus::spi::ExclusiveDevice; @@ -28,9 +28,9 @@ async fn ethernet_task( runner: Runner< 'static, W5500, - ExclusiveDevice<Spi<'static, SPI0, Async>, Output<'static, PIN_17>, Delay>, - Input<'static, PIN_21>, - Output<'static, PIN_20>, + ExclusiveDevice<Spi<'static, SPI0, Async>, Output<'static>, Delay>, + Input<'static>, + Output<'static>, >, ) -> ! { runner.run().await diff --git a/examples/rp/src/bin/ethernet_w5500_udp.rs b/examples/rp/src/bin/ethernet_w5500_udp.rs index 41bd7d077..adb1d8941 100644 --- a/examples/rp/src/bin/ethernet_w5500_udp.rs +++ b/examples/rp/src/bin/ethernet_w5500_udp.rs @@ -14,7 +14,7 @@ use embassy_net_wiznet::chip::W5500; use embassy_net_wiznet::*; 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::peripherals::SPI0; use embassy_rp::spi::{Async, Config as SpiConfig, Spi}; use embassy_time::Delay; use embedded_hal_bus::spi::ExclusiveDevice; @@ -27,9 +27,9 @@ async fn ethernet_task( runner: Runner< 'static, W5500, - ExclusiveDevice<Spi<'static, SPI0, Async>, Output<'static, PIN_17>, Delay>, - Input<'static, PIN_21>, - Output<'static, PIN_20>, + ExclusiveDevice<Spi<'static, SPI0, Async>, Output<'static>, Delay>, + Input<'static>, + Output<'static>, >, ) -> ! { runner.run().await diff --git a/examples/rp/src/bin/i2c_slave.rs b/examples/rp/src/bin/i2c_slave.rs index 479f9a16a..9fffb4646 100644 --- a/examples/rp/src/bin/i2c_slave.rs +++ b/examples/rp/src/bin/i2c_slave.rs @@ -26,7 +26,7 @@ async fn device_task(mut dev: i2c_slave::I2cSlave<'static, I2C1>) -> ! { loop { let mut buf = [0u8; 128]; match dev.listen(&mut buf).await { - Ok(i2c_slave::Command::GeneralCall(len)) => info!("Device recieved general call write: {}", buf[..len]), + Ok(i2c_slave::Command::GeneralCall(len)) => info!("Device received general call write: {}", buf[..len]), Ok(i2c_slave::Command::Read) => loop { match dev.respond_to_read(&[state]).await { Ok(x) => match x { @@ -40,9 +40,9 @@ async fn device_task(mut dev: i2c_slave::I2cSlave<'static, I2C1>) -> ! { Err(e) => error!("error while responding {}", e), } }, - Ok(i2c_slave::Command::Write(len)) => info!("Device recieved write: {}", buf[..len]), + Ok(i2c_slave::Command::Write(len)) => info!("Device received write: {}", buf[..len]), Ok(i2c_slave::Command::WriteRead(len)) => { - info!("device recieved write read: {:x}", buf[..len]); + info!("device received write read: {:x}", buf[..len]); match buf[0] { // Set the state 0xC2 => { @@ -110,7 +110,7 @@ async fn main(spawner: Spawner) { let c_sda = p.PIN_1; let c_scl = p.PIN_0; let mut config = i2c::Config::default(); - config.frequency = 5_000; + config.frequency = 1_000_000; let controller = i2c::I2c::new_async(p.I2C0, c_sda, c_scl, Irqs, config); unwrap!(spawner.spawn(controller_task(controller))); diff --git a/examples/rp/src/bin/multicore.rs b/examples/rp/src/bin/multicore.rs index a1678d99a..c7b087476 100644 --- a/examples/rp/src/bin/multicore.rs +++ b/examples/rp/src/bin/multicore.rs @@ -9,7 +9,6 @@ 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::Timer; @@ -52,7 +51,7 @@ async fn core0_task() { } #[embassy_executor::task] -async fn core1_task(mut led: Output<'static, PIN_25>) { +async fn core1_task(mut led: Output<'static>) { info!("Hello from core 1"); loop { match CHANNEL.receive().await { diff --git a/examples/rp/src/bin/pio_i2s.rs b/examples/rp/src/bin/pio_i2s.rs new file mode 100644 index 000000000..cf60e5b30 --- /dev/null +++ b/examples/rp/src/bin/pio_i2s.rs @@ -0,0 +1,125 @@ +//! This example shows generating audio and sending it to a connected i2s DAC using the PIO +//! module of the RP2040. +//! +//! Connect the i2s DAC as follows: +//! bclk : GPIO 18 +//! lrc : GPIO 19 +//! din : GPIO 20 +//! Then hold down the boot select button to trigger a rising triangle waveform. + +#![no_std] +#![no_main] + +use core::mem; + +use embassy_executor::Spawner; +use embassy_rp::peripherals::PIO0; +use embassy_rp::pio::{Config, FifoJoin, InterruptHandler, Pio, ShiftConfig, ShiftDirection}; +use embassy_rp::{bind_interrupts, Peripheral}; +use fixed::traits::ToFixed; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + PIO0_IRQ_0 => InterruptHandler<PIO0>; +}); + +const SAMPLE_RATE: u32 = 48_000; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut p = embassy_rp::init(Default::default()); + + // Setup pio state machine for i2s output + let mut pio = Pio::new(p.PIO0, Irqs); + + #[rustfmt::skip] + let pio_program = pio_proc::pio_asm!( + ".side_set 2", + " set x, 14 side 0b01", // side 0bWB - W = Word Clock, B = Bit Clock + "left_data:", + " out pins, 1 side 0b00", + " jmp x-- left_data side 0b01", + " out pins 1 side 0b10", + " set x, 14 side 0b11", + "right_data:", + " out pins 1 side 0b10", + " jmp x-- right_data side 0b11", + " out pins 1 side 0b00", + ); + + let bit_clock_pin = p.PIN_18; + let left_right_clock_pin = p.PIN_19; + let data_pin = p.PIN_20; + + let data_pin = pio.common.make_pio_pin(data_pin); + let bit_clock_pin = pio.common.make_pio_pin(bit_clock_pin); + let left_right_clock_pin = pio.common.make_pio_pin(left_right_clock_pin); + + let cfg = { + let mut cfg = Config::default(); + cfg.use_program( + &pio.common.load_program(&pio_program.program), + &[&bit_clock_pin, &left_right_clock_pin], + ); + cfg.set_out_pins(&[&data_pin]); + const BIT_DEPTH: u32 = 16; + const CHANNELS: u32 = 2; + let clock_frequency = SAMPLE_RATE * BIT_DEPTH * CHANNELS; + cfg.clock_divider = (125_000_000. / clock_frequency as f64 / 2.).to_fixed(); + cfg.shift_out = ShiftConfig { + threshold: 32, + direction: ShiftDirection::Left, + auto_fill: true, + }; + // join fifos to have twice the time to start the next dma transfer + cfg.fifo_join = FifoJoin::TxOnly; + cfg + }; + pio.sm0.set_config(&cfg); + pio.sm0.set_pin_dirs( + embassy_rp::pio::Direction::Out, + &[&data_pin, &left_right_clock_pin, &bit_clock_pin], + ); + + // create two audio buffers (back and front) which will take turns being + // filled with new audio data and being sent to the pio fifo using dma + const BUFFER_SIZE: usize = 960; + static DMA_BUFFER: StaticCell<[u32; BUFFER_SIZE * 2]> = StaticCell::new(); + let dma_buffer = DMA_BUFFER.init_with(|| [0u32; BUFFER_SIZE * 2]); + let (mut back_buffer, mut front_buffer) = dma_buffer.split_at_mut(BUFFER_SIZE); + + // start pio state machine + pio.sm0.set_enable(true); + let tx = pio.sm0.tx(); + let mut dma_ref = p.DMA_CH0.into_ref(); + + let mut fade_value: i32 = 0; + let mut phase: i32 = 0; + + loop { + // trigger transfer of front buffer data to the pio fifo + // but don't await the returned future, yet + let dma_future = tx.dma_push(dma_ref.reborrow(), front_buffer); + + // fade in audio when bootsel is pressed + let fade_target = if p.BOOTSEL.is_pressed() { i32::MAX } else { 0 }; + + // fill back buffer with fresh audio samples before awaiting the dma future + for s in back_buffer.iter_mut() { + // exponential approach of fade_value => fade_target + fade_value += (fade_target - fade_value) >> 14; + // generate triangle wave with amplitude and frequency based on fade value + phase = (phase + (fade_value >> 22)) & 0xffff; + let triangle_sample = (phase as i16 as i32).abs() - 16384; + let sample = (triangle_sample * (fade_value >> 15)) >> 16; + // duplicate mono sample into lower and upper half of dma word + *s = (sample as u16 as u32) * 0x10001; + } + + // now await the dma future. once the dma finishes, the next buffer needs to be queued + // within DMA_DEPTH / SAMPLE_RATE = 8 / 48000 seconds = 166us + dma_future.await; + mem::swap(&mut back_buffer, &mut front_buffer); + } +} diff --git a/examples/rp/src/bin/pio_ws2812.rs b/examples/rp/src/bin/pio_ws2812.rs index 9a97cb8a7..ac145933c 100644 --- a/examples/rp/src/bin/pio_ws2812.rs +++ b/examples/rp/src/bin/pio_ws2812.rs @@ -12,7 +12,7 @@ use embassy_rp::pio::{ Common, Config, FifoJoin, Instance, InterruptHandler, Pio, PioPin, ShiftConfig, ShiftDirection, StateMachine, }; use embassy_rp::{bind_interrupts, clocks, into_ref, Peripheral, PeripheralRef}; -use embassy_time::Timer; +use embassy_time::{Duration, Ticker, Timer}; use fixed::types::U24F8; use fixed_macro::fixed; use smart_leds::RGB8; @@ -107,6 +107,8 @@ impl<'d, P: Instance, const S: usize, const N: usize> Ws2812<'d, P, S, N> { // DMA transfer self.sm.tx().dma_push(self.dma.reborrow(), &words).await; + + Timer::after_micros(55).await; } } @@ -143,6 +145,7 @@ async fn main(_spawner: Spawner) { 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. + let mut ticker = Ticker::every(Duration::from_millis(10)); loop { for j in 0..(256 * 5) { debug!("New Colors:"); @@ -152,7 +155,7 @@ async fn main(_spawner: Spawner) { } ws2812.write(&data).await; - Timer::after_millis(10).await; + ticker.next().await; } } } diff --git a/examples/rp/src/bin/usb_hid_mouse.rs b/examples/rp/src/bin/usb_hid_mouse.rs new file mode 100644 index 000000000..afebd8813 --- /dev/null +++ b/examples/rp/src/bin/usb_hid_mouse.rs @@ -0,0 +1,182 @@ +#![no_std] +#![no_main] + +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::clocks::RoscRng; +use embassy_rp::gpio::{Input, Pull}; +use embassy_rp::peripherals::USB; +use embassy_rp::usb::{Driver, InterruptHandler}; +use embassy_time::Timer; +use embassy_usb::class::hid::{HidReaderWriter, ReportId, RequestHandler, State}; +use embassy_usb::control::OutResponse; +use embassy_usb::{Builder, Config, Handler}; +use rand::Rng; +use usbd_hid::descriptor::{MouseReport, SerializedDescriptor}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USBCTRL_IRQ => InterruptHandler<USB>; +}); + +#[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: MouseReport::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); + + // Enable the schmitt trigger to slightly debounce. + signal_pin.set_schmitt(true); + + let (reader, mut writer) = hid.split(); + + // Do stuff with the class! + let in_fut = async { + let mut rng = RoscRng; + + loop { + // every 1 second + _ = Timer::after_secs(1).await; + let report = MouseReport { + buttons: 0, + x: rng.gen_range(-100..100), // random small x movement + y: rng.gen_range(-100..100), // random small y movement + wheel: 0, + pan: 0, + }; + // Send the report. + 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<usize> { + 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<ReportId>, dur: u32) { + info!("Set idle rate for {:?} to {:?}", id, dur); + } + + fn get_idle_ms(&self, id: Option<ReportId>) -> Option<u32> { + 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_serial_with_logger.rs b/examples/rp/src/bin/usb_serial_with_logger.rs new file mode 100644 index 000000000..4ba4fc25c --- /dev/null +++ b/examples/rp/src/bin/usb_serial_with_logger.rs @@ -0,0 +1,117 @@ +//! This example shows how to use USB (Universal Serial Bus) in the RP2040 chip as well as how to create multiple usb classes for one device +//! +//! This creates a USB serial port that echos. It will also print out logging information on a separate serial device + +#![no_std] +#![no_main] + +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<USB>; +}); + +#[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 logger_state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut device_descriptor, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], // no msos descriptors + &mut control_buf, + ); + + // Create classes on the builder. + let mut class = CdcAcmClass::new(&mut builder, &mut state, 64); + + // Create a class for the logger + let logger_class = CdcAcmClass::new(&mut builder, &mut logger_state, 64); + + // Creates the logger and returns the logger future + // Note: You'll need to use log::info! afterwards instead of info! for this to work (this also applies to all the other log::* macros) + let log_fut = embassy_usb_logger::with_class!(1024, log::LevelFilter::Info, logger_class); + + // 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; + log::info!("Connected"); + let _ = echo(&mut class).await; + log::info!("Disconnected"); + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, join(echo_fut, log_fut)).await; +} + +struct Disconnected {} + +impl From<EndpointError> 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/wifi_ap_tcp_server.rs b/examples/rp/src/bin/wifi_ap_tcp_server.rs index 1bd75607e..b60852359 100644 --- a/examples/rp/src/bin/wifi_ap_tcp_server.rs +++ b/examples/rp/src/bin/wifi_ap_tcp_server.rs @@ -14,7 +14,7 @@ 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::peripherals::{DMA_CH0, PIO0}; use embassy_rp::pio::{InterruptHandler, Pio}; use embassy_time::Duration; use embedded_io_async::Write; @@ -26,9 +26,7 @@ bind_interrupts!(struct Irqs { }); #[embassy_executor::task] -async fn wifi_task( - runner: cyw43::Runner<'static, Output<'static, PIN_23>, PioSpi<'static, PIN_25, PIO0, 0, DMA_CH0>>, -) -> ! { +async fn wifi_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { runner.run().await } diff --git a/examples/rp/src/bin/wifi_blinky.rs b/examples/rp/src/bin/wifi_blinky.rs index 1ed74993c..18eefe41f 100644 --- a/examples/rp/src/bin/wifi_blinky.rs +++ b/examples/rp/src/bin/wifi_blinky.rs @@ -10,7 +10,7 @@ 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::peripherals::{DMA_CH0, PIO0}; use embassy_rp::pio::{InterruptHandler, Pio}; use embassy_time::{Duration, Timer}; use static_cell::StaticCell; @@ -21,9 +21,7 @@ bind_interrupts!(struct Irqs { }); #[embassy_executor::task] -async fn wifi_task( - runner: cyw43::Runner<'static, Output<'static, PIN_23>, PioSpi<'static, PIN_25, PIO0, 0, DMA_CH0>>, -) -> ! { +async fn wifi_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { runner.run().await } diff --git a/examples/rp/src/bin/wifi_scan.rs b/examples/rp/src/bin/wifi_scan.rs index 45bb5b76c..e0f85a6b0 100644 --- a/examples/rp/src/bin/wifi_scan.rs +++ b/examples/rp/src/bin/wifi_scan.rs @@ -13,7 +13,7 @@ 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::peripherals::{DMA_CH0, PIO0}; use embassy_rp::pio::{InterruptHandler, Pio}; use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _}; @@ -23,9 +23,7 @@ bind_interrupts!(struct Irqs { }); #[embassy_executor::task] -async fn wifi_task( - runner: cyw43::Runner<'static, Output<'static, PIN_23>, PioSpi<'static, PIN_25, PIO0, 0, DMA_CH0>>, -) -> ! { +async fn wifi_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { runner.run().await } @@ -65,7 +63,7 @@ async fn main(spawner: Spawner) { .set_power_management(cyw43::PowerManagementMode::PowerSave) .await; - let mut scanner = control.scan().await; + let mut scanner = control.scan(Default::default()).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 index c346f1ded..f1afc4a00 100644 --- a/examples/rp/src/bin/wifi_tcp_server.rs +++ b/examples/rp/src/bin/wifi_tcp_server.rs @@ -14,7 +14,7 @@ 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::peripherals::{DMA_CH0, PIO0}; use embassy_rp::pio::{InterruptHandler, Pio}; use embassy_time::{Duration, Timer}; use embedded_io_async::Write; @@ -29,9 +29,7 @@ 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>>, -) -> ! { +async fn wifi_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { runner.run().await } diff --git a/examples/std/README.md b/examples/std/README.md index adc795928..e3a59d6ea 100644 --- a/examples/std/README.md +++ b/examples/std/README.md @@ -13,11 +13,11 @@ 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` +Second, have something listening there. For example `nc -lp 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/stm32c0/src/bin/button_exti.rs b/examples/stm32c0/src/bin/button_exti.rs index 1e970fdd6..34a08bbc6 100644 --- a/examples/stm32c0/src/bin/button_exti.rs +++ b/examples/stm32c0/src/bin/button_exti.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; -use embassy_stm32::gpio::{Input, Pull}; +use embassy_stm32::gpio::Pull; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] @@ -12,8 +12,7 @@ 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); + let mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::Up); info!("Press the USER button..."); diff --git a/examples/stm32f0/Cargo.toml b/examples/stm32f0/Cargo.toml index 71b0eb683..c74980dc4 100644 --- a/examples/stm32f0/Cargo.toml +++ b/examples/stm32f0/Cargo.toml @@ -13,7 +13,7 @@ cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-sing cortex-m-rt = "0.7.0" defmt = "0.3" defmt-rtt = "0.4" -panic-probe = "0.3" +panic-probe = { version = "0.3", features = ["print-defmt"] } embassy-sync = { version = "0.5.0", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } diff --git a/examples/stm32f0/src/bin/adc.rs b/examples/stm32f0/src/bin/adc.rs index 8fef062b3..c2fb143cd 100644 --- a/examples/stm32f0/src/bin/adc.rs +++ b/examples/stm32f0/src/bin/adc.rs @@ -19,7 +19,7 @@ async fn main(_spawner: Spawner) { info!("Hello World!"); let mut adc = Adc::new(p.ADC, Irqs, &mut Delay); - adc.set_sample_time(SampleTime::Cycles71_5); + adc.set_sample_time(SampleTime::CYCLES71_5); let mut pin = p.PA1; let mut vrefint = adc.enable_vref(&mut Delay); diff --git a/examples/stm32f0/src/bin/button_controlled_blink.rs b/examples/stm32f0/src/bin/button_controlled_blink.rs index 360d153c3..4465483d9 100644 --- a/examples/stm32f0/src/bin/button_controlled_blink.rs +++ b/examples/stm32f0/src/bin/button_controlled_blink.rs @@ -8,7 +8,7 @@ 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_stm32::gpio::{AnyPin, Level, Output, Pin, Pull, Speed}; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; @@ -36,8 +36,7 @@ async fn main(spawner: Spawner) { // 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); + let mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::None); // Create and initialize a delay variable to manage delay loop let mut del_var = 2000; diff --git a/examples/stm32f0/src/bin/button_exti.rs b/examples/stm32f0/src/bin/button_exti.rs index ce17c1a48..fd615a215 100644 --- a/examples/stm32f0/src/bin/button_exti.rs +++ b/examples/stm32f0/src/bin/button_exti.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; -use embassy_stm32::gpio::{Input, Pull}; +use embassy_stm32::gpio::Pull; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] @@ -13,8 +13,7 @@ async fn main(_spawner: Spawner) { 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); + let mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::Down); info!("Press the USER button..."); loop { diff --git a/examples/stm32f1/src/bin/hello.rs b/examples/stm32f1/src/bin/hello.rs index 7b761ecc1..3c295612c 100644 --- a/examples/stm32f1/src/bin/hello.rs +++ b/examples/stm32f1/src/bin/hello.rs @@ -3,15 +3,13 @@ use defmt::info; use embassy_executor::Spawner; -use embassy_stm32::time::Hertz; use embassy_stm32::Config; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] async fn main(_spawner: Spawner) -> ! { - let mut config = Config::default(); - config.rcc.sys_ck = Some(Hertz(36_000_000)); + let config = Config::default(); let _p = embassy_stm32::init(config); loop { diff --git a/examples/stm32f1/src/bin/usb_serial.rs b/examples/stm32f1/src/bin/usb_serial.rs index e28381893..1ae6c1dee 100644 --- a/examples/stm32f1/src/bin/usb_serial.rs +++ b/examples/stm32f1/src/bin/usb_serial.rs @@ -21,9 +21,23 @@ bind_interrupts!(struct Irqs { #[embassy_executor::main] async fn main(_spawner: Spawner) { let mut config = Config::default(); - config.rcc.hse = Some(Hertz(8_000_000)); - config.rcc.sys_ck = Some(Hertz(48_000_000)); - config.rcc.pclk1 = Some(Hertz(24_000_000)); + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + // Oscillator for bluepill, Bypass for nucleos. + mode: HseMode::Oscillator, + }); + config.rcc.pll = Some(Pll { + src: PllSource::HSE, + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL9, + }); + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV2; + config.rcc.apb2_pre = APBPrescaler::DIV1; + } let mut p = embassy_stm32::init(config); info!("Hello World!"); diff --git a/examples/stm32f3/src/bin/button_events.rs b/examples/stm32f3/src/bin/button_events.rs index 2f7da4ef5..f5ed5d2c9 100644 --- a/examples/stm32f3/src/bin/button_events.rs +++ b/examples/stm32f3/src/bin/button_events.rs @@ -12,21 +12,20 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; -use embassy_stm32::gpio::{AnyPin, Input, Level, Output, Pin, Pull, Speed}; -use embassy_stm32::peripherals::PA0; +use embassy_stm32::gpio::{Level, Output, Pull, Speed}; use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex; use embassy_sync::channel::Channel; use embassy_time::{with_timeout, Duration, Timer}; use {defmt_rtt as _, panic_probe as _}; struct Leds<'a> { - leds: [Output<'a, AnyPin>; 8], + leds: [Output<'a>; 8], direction: i8, current_led: usize, } impl<'a> Leds<'a> { - fn new(pins: [Output<'a, AnyPin>; 8]) -> Self { + fn new(pins: [Output<'a>; 8]) -> Self { Self { leds: pins, direction: 1, @@ -100,18 +99,17 @@ static CHANNEL: Channel<ThreadModeRawMutex, ButtonEvent, 4> = Channel::new(); #[embassy_executor::main] async fn main(spawner: Spawner) { let p = embassy_stm32::init(Default::default()); - let button = Input::new(p.PA0, Pull::Down); - let button = ExtiInput::new(button, p.EXTI0); + let button = ExtiInput::new(p.PA0, p.EXTI0, Pull::Down); info!("Press the USER button..."); let leds = [ - Output::new(p.PE9.degrade(), Level::Low, Speed::Low), - Output::new(p.PE10.degrade(), Level::Low, Speed::Low), - Output::new(p.PE11.degrade(), Level::Low, Speed::Low), - Output::new(p.PE12.degrade(), Level::Low, Speed::Low), - Output::new(p.PE13.degrade(), Level::Low, Speed::Low), - Output::new(p.PE14.degrade(), Level::Low, Speed::Low), - Output::new(p.PE15.degrade(), Level::Low, Speed::Low), - Output::new(p.PE8.degrade(), Level::Low, Speed::Low), + Output::new(p.PE9, Level::Low, Speed::Low), + Output::new(p.PE10, Level::Low, Speed::Low), + Output::new(p.PE11, Level::Low, Speed::Low), + Output::new(p.PE12, Level::Low, Speed::Low), + Output::new(p.PE13, Level::Low, Speed::Low), + Output::new(p.PE14, Level::Low, Speed::Low), + Output::new(p.PE15, Level::Low, Speed::Low), + Output::new(p.PE8, Level::Low, Speed::Low), ]; let leds = Leds::new(leds); @@ -127,7 +125,7 @@ async fn led_blinker(mut leds: Leds<'static>) { } #[embassy_executor::task] -async fn button_waiter(mut button: ExtiInput<'static, PA0>) { +async fn button_waiter(mut button: ExtiInput<'static>) { const DOUBLE_CLICK_DELAY: u64 = 250; const HOLD_DELAY: u64 = 1000; diff --git a/examples/stm32f3/src/bin/button_exti.rs b/examples/stm32f3/src/bin/button_exti.rs index 86ff68492..a55530e0e 100644 --- a/examples/stm32f3/src/bin/button_exti.rs +++ b/examples/stm32f3/src/bin/button_exti.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; -use embassy_stm32::gpio::{Input, Pull}; +use embassy_stm32::gpio::Pull; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] @@ -12,8 +12,7 @@ async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); info!("Hello World!"); - let button = Input::new(p.PA0, Pull::Down); - let mut button = ExtiInput::new(button, p.EXTI0); + let mut button = ExtiInput::new(p.PA0, p.EXTI0, Pull::Down); info!("Press the USER button..."); diff --git a/examples/stm32f3/src/bin/hello.rs b/examples/stm32f3/src/bin/hello.rs index fd54da53d..3c295612c 100644 --- a/examples/stm32f3/src/bin/hello.rs +++ b/examples/stm32f3/src/bin/hello.rs @@ -3,16 +3,13 @@ use defmt::info; use embassy_executor::Spawner; -use embassy_stm32::time::Hertz; use embassy_stm32::Config; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] async fn main(_spawner: Spawner) -> ! { - let mut config = Config::default(); - config.rcc.hse = Some(Hertz(8_000_000)); - config.rcc.sysclk = Some(Hertz(16_000_000)); + let config = Config::default(); let _p = embassy_stm32::init(config); loop { diff --git a/examples/stm32f3/src/bin/usb_serial.rs b/examples/stm32f3/src/bin/usb_serial.rs index cf9ecedfa..ee1c43afd 100644 --- a/examples/stm32f3/src/bin/usb_serial.rs +++ b/examples/stm32f3/src/bin/usb_serial.rs @@ -21,11 +21,22 @@ bind_interrupts!(struct Irqs { #[embassy_executor::main] async fn main(_spawner: Spawner) { let mut config = Config::default(); - config.rcc.hse = Some(mhz(8)); - config.rcc.sysclk = Some(mhz(48)); - config.rcc.pclk1 = Some(mhz(24)); - config.rcc.pclk2 = Some(mhz(24)); - config.rcc.pll48 = true; + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: mhz(8), + mode: HseMode::Bypass, + }); + config.rcc.pll = Some(Pll { + src: PllSource::HSE, + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL9, + }); + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV2; + config.rcc.apb2_pre = APBPrescaler::DIV1; + } let p = embassy_stm32::init(config); info!("Hello World!"); diff --git a/examples/stm32f334/src/bin/adc.rs b/examples/stm32f334/src/bin/adc.rs index 063ee9dac..bd126ce68 100644 --- a/examples/stm32f334/src/bin/adc.rs +++ b/examples/stm32f334/src/bin/adc.rs @@ -5,7 +5,6 @@ use defmt::info; use embassy_executor::Spawner; use embassy_stm32::adc::{Adc, SampleTime}; use embassy_stm32::peripherals::ADC1; -use embassy_stm32::rcc::{AdcClockSource, Adcpres}; use embassy_stm32::time::mhz; use embassy_stm32::{adc, bind_interrupts, Config}; use embassy_time::{Delay, Timer}; @@ -18,19 +17,30 @@ bind_interrupts!(struct Irqs { #[embassy_executor::main] async fn main(_spawner: Spawner) -> ! { let mut config = Config::default(); - config.rcc.sysclk = Some(mhz(64)); - config.rcc.hclk = Some(mhz(64)); - config.rcc.pclk1 = Some(mhz(32)); - config.rcc.pclk2 = Some(mhz(64)); - config.rcc.adc = Some(AdcClockSource::Pll(Adcpres::DIV1)); - + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: mhz(8), + mode: HseMode::Bypass, + }); + config.rcc.pll = Some(Pll { + src: PllSource::HSE, + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL9, + }); + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV2; + config.rcc.apb2_pre = APBPrescaler::DIV1; + config.rcc.adc = AdcClockSource::Pll(AdcPllPrescaler::DIV1); + } let mut p = embassy_stm32::init(config); info!("create adc..."); let mut adc = Adc::new(p.ADC1, Irqs, &mut Delay); - adc.set_sample_time(SampleTime::Cycles601_5); + adc.set_sample_time(SampleTime::CYCLES601_5); info!("enable vrefint..."); diff --git a/examples/stm32f334/src/bin/hello.rs b/examples/stm32f334/src/bin/hello.rs index fd54da53d..3c295612c 100644 --- a/examples/stm32f334/src/bin/hello.rs +++ b/examples/stm32f334/src/bin/hello.rs @@ -3,16 +3,13 @@ use defmt::info; use embassy_executor::Spawner; -use embassy_stm32::time::Hertz; use embassy_stm32::Config; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] async fn main(_spawner: Spawner) -> ! { - let mut config = Config::default(); - config.rcc.hse = Some(Hertz(8_000_000)); - config.rcc.sysclk = Some(Hertz(16_000_000)); + let config = Config::default(); let _p = embassy_stm32::init(config); loop { diff --git a/examples/stm32f334/src/bin/opamp.rs b/examples/stm32f334/src/bin/opamp.rs index 850a0e335..a5c710aa2 100644 --- a/examples/stm32f334/src/bin/opamp.rs +++ b/examples/stm32f334/src/bin/opamp.rs @@ -6,7 +6,6 @@ use embassy_executor::Spawner; use embassy_stm32::adc::{Adc, SampleTime}; use embassy_stm32::opamp::{OpAmp, OpAmpGain}; use embassy_stm32::peripherals::ADC2; -use embassy_stm32::rcc::{AdcClockSource, Adcpres}; use embassy_stm32::time::mhz; use embassy_stm32::{adc, bind_interrupts, Config}; use embassy_time::{Delay, Timer}; @@ -19,12 +18,23 @@ bind_interrupts!(struct Irqs { #[embassy_executor::main] async fn main(_spawner: Spawner) -> ! { let mut config = Config::default(); - config.rcc.sysclk = Some(mhz(64)); - config.rcc.hclk = Some(mhz(64)); - config.rcc.pclk1 = Some(mhz(32)); - config.rcc.pclk2 = Some(mhz(64)); - config.rcc.adc = Some(AdcClockSource::Pll(Adcpres::DIV1)); - + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: mhz(8), + mode: HseMode::Bypass, + }); + config.rcc.pll = Some(Pll { + src: PllSource::HSE, + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL9, + }); + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV2; + config.rcc.apb2_pre = APBPrescaler::DIV1; + config.rcc.adc = AdcClockSource::Pll(AdcPllPrescaler::DIV1); + } let mut p = embassy_stm32::init(config); info!("create adc..."); @@ -32,7 +42,7 @@ async fn main(_spawner: Spawner) -> ! { let mut adc = Adc::new(p.ADC2, Irqs, &mut Delay); let mut opamp = OpAmp::new(p.OPAMP2); - adc.set_sample_time(SampleTime::Cycles601_5); + adc.set_sample_time(SampleTime::CYCLES601_5); info!("enable vrefint..."); diff --git a/examples/stm32f334/src/bin/pwm.rs b/examples/stm32f334/src/bin/pwm.rs index c149cad92..e6d1a6c02 100644 --- a/examples/stm32f334/src/bin/pwm.rs +++ b/examples/stm32f334/src/bin/pwm.rs @@ -4,7 +4,6 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::hrtim::*; -use embassy_stm32::rcc::HrtimClockSource; use embassy_stm32::time::{khz, mhz}; use embassy_stm32::Config; use embassy_time::Timer; @@ -12,14 +11,27 @@ use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] async fn main(_spawner: Spawner) { - let mut config: Config = Default::default(); - config.rcc.sysclk = Some(mhz(64)); - config.rcc.hclk = Some(mhz(64)); - config.rcc.pclk1 = Some(mhz(32)); - config.rcc.pclk2 = Some(mhz(64)); - config.rcc.hrtim = HrtimClockSource::PllClk; + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: mhz(8), + mode: HseMode::Bypass, + }); + config.rcc.pll = Some(Pll { + src: PllSource::HSE, + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL9, + }); + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV2; + config.rcc.apb2_pre = APBPrescaler::DIV1; + config.rcc.mux.hrtim1sw = embassy_stm32::rcc::mux::Timsw::PLL1_P; + } let p = embassy_stm32::init(config); + info!("Hello World!"); let ch1 = PwmPin::new_cha(p.PA8); diff --git a/examples/stm32f4/Cargo.toml b/examples/stm32f4/Cargo.toml index cd46fc85b..512158bef 100644 --- a/examples/stm32f4/Cargo.toml +++ b/examples/stm32f4/Cargo.toml @@ -27,6 +27,7 @@ heapless = { version = "0.8", default-features = false } nb = "1.0.0" embedded-storage = "0.3.1" micromath = "2.0.0" +usbd-hid = "0.7.0" static_cell = "2" chrono = { version = "^0.4", default-features = false} diff --git a/examples/stm32f4/src/bin/button_exti.rs b/examples/stm32f4/src/bin/button_exti.rs index 67751187d..2a546dac5 100644 --- a/examples/stm32f4/src/bin/button_exti.rs +++ b/examples/stm32f4/src/bin/button_exti.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; -use embassy_stm32::gpio::{Input, Pull}; +use embassy_stm32::gpio::Pull; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] @@ -12,8 +12,7 @@ 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); + let mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::Down); info!("Press the USER button..."); diff --git a/examples/stm32f4/src/bin/rtc.rs b/examples/stm32f4/src/bin/rtc.rs index abab07b6b..82d8a37ba 100644 --- a/examples/stm32f4/src/bin/rtc.rs +++ b/examples/stm32f4/src/bin/rtc.rs @@ -28,7 +28,7 @@ async fn main(_spawner: Spawner) { loop { let now: NaiveDateTime = rtc.now().unwrap().into(); - info!("{}", now.timestamp()); + info!("{}", now.and_utc().timestamp()); Timer::after_millis(1000).await; } diff --git a/examples/stm32f4/src/bin/usb_hid_mouse.rs b/examples/stm32f4/src/bin/usb_hid_mouse.rs new file mode 100644 index 000000000..c98792880 --- /dev/null +++ b/examples/stm32f4/src/bin/usb_hid_mouse.rs @@ -0,0 +1,148 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::time::Hertz; +use embassy_stm32::usb_otg::Driver; +use embassy_stm32::{bind_interrupts, peripherals, usb_otg, Config}; +use embassy_time::Timer; +use embassy_usb::class::hid::{HidWriter, ReportId, RequestHandler, State}; +use embassy_usb::control::OutResponse; +use embassy_usb::Builder; +use futures::future::join; +use usbd_hid::descriptor::{MouseReport, SerializedDescriptor}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + OTG_FS => usb_otg::InterruptHandler<peripherals::USB_OTG_FS>; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::Bypass, + }); + config.rcc.pll_src = PllSource::HSE; + config.rcc.pll = Some(Pll { + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL168, + divp: Some(PllPDiv::DIV2), // 8mhz / 4 * 168 / 2 = 168Mhz. + divq: Some(PllQDiv::DIV7), // 8mhz / 4 * 168 / 7 = 48Mhz. + divr: None, + }); + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV4; + config.rcc.apb2_pre = APBPrescaler::DIV2; + config.rcc.sys = Sysclk::PLL1_P; + } + 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("HID mouse 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 request_handler = MyRequestHandler {}; + + let mut state = State::new(); + + let mut builder = Builder::new( + driver, + config, + &mut device_descriptor, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], // no msos descriptors + &mut control_buf, + ); + + // Create classes on the builder. + let config = embassy_usb::class::hid::Config { + report_descriptor: MouseReport::desc(), + request_handler: Some(&request_handler), + poll_ms: 60, + max_packet_size: 8, + }; + + let mut writer = HidWriter::<_, 5>::new(&mut builder, &mut state, config); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + let usb_fut = usb.run(); + + // Do stuff with the class! + let hid_fut = async { + let mut y: i8 = 5; + loop { + Timer::after_millis(500).await; + + y = -y; + let report = MouseReport { + buttons: 0, + x: 0, + y, + wheel: 0, + pan: 0, + }; + match writer.write_serialize(&report).await { + Ok(()) => {} + Err(e) => warn!("Failed to send report: {:?}", e), + } + } + }; + + // Run everything concurrently. + // If we had made everything `'static` above instead, we could do this using separate tasks instead. + join(usb_fut, hid_fut).await; +} + +struct MyRequestHandler {} + +impl RequestHandler for MyRequestHandler { + fn get_report(&self, id: ReportId, _buf: &mut [u8]) -> Option<usize> { + 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<ReportId>, dur: u32) { + info!("Set idle rate for {:?} to {:?}", id, dur); + } + + fn get_idle_ms(&self, id: Option<ReportId>) -> Option<u32> { + info!("Get idle rate for {:?}", id); + None + } +} diff --git a/examples/stm32f4/src/bin/ws2812_pwm.rs b/examples/stm32f4/src/bin/ws2812_pwm.rs index 239709253..6122cea2d 100644 --- a/examples/stm32f4/src/bin/ws2812_pwm.rs +++ b/examples/stm32f4/src/bin/ws2812_pwm.rs @@ -91,7 +91,7 @@ async fn main(_spawner: Spawner) { loop { for &color in color_list { // with &mut, we can easily reuse same DMA channel multiple times - ws2812_pwm.gen_waveform(&mut dp.DMA1_CH2, pwm_channel, color).await; + ws2812_pwm.waveform_up(&mut dp.DMA1_CH2, pwm_channel, color).await; // ws2812 need at least 50 us low level input to confirm the input data and change it's state Timer::after_micros(50).await; // wait until ticker tick diff --git a/examples/stm32f7/.cargo/config.toml b/examples/stm32f7/.cargo/config.toml index 9088eea6e..086da2d78 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-rs chip list` -runner = "probe-rs run --chip STM32F767ZITx" +runner = "probe-rs run --chip STM32F777ZITx" [build] target = "thumbv7em-none-eabihf" diff --git a/examples/stm32f7/Cargo.toml b/examples/stm32f7/Cargo.toml index 941ba38cd..305816a2b 100644 --- a/examples/stm32f7/Cargo.toml +++ b/examples/stm32f7/Cargo.toml @@ -5,8 +5,8 @@ version = "0.1.0" license = "MIT OR Apache-2.0" [dependencies] -# Change stm32f767zi to your chip name, if necessary. -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32f767zi", "memory-x", "unstable-pac", "time-driver-any", "exti"] } +# Change stm32f777zi to your chip name, if necessary. +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "stm32f777zi", "memory-x", "unstable-pac", "time-driver-any", "exti"] } embassy-sync = { version = "0.5.0", path = "../../embassy-sync", features = ["defmt"] } embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["task-arena-size-32768", "arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] } embassy-time = { version = "0.3.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } @@ -28,6 +28,9 @@ rand_core = "0.6.3" critical-section = "1.1" embedded-storage = "0.3.1" static_cell = "2" +sha2 = { version = "0.10.8", default-features = false } +hmac = "0.12.1" +aes-gcm = {version = "0.10.3", default-features = false, features = ["aes", "heapless"] } [profile.release] debug = 2 diff --git a/examples/stm32f7/src/bin/button_exti.rs b/examples/stm32f7/src/bin/button_exti.rs index 67751187d..2a546dac5 100644 --- a/examples/stm32f7/src/bin/button_exti.rs +++ b/examples/stm32f7/src/bin/button_exti.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; -use embassy_stm32::gpio::{Input, Pull}; +use embassy_stm32::gpio::Pull; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] @@ -12,8 +12,7 @@ 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); + let mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::Down); info!("Press the USER button..."); diff --git a/examples/stm32f7/src/bin/cryp.rs b/examples/stm32f7/src/bin/cryp.rs new file mode 100644 index 000000000..04927841a --- /dev/null +++ b/examples/stm32f7/src/bin/cryp.rs @@ -0,0 +1,74 @@ +#![no_std] +#![no_main] + +use aes_gcm::aead::heapless::Vec; +use aes_gcm::aead::{AeadInPlace, KeyInit}; +use aes_gcm::Aes128Gcm; +use defmt::info; +use embassy_executor::Spawner; +use embassy_stm32::cryp::*; +use embassy_stm32::Config; +use embassy_time::Instant; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let config = Config::default(); + let p = embassy_stm32::init(config); + + let payload: &[u8] = b"hello world"; + let aad: &[u8] = b"additional data"; + + let hw_cryp = Cryp::new(p.CRYP); + let key: [u8; 16] = [0; 16]; + let mut ciphertext: [u8; 11] = [0; 11]; + let mut plaintext: [u8; 11] = [0; 11]; + let iv: [u8; 12] = [0; 12]; + + let hw_start_time = Instant::now(); + + // Encrypt in hardware using AES-GCM 128-bit + let aes_gcm = AesGcm::new(&key, &iv); + let mut gcm_encrypt = hw_cryp.start(&aes_gcm, Direction::Encrypt); + hw_cryp.aad_blocking(&mut gcm_encrypt, aad, true); + hw_cryp.payload_blocking(&mut gcm_encrypt, payload, &mut ciphertext, true); + let encrypt_tag = hw_cryp.finish_blocking(gcm_encrypt); + + // Decrypt in hardware using AES-GCM 128-bit + let mut gcm_decrypt = hw_cryp.start(&aes_gcm, Direction::Decrypt); + hw_cryp.aad_blocking(&mut gcm_decrypt, aad, true); + hw_cryp.payload_blocking(&mut gcm_decrypt, &ciphertext, &mut plaintext, true); + let decrypt_tag = hw_cryp.finish_blocking(gcm_decrypt); + + let hw_end_time = Instant::now(); + let hw_execution_time = hw_end_time - hw_start_time; + + info!("AES-GCM Ciphertext: {:?}", ciphertext); + info!("AES-GCM Plaintext: {:?}", plaintext); + assert_eq!(payload, plaintext); + assert_eq!(encrypt_tag, decrypt_tag); + + let sw_start_time = Instant::now(); + + // Encrypt in software using AES-GCM 128-bit + let mut payload_vec: Vec<u8, 32> = Vec::from_slice(&payload).unwrap(); + let cipher = Aes128Gcm::new(&key.into()); + let _ = cipher.encrypt_in_place(&iv.into(), aad.into(), &mut payload_vec); + + assert_eq!(ciphertext, payload_vec[0..ciphertext.len()]); + assert_eq!( + encrypt_tag, + payload_vec[ciphertext.len()..ciphertext.len() + encrypt_tag.len()] + ); + + // Decrypt in software using AES-GCM 128-bit + let _ = cipher.decrypt_in_place(&iv.into(), aad.into(), &mut payload_vec); + + let sw_end_time = Instant::now(); + let sw_execution_time = sw_end_time - sw_start_time; + + info!("Hardware Execution Time: {:?}", hw_execution_time); + info!("Software Execution Time: {:?}", sw_execution_time); + + loop {} +} diff --git a/examples/stm32f7/src/bin/eth.rs b/examples/stm32f7/src/bin/eth.rs index 5bff48197..9a608e909 100644 --- a/examples/stm32f7/src/bin/eth.rs +++ b/examples/stm32f7/src/bin/eth.rs @@ -19,7 +19,7 @@ use {defmt_rtt as _, panic_probe as _}; bind_interrupts!(struct Irqs { ETH => eth::InterruptHandler; - RNG => rng::InterruptHandler<peripherals::RNG>; + HASH_RNG => rng::InterruptHandler<peripherals::RNG>; }); type Device = Ethernet<'static, ETH, GenericSMI>; diff --git a/examples/stm32f7/src/bin/hash.rs b/examples/stm32f7/src/bin/hash.rs new file mode 100644 index 000000000..c2d1a7158 --- /dev/null +++ b/examples/stm32f7/src/bin/hash.rs @@ -0,0 +1,78 @@ +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_stm32::hash::*; +use embassy_stm32::{bind_interrupts, hash, peripherals, Config}; +use embassy_time::Instant; +use hmac::{Hmac, Mac}; +use sha2::{Digest, Sha256}; +use {defmt_rtt as _, panic_probe as _}; + +type HmacSha256 = Hmac<Sha256>; + +bind_interrupts!(struct Irqs { + HASH_RNG => hash::InterruptHandler<peripherals::HASH>; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let config = Config::default(); + let p = embassy_stm32::init(config); + + let test_1: &[u8] = b"as;dfhaslfhas;oifvnasd;nifvnhasd;nifvhndlkfghsd;nvfnahssdfgsdafgsasdfasdfasdfasdfasdfghjklmnbvcalskdjghalskdjgfbaslkdjfgbalskdjgbalskdjbdfhsdfhsfghsfghfgh"; + let test_2: &[u8] = b"fdhalksdjfhlasdjkfhalskdjfhgal;skdjfgalskdhfjgalskdjfglafgadfgdfgdafgaadsfgfgdfgadrgsyfthxfgjfhklhjkfgukhulkvhlvhukgfhfsrghzdhxyfufynufyuszeradrtydyytserr"; + + let mut hw_hasher = Hash::new(p.HASH, p.DMA2_CH7, Irqs); + + let hw_start_time = Instant::now(); + + // Compute a digest in hardware. + let mut context = hw_hasher.start(Algorithm::SHA256, DataType::Width8, None); + hw_hasher.update(&mut context, test_1).await; + hw_hasher.update(&mut context, test_2).await; + let mut hw_digest: [u8; 32] = [0; 32]; + hw_hasher.finish(context, &mut hw_digest).await; + + let hw_end_time = Instant::now(); + let hw_execution_time = hw_end_time - hw_start_time; + + let sw_start_time = Instant::now(); + + // Compute a digest in software. + let mut sw_hasher = Sha256::new(); + sw_hasher.update(test_1); + sw_hasher.update(test_2); + let sw_digest = sw_hasher.finalize(); + + let sw_end_time = Instant::now(); + let sw_execution_time = sw_end_time - sw_start_time; + + info!("Hardware Digest: {:?}", hw_digest); + info!("Software Digest: {:?}", sw_digest[..]); + info!("Hardware Execution Time: {:?}", hw_execution_time); + info!("Software Execution Time: {:?}", sw_execution_time); + assert_eq!(hw_digest, sw_digest[..]); + + let hmac_key: [u8; 64] = [0x55; 64]; + + // Compute HMAC in hardware. + let mut sha256hmac_context = hw_hasher.start(Algorithm::SHA256, DataType::Width8, Some(&hmac_key)); + hw_hasher.update(&mut sha256hmac_context, test_1).await; + hw_hasher.update(&mut sha256hmac_context, test_2).await; + let mut hw_hmac: [u8; 32] = [0; 32]; + hw_hasher.finish(sha256hmac_context, &mut hw_hmac).await; + + // Compute HMAC in software. + let mut sw_mac = HmacSha256::new_from_slice(&hmac_key).unwrap(); + sw_mac.update(test_1); + sw_mac.update(test_2); + let sw_hmac = sw_mac.finalize().into_bytes(); + + info!("Hardware HMAC: {:?}", hw_hmac); + info!("Software HMAC: {:?}", sw_hmac[..]); + assert_eq!(hw_hmac, sw_hmac[..]); + + loop {} +} diff --git a/examples/stm32g0/src/bin/button_exti.rs b/examples/stm32g0/src/bin/button_exti.rs index 1e970fdd6..34a08bbc6 100644 --- a/examples/stm32g0/src/bin/button_exti.rs +++ b/examples/stm32g0/src/bin/button_exti.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; -use embassy_stm32::gpio::{Input, Pull}; +use embassy_stm32::gpio::Pull; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] @@ -12,8 +12,7 @@ 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); + let mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::Up); info!("Press the USER button..."); diff --git a/examples/stm32g0/src/bin/hf_timer.rs b/examples/stm32g0/src/bin/hf_timer.rs index 78a779e29..3ea06cdee 100644 --- a/examples/stm32g0/src/bin/hf_timer.rs +++ b/examples/stm32g0/src/bin/hf_timer.rs @@ -4,37 +4,36 @@ use defmt::info; use embassy_executor::Spawner; use embassy_stm32::gpio::OutputType; -use embassy_stm32::pac::rcc::vals::Tim1sel; -use embassy_stm32::rcc::{ClockSrc, Config as RccConfig, PllConfig, PllSource, Pllm, Plln, Pllq, Pllr}; use embassy_stm32::time::khz; use embassy_stm32::timer::complementary_pwm::{ComplementaryPwm, ComplementaryPwmPin}; use embassy_stm32::timer::simple_pwm::PwmPin; use embassy_stm32::timer::Channel; -use embassy_stm32::{pac, Config as PeripheralConfig}; +use embassy_stm32::Config as PeripheralConfig; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] async fn main(_spawner: Spawner) { - let mut rcc_config = RccConfig::default(); - rcc_config.mux = ClockSrc::PLL(PllConfig { - source: PllSource::HSI, - m: Pllm::DIV1, - n: Plln::MUL16, - r: Pllr::DIV4, // CPU clock comes from PLLR (HSI (16MHz) / 1 * 16 / 4 = 64MHz) - q: Some(Pllq::DIV2), // TIM1 or TIM15 can be sourced from PLLQ (HSI (16MHz) / 1 * 16 / 2 = 128MHz) - p: None, - }); + let mut config = PeripheralConfig::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = true; + config.rcc.pll = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL16, + divp: None, + divq: Some(PllQDiv::DIV2), // 16 / 1 * 16 / 2 = 128 Mhz + divr: Some(PllRDiv::DIV4), // 16 / 1 * 16 / 4 = 64 Mhz + }); + config.rcc.sys = Sysclk::PLL1_R; - let mut peripheral_config = PeripheralConfig::default(); - peripheral_config.rcc = rcc_config; - - let p = embassy_stm32::init(peripheral_config); - - // configure TIM1 mux to select PLLQ as clock source - // https://www.st.com/resource/en/reference_manual/rm0444-stm32g0x1-advanced-armbased-32bit-mcus-stmicroelectronics.pdf - // RM0444 page 210 - // RCC - Peripherals Independent Clock Control Register - bit 22 -> 1 - pac::RCC.ccipr().modify(|w| w.set_tim1sel(Tim1sel::PLL1_Q)); + // configure TIM1 mux to select PLLQ as clock source + // https://www.st.com/resource/en/reference_manual/rm0444-stm32g0x1-advanced-armbased-32bit-mcus-stmicroelectronics.pdf + // RM0444 page 210 + // RCC - Peripherals Independent Clock Control Register - bit 22 -> 1 + config.rcc.mux.tim1sel = embassy_stm32::rcc::mux::Tim1sel::PLL1_Q; + } + let p = embassy_stm32::init(config); let ch1 = PwmPin::new_ch1(p.PA8, OutputType::PushPull); let ch1n = ComplementaryPwmPin::new_ch1(p.PA7, OutputType::PushPull); diff --git a/examples/stm32g0/src/bin/usb_serial.rs b/examples/stm32g0/src/bin/usb_serial.rs index f5aaa5624..8b9915626 100644 --- a/examples/stm32g0/src/bin/usb_serial.rs +++ b/examples/stm32g0/src/bin/usb_serial.rs @@ -4,7 +4,6 @@ use defmt::{panic, *}; use embassy_executor::Spawner; use embassy_futures::join::join; -use embassy_stm32::rcc::{Hsi48Config, UsbSrc}; use embassy_stm32::usb::{Driver, Instance}; use embassy_stm32::{bind_interrupts, peripherals, usb, Config}; use embassy_usb::class::cdc_acm::{CdcAcmClass, State}; @@ -19,10 +18,11 @@ bind_interrupts!(struct Irqs { #[embassy_executor::main] async fn main(_spawner: Spawner) { let mut config = Config::default(); - config.rcc.usb_src = Some(UsbSrc::Hsi48(Hsi48Config { - sync_from_usb: true, - ..Default::default() - })); + { + use embassy_stm32::rcc::*; + config.rcc.hsi48 = Some(Hsi48Config { sync_from_usb: true }); + config.rcc.mux.usbsel = mux::Usbsel::HSI48; + } let p = embassy_stm32::init(config); info!("Hello World!"); diff --git a/examples/stm32g4/Cargo.toml b/examples/stm32g4/Cargo.toml index 895ad3e7c..64c749b9b 100644 --- a/examples/stm32g4/Cargo.toml +++ b/examples/stm32g4/Cargo.toml @@ -12,7 +12,7 @@ embassy-executor = { version = "0.5.0", path = "../../embassy-executor", feature embassy-time = { version = "0.3.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] } embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } -usbd-hid = "0.6.0" +usbd-hid = "0.7.0" defmt = "0.3" defmt-rtt = "0.4" @@ -20,9 +20,11 @@ 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-can = { version = "0.4" } panic-probe = { version = "0.3", features = ["print-defmt"] } futures = { version = "0.3.17", default-features = false, features = ["async-await"] } heapless = { version = "0.8", default-features = false } +static_cell = "2.0.0" [profile.release] debug = 2 diff --git a/examples/stm32g4/src/bin/adc.rs b/examples/stm32g4/src/bin/adc.rs index 35324d931..ae64bc8e4 100644 --- a/examples/stm32g4/src/bin/adc.rs +++ b/examples/stm32g4/src/bin/adc.rs @@ -4,7 +4,6 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::adc::{Adc, SampleTime}; -use embassy_stm32::rcc::{AdcClockSource, ClockSrc, Pll, PllM, PllN, PllR, PllSource}; use embassy_stm32::Config; use embassy_time::{Delay, Timer}; use {defmt_rtt as _, panic_probe as _}; @@ -12,25 +11,25 @@ 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: PllSource::HSI, - 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.adc12_clock_source = AdcClockSource::SYS; - config.rcc.mux = ClockSrc::PLL; - + { + use embassy_stm32::rcc::*; + config.rcc.pll = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL85, + divp: None, + divq: None, + // Main system clock at 170 MHz + divr: Some(PllRDiv::DIV2), + }); + config.rcc.mux.adc12sel = mux::Adcsel::SYS; + config.rcc.sys = Sysclk::PLL1_R; + } let mut p = embassy_stm32::init(config); info!("Hello World!"); let mut adc = Adc::new(p.ADC2, &mut Delay); - adc.set_sample_time(SampleTime::Cycles32_5); + adc.set_sample_time(SampleTime::CYCLES32_5); loop { let measured = adc.read(&mut p.PA7); diff --git a/examples/stm32g4/src/bin/button_exti.rs b/examples/stm32g4/src/bin/button_exti.rs index 67751187d..2a546dac5 100644 --- a/examples/stm32g4/src/bin/button_exti.rs +++ b/examples/stm32g4/src/bin/button_exti.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; -use embassy_stm32::gpio::{Input, Pull}; +use embassy_stm32::gpio::Pull; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] @@ -12,8 +12,7 @@ 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); + let mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::Down); info!("Press the USER button..."); diff --git a/examples/stm32g4/src/bin/can.rs b/examples/stm32g4/src/bin/can.rs new file mode 100644 index 000000000..4373a89a8 --- /dev/null +++ b/examples/stm32g4/src/bin/can.rs @@ -0,0 +1,229 @@ +#![no_std] +#![no_main] +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::peripherals::*; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, can, Config}; +use embassy_time::Timer; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + FDCAN1_IT0 => can::IT0InterruptHandler<FDCAN1>; + FDCAN1_IT1 => can::IT1InterruptHandler<FDCAN1>; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: Hertz(24_000_000), + mode: HseMode::Oscillator, + }); + config.rcc.pll = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV6, + mul: PllMul::MUL85, + divp: None, + divq: Some(PllQDiv::DIV8), // 42.5 Mhz for fdcan. + divr: Some(PllRDiv::DIV2), // Main system clock at 170 MHz + }); + config.rcc.mux.fdcansel = mux::Fdcansel::PLL1_Q; + config.rcc.sys = Sysclk::PLL1_R; + } + let peripherals = embassy_stm32::init(config); + + let mut can = can::FdcanConfigurator::new(peripherals.FDCAN1, peripherals.PA11, peripherals.PA12, Irqs); + + can.set_extended_filter( + can::filter::ExtendedFilterSlot::_0, + can::filter::ExtendedFilter::accept_all_into_fifo1(), + ); + + // 250k bps + can.set_bitrate(250_000); + + let use_fd = false; + + // 1M bps + if use_fd { + can.set_fd_data_bitrate(1_000_000, false); + } + + info!("Configured"); + + let mut can = can.start(match use_fd { + true => can::FdcanOperatingMode::InternalLoopbackMode, + false => can::FdcanOperatingMode::NormalOperationMode, + }); + + let mut i = 0; + let mut last_read_ts = embassy_time::Instant::now(); + + loop { + let frame = can::frame::ClassicFrame::new_extended(0x123456F, &[i; 8]).unwrap(); + info!("Writing frame"); + + _ = can.write(&frame).await; + + match can.read().await { + Ok((rx_frame, ts)) => { + let delta = (ts - last_read_ts).as_millis(); + last_read_ts = ts; + info!( + "Rx: {} {:02x} --- {}ms", + rx_frame.header().len(), + rx_frame.data()[0..rx_frame.header().len() as usize], + delta, + ) + } + Err(_err) => error!("Error in frame"), + } + + Timer::after_millis(250).await; + + i += 1; + if i > 2 { + break; + } + } + + // Use the FD API's even if we don't get FD packets. + + loop { + if use_fd { + let frame = can::frame::FdFrame::new_extended(0x123456F, &[i; 16]).unwrap(); + info!("Writing frame using FD API"); + _ = can.write_fd(&frame).await; + } else { + let frame = can::frame::FdFrame::new_extended(0x123456F, &[i; 8]).unwrap(); + info!("Writing frame using FD API"); + _ = can.write_fd(&frame).await; + } + + match can.read_fd().await { + Ok((rx_frame, ts)) => { + let delta = (ts - last_read_ts).as_millis(); + last_read_ts = ts; + info!( + "Rx: {} {:02x} --- using FD API {}ms", + rx_frame.header().len(), + rx_frame.data()[0..rx_frame.header().len() as usize], + delta, + ) + } + Err(_err) => error!("Error in frame"), + } + + Timer::after_millis(250).await; + + i += 1; + if i > 4 { + break; + } + } + i = 0; + let (mut tx, mut rx) = can.split(); + // With split + loop { + let frame = can::frame::ClassicFrame::new_extended(0x123456F, &[i; 8]).unwrap(); + info!("Writing frame"); + _ = tx.write(&frame).await; + + match rx.read().await { + Ok((rx_frame, ts)) => { + let delta = (ts - last_read_ts).as_millis(); + last_read_ts = ts; + info!( + "Rx: {} {:02x} --- {}ms", + rx_frame.header().len(), + rx_frame.data()[0..rx_frame.header().len() as usize], + delta, + ) + } + Err(_err) => error!("Error in frame"), + } + + Timer::after_millis(250).await; + + i += 1; + + if i > 2 { + break; + } + } + + let can = can::Fdcan::join(tx, rx); + + info!("\n\n\nBuffered\n"); + if use_fd { + static TX_BUF: StaticCell<can::TxFdBuf<8>> = StaticCell::new(); + static RX_BUF: StaticCell<can::RxFdBuf<10>> = StaticCell::new(); + let mut can = can.buffered_fd( + TX_BUF.init(can::TxFdBuf::<8>::new()), + RX_BUF.init(can::RxFdBuf::<10>::new()), + ); + loop { + let frame = can::frame::FdFrame::new_extended(0x123456F, &[i; 16]).unwrap(); + info!("Writing frame"); + + _ = can.write(frame).await; + + match can.read().await { + Ok((rx_frame, ts)) => { + let delta = (ts - last_read_ts).as_millis(); + last_read_ts = ts; + info!( + "Rx: {} {:02x} --- {}ms", + rx_frame.header().len(), + rx_frame.data()[0..rx_frame.header().len() as usize], + delta, + ) + } + Err(_err) => error!("Error in frame"), + } + + Timer::after_millis(250).await; + + i += 1; + } + } else { + static TX_BUF: StaticCell<can::TxBuf<8>> = StaticCell::new(); + static RX_BUF: StaticCell<can::RxBuf<10>> = StaticCell::new(); + let mut can = can.buffered( + TX_BUF.init(can::TxBuf::<8>::new()), + RX_BUF.init(can::RxBuf::<10>::new()), + ); + loop { + let frame = can::frame::ClassicFrame::new_extended(0x123456F, &[i; 8]).unwrap(); + info!("Writing frame"); + + // You can use any of these approaches to send. The writer makes it + // easy to share sending from multiple tasks. + //_ = can.write(frame).await; + //can.writer().try_write(frame).unwrap(); + can.writer().write(frame).await; + + match can.read().await { + Ok((rx_frame, ts)) => { + let delta = (ts - last_read_ts).as_millis(); + last_read_ts = ts; + info!( + "Rx: {} {:02x} --- {}ms", + rx_frame.header().len(), + rx_frame.data()[0..rx_frame.header().len() as usize], + delta, + ) + } + Err(_err) => error!("Error in frame"), + } + + Timer::after_millis(250).await; + + i += 1; + } + } +} diff --git a/examples/stm32g4/src/bin/pll.rs b/examples/stm32g4/src/bin/pll.rs index 46ebe0b0d..08ed95b34 100644 --- a/examples/stm32g4/src/bin/pll.rs +++ b/examples/stm32g4/src/bin/pll.rs @@ -3,7 +3,6 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_stm32::rcc::{ClockSrc, Pll, PllM, PllN, PllR, PllSource}; use embassy_stm32::Config; use embassy_time::Timer; use {defmt_rtt as _, panic_probe as _}; @@ -11,19 +10,20 @@ 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: PllSource::HSI, - 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; - + { + use embassy_stm32::rcc::*; + config.rcc.hsi = true; + config.rcc.pll = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL85, + divp: None, + divq: None, + // Main system clock at 170 MHz + divr: Some(PllRDiv::DIV2), + }); + config.rcc.sys = Sysclk::PLL1_R; + } let _p = embassy_stm32::init(config); info!("Hello World!"); diff --git a/examples/stm32g4/src/bin/usb_serial.rs b/examples/stm32g4/src/bin/usb_serial.rs index c26fa76b7..dc95aa6e5 100644 --- a/examples/stm32g4/src/bin/usb_serial.rs +++ b/examples/stm32g4/src/bin/usb_serial.rs @@ -3,7 +3,6 @@ use defmt::{panic, *}; use embassy_executor::Spawner; -use embassy_stm32::rcc::{Clock48MhzSrc, ClockSrc, Hsi48Config, Pll, PllM, PllN, PllQ, PllR, PllSource}; use embassy_stm32::time::Hertz; use embassy_stm32::usb::{self, Driver, Instance}; use embassy_stm32::{bind_interrupts, peripherals, Config}; @@ -20,31 +19,27 @@ bind_interrupts!(struct Irqs { #[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 plldivq = if USE_HSI48 { None } else { Some(PllQ::DIV6) }; - - config.rcc.pll = Some(Pll { - source: PllSource::HSE(Hertz(8_000_000)), - prediv_m: PllM::DIV2, - mul_n: PllN::MUL72, - div_p: None, - div_q: plldivq, - // Main system clock at 144 MHz - div_r: Some(PllR::DIV2), - }); - - config.rcc.mux = ClockSrc::PLL; - - if USE_HSI48 { + { + use embassy_stm32::rcc::*; // 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(Hsi48Config { sync_from_usb: true })); - } else { - config.rcc.clock_48mhz_src = Some(Clock48MhzSrc::PllQ); + config.rcc.hsi48 = Some(Hsi48Config { sync_from_usb: true }); + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::Oscillator, + }); + config.rcc.pll = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV2, + mul: PllMul::MUL72, + divp: None, + divq: Some(PllQDiv::DIV6), // 48mhz + divr: Some(PllRDiv::DIV2), // Main system clock at 144 MHz + }); + config.rcc.sys = Sysclk::PLL1_R; + config.rcc.boost = true; // BOOST! + config.rcc.mux.clk48sel = mux::Clk48sel::HSI48; + //config.rcc.mux.clk48sel = mux::Clk48sel::PLL1_Q; // uncomment to use PLL1_Q instead. } - let p = embassy_stm32::init(config); info!("Hello World!"); diff --git a/examples/stm32h5/src/bin/button_exti.rs b/examples/stm32h5/src/bin/button_exti.rs index 67751187d..2a546dac5 100644 --- a/examples/stm32h5/src/bin/button_exti.rs +++ b/examples/stm32h5/src/bin/button_exti.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; -use embassy_stm32::gpio::{Input, Pull}; +use embassy_stm32::gpio::Pull; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] @@ -12,8 +12,7 @@ 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); + let mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::Down); info!("Press the USER button..."); diff --git a/examples/stm32h5/src/bin/can.rs b/examples/stm32h5/src/bin/can.rs new file mode 100644 index 000000000..643df27f9 --- /dev/null +++ b/examples/stm32h5/src/bin/can.rs @@ -0,0 +1,96 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::peripherals::*; +use embassy_stm32::{bind_interrupts, can, rcc, Config}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + FDCAN1_IT0 => can::IT0InterruptHandler<FDCAN1>; + FDCAN1_IT1 => can::IT1InterruptHandler<FDCAN1>; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + config.rcc.hse = Some(rcc::Hse { + freq: embassy_stm32::time::Hertz(25_000_000), + mode: rcc::HseMode::Oscillator, + }); + config.rcc.mux.fdcan12sel = rcc::mux::Fdcansel::HSE; + + let peripherals = embassy_stm32::init(config); + + let mut can = can::FdcanConfigurator::new(peripherals.FDCAN1, peripherals.PA11, peripherals.PA12, Irqs); + + // 250k bps + can.set_bitrate(250_000); + + //let mut can = can.into_internal_loopback_mode(); + let mut can = can.into_normal_mode(); + + info!("CAN Configured"); + + let mut i = 0; + let mut last_read_ts = embassy_time::Instant::now(); + + loop { + let frame = can::frame::ClassicFrame::new_extended(0x123456F, &[i; 8]).unwrap(); + info!("Writing frame"); + _ = can.write(&frame).await; + + match can.read().await { + Ok((rx_frame, ts)) => { + let delta = (ts - last_read_ts).as_millis(); + last_read_ts = ts; + info!( + "Rx: {:x} {:x} {:x} {:x} --- NEW {}", + rx_frame.data()[0], + rx_frame.data()[1], + rx_frame.data()[2], + rx_frame.data()[3], + delta, + ) + } + Err(_err) => error!("Error in frame"), + } + + Timer::after_millis(250).await; + + i += 1; + if i > 3 { + break; + } + } + + let (mut tx, mut rx) = can.split(); + // With split + loop { + let frame = can::frame::ClassicFrame::new_extended(0x123456F, &[i; 8]).unwrap(); + info!("Writing frame"); + _ = tx.write(&frame).await; + + match rx.read().await { + Ok((rx_frame, ts)) => { + let delta = (ts - last_read_ts).as_millis(); + last_read_ts = ts; + info!( + "Rx: {:x} {:x} {:x} {:x} --- NEW {}", + rx_frame.data()[0], + rx_frame.data()[1], + rx_frame.data()[2], + rx_frame.data()[3], + delta, + ) + } + Err(_err) => error!("Error in frame"), + } + + Timer::after_millis(250).await; + + i += 1; + } +} diff --git a/examples/stm32h5/src/bin/usb_serial.rs b/examples/stm32h5/src/bin/usb_serial.rs index 208493d8c..83477c8fa 100644 --- a/examples/stm32h5/src/bin/usb_serial.rs +++ b/examples/stm32h5/src/bin/usb_serial.rs @@ -5,7 +5,7 @@ use defmt::{panic, *}; use embassy_executor::Spawner; use embassy_stm32::time::Hertz; use embassy_stm32::usb::{Driver, Instance}; -use embassy_stm32::{bind_interrupts, pac, peripherals, usb, 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; @@ -41,15 +41,12 @@ async fn main(_spawner: Spawner) { config.rcc.apb3_pre = APBPrescaler::DIV4; config.rcc.sys = Sysclk::PLL1_P; config.rcc.voltage_scale = VoltageScale::Scale0; + config.rcc.mux.usbsel = mux::Usbsel::HSI48; } 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); diff --git a/examples/stm32h7/src/bin/adc.rs b/examples/stm32h7/src/bin/adc.rs index fe6fe69a1..a5594d10c 100644 --- a/examples/stm32h7/src/bin/adc.rs +++ b/examples/stm32h7/src/bin/adc.rs @@ -38,7 +38,7 @@ async fn main(_spawner: Spawner) { config.rcc.apb3_pre = APBPrescaler::DIV2; // 100 Mhz config.rcc.apb4_pre = APBPrescaler::DIV2; // 100 Mhz config.rcc.voltage_scale = VoltageScale::Scale1; - config.rcc.adc_clock_source = AdcClockSource::PLL2_P; + config.rcc.mux.adcsel = mux::Adcsel::PLL2_P; } let mut p = embassy_stm32::init(config); @@ -46,7 +46,7 @@ async fn main(_spawner: Spawner) { let mut adc = Adc::new(p.ADC3, &mut Delay); - adc.set_sample_time(SampleTime::Cycles32_5); + adc.set_sample_time(SampleTime::CYCLES32_5); let mut vrefint_channel = adc.enable_vrefint(); diff --git a/examples/stm32h7/src/bin/button_exti.rs b/examples/stm32h7/src/bin/button_exti.rs index 67751187d..2a546dac5 100644 --- a/examples/stm32h7/src/bin/button_exti.rs +++ b/examples/stm32h7/src/bin/button_exti.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; -use embassy_stm32::gpio::{Input, Pull}; +use embassy_stm32::gpio::Pull; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] @@ -12,8 +12,7 @@ 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); + let mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::Down); info!("Press the USER button..."); diff --git a/examples/stm32h7/src/bin/can.rs b/examples/stm32h7/src/bin/can.rs new file mode 100644 index 000000000..13a6a5051 --- /dev/null +++ b/examples/stm32h7/src/bin/can.rs @@ -0,0 +1,96 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::peripherals::*; +use embassy_stm32::{bind_interrupts, can, rcc, Config}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + FDCAN1_IT0 => can::IT0InterruptHandler<FDCAN1>; + FDCAN1_IT1 => can::IT1InterruptHandler<FDCAN1>; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + config.rcc.hse = Some(rcc::Hse { + freq: embassy_stm32::time::Hertz(25_000_000), + mode: rcc::HseMode::Oscillator, + }); + config.rcc.mux.fdcansel = rcc::mux::Fdcansel::HSE; + + let peripherals = embassy_stm32::init(config); + + let mut can = can::FdcanConfigurator::new(peripherals.FDCAN1, peripherals.PA11, peripherals.PA12, Irqs); + + // 250k bps + can.set_bitrate(250_000); + + //let mut can = can.into_internal_loopback_mode(); + let mut can = can.into_normal_mode(); + + info!("CAN Configured"); + + let mut i = 0; + let mut last_read_ts = embassy_time::Instant::now(); + + loop { + let frame = can::frame::ClassicFrame::new_extended(0x123456F, &[i; 8]).unwrap(); + info!("Writing frame"); + _ = can.write(&frame).await; + + match can.read().await { + Ok((rx_frame, ts)) => { + let delta = (ts - last_read_ts).as_millis(); + last_read_ts = ts; + info!( + "Rx: {:x} {:x} {:x} {:x} --- NEW {}", + rx_frame.data()[0], + rx_frame.data()[1], + rx_frame.data()[2], + rx_frame.data()[3], + delta, + ) + } + Err(_err) => error!("Error in frame"), + } + + Timer::after_millis(250).await; + + i += 1; + if i > 3 { + break; + } + } + + let (mut tx, mut rx) = can.split(); + // With split + loop { + let frame = can::frame::ClassicFrame::new_extended(0x123456F, &[i; 8]).unwrap(); + info!("Writing frame"); + _ = tx.write(&frame).await; + + match rx.read().await { + Ok((rx_frame, ts)) => { + let delta = (ts - last_read_ts).as_millis(); + last_read_ts = ts; + info!( + "Rx: {:x} {:x} {:x} {:x} --- NEW {}", + rx_frame.data()[0], + rx_frame.data()[1], + rx_frame.data()[2], + rx_frame.data()[3], + delta, + ) + } + Err(_err) => error!("Error in frame"), + } + + Timer::after_millis(250).await; + + i += 1; + } +} diff --git a/examples/stm32h7/src/bin/dac.rs b/examples/stm32h7/src/bin/dac.rs index a9bf46de0..a6f969aba 100644 --- a/examples/stm32h7/src/bin/dac.rs +++ b/examples/stm32h7/src/bin/dac.rs @@ -40,7 +40,7 @@ fn main() -> ! { config.rcc.apb3_pre = APBPrescaler::DIV2; // 100 Mhz config.rcc.apb4_pre = APBPrescaler::DIV2; // 100 Mhz config.rcc.voltage_scale = VoltageScale::Scale1; - config.rcc.adc_clock_source = AdcClockSource::PLL2_P; + config.rcc.mux.adcsel = mux::Adcsel::PLL2_P; } let p = embassy_stm32::init(config); diff --git a/examples/stm32h7/src/bin/dac_dma.rs b/examples/stm32h7/src/bin/dac_dma.rs index 8e5c41a43..feec28993 100644 --- a/examples/stm32h7/src/bin/dac_dma.rs +++ b/examples/stm32h7/src/bin/dac_dma.rs @@ -8,7 +8,7 @@ use embassy_stm32::pac::timer::vals::Mms; use embassy_stm32::peripherals::{DAC1, DMA1_CH3, DMA1_CH4, TIM6, TIM7}; use embassy_stm32::rcc::low_level::RccPeripheral; use embassy_stm32::time::Hertz; -use embassy_stm32::timer::low_level::Basic16bitInstance; +use embassy_stm32::timer::low_level::BasicInstance; use micromath::F32Ext; use {defmt_rtt as _, panic_probe as _}; @@ -42,7 +42,7 @@ async fn main(spawner: Spawner) { config.rcc.apb3_pre = APBPrescaler::DIV2; // 100 Mhz config.rcc.apb4_pre = APBPrescaler::DIV2; // 100 Mhz config.rcc.voltage_scale = VoltageScale::Scale1; - config.rcc.adc_clock_source = AdcClockSource::PLL2_P; + config.rcc.mux.adcsel = mux::Adcsel::PLL2_P; } // Initialize the board and obtain a Peripherals instance @@ -75,9 +75,9 @@ async fn dac_task1(mut dac: DacCh1<'static, DAC1, DMA1_CH3>) { dac.enable(); TIM6::enable_and_reset(); - 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| { + TIM6::regs_basic().arr().modify(|w| w.set_arr(reload as u16 - 1)); + TIM6::regs_basic().cr2().modify(|w| w.set_mms(Mms::UPDATE)); + TIM6::regs_basic().cr1().modify(|w| { w.set_opm(false); w.set_cen(true); }); @@ -112,9 +112,9 @@ async fn dac_task2(mut dac: DacCh2<'static, DAC1, DMA1_CH4>) { } TIM7::enable_and_reset(); - 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| { + TIM7::regs_basic().arr().modify(|w| w.set_arr(reload as u16 - 1)); + TIM7::regs_basic().cr2().modify(|w| w.set_mms(Mms::UPDATE)); + TIM7::regs_basic().cr1().modify(|w| { w.set_opm(false); w.set_cen(true); }); diff --git a/examples/stm32h7/src/bin/eth_client.rs b/examples/stm32h7/src/bin/eth_client.rs index dcc6e36e2..aeb169e19 100644 --- a/examples/stm32h7/src/bin/eth_client.rs +++ b/examples/stm32h7/src/bin/eth_client.rs @@ -65,6 +65,7 @@ async fn main(spawner: Spawner) -> ! { let mac_addr = [0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF]; static PACKETS: StaticCell<PacketQueue<16, 16>> = StaticCell::new(); + let device = Ethernet::new( PACKETS.init(PacketQueue::<16, 16>::new()), p.ETH, diff --git a/examples/stm32h7/src/bin/eth_client_mii.rs b/examples/stm32h7/src/bin/eth_client_mii.rs new file mode 100644 index 000000000..de6ea522a --- /dev/null +++ b/examples/stm32h7/src/bin/eth_client_mii.rs @@ -0,0 +1,142 @@ +#![no_std] +#![no_main] + +use defmt::*; +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, PacketQueue}; +use embassy_stm32::peripherals::ETH; +use embassy_stm32::rng::Rng; +use embassy_stm32::{bind_interrupts, eth, peripherals, rng, Config}; +use embassy_time::Timer; +use embedded_io_async::Write; +use embedded_nal_async::{Ipv4Addr, SocketAddr, SocketAddrV4, TcpConnect}; +use rand_core::RngCore; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + ETH => eth::InterruptHandler; + RNG => rng::InterruptHandler<peripherals::RNG>; +}); + +type Device = Ethernet<'static, ETH, GenericSMI>; + +#[embassy_executor::task] +async fn net_task(stack: &'static Stack<Device>) -> ! { + stack.run().await +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) -> ! { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = Some(HSIPrescaler::DIV1); + config.rcc.csi = true; + config.rcc.hsi48 = Some(Default::default()); // needed for RNG + config.rcc.pll1 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL50, + divp: Some(PllDiv::DIV2), + divq: None, + divr: None, + }); + config.rcc.sys = Sysclk::PLL1_P; // 400 Mhz + config.rcc.ahb_pre = AHBPrescaler::DIV2; // 200 Mhz + config.rcc.apb1_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb2_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb3_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb4_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.voltage_scale = VoltageScale::Scale1; + } + let p = embassy_stm32::init(config); + info!("Hello World!"); + + // Generate random seed. + let mut rng = Rng::new(p.RNG, Irqs); + 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]; + + static PACKETS: StaticCell<PacketQueue<16, 16>> = StaticCell::new(); + + let device = Ethernet::new_mii( + PACKETS.init(PacketQueue::<16, 16>::new()), + p.ETH, + Irqs, + p.PA1, + p.PC3, + p.PA2, + p.PC1, + p.PA7, + p.PC4, + p.PC5, + p.PB0, + p.PB1, + p.PG13, + p.PG12, + p.PC2, + p.PE2, + p.PG11, + GenericSMI::new(1), + mac_addr, + ); + info!("Device created"); + + 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 + static STACK: StaticCell<Stack<Device>> = StaticCell::new(); + static RESOURCES: StaticCell<StackResources<3>> = StaticCell::new(); + let stack = &*STACK.init(Stack::new( + device, + config, + RESOURCES.init(StackResources::<3>::new()), + seed, + )); + + // Launch network task + unwrap!(spawner.spawn(net_task(stack))); + + // Ensure DHCP configuration is up before trying connect + stack.wait_config_up().await; + + info!("Network task initialized"); + + let state: TcpClientState<1, 1024, 1024> = TcpClientState::new(); + let client = TcpClient::new(&stack, &state); + + loop { + // You need to start a server on the host machine, for example: `nc -l 8000` + let addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(192, 168, 100, 1), 8000)); + + info!("connecting..."); + let r = client.connect(addr).await; + if let Err(e) = r { + info!("connect error: {:?}", e); + Timer::after_secs(1).await; + continue; + } + let mut connection = r.unwrap(); + info!("connected!"); + loop { + let r = connection.write_all(b"Hello\n").await; + if let Err(e) = r { + info!("write error: {:?}", e); + break; + } + Timer::after_secs(1).await; + } + } +} diff --git a/examples/stm32h7/src/bin/low_level_timer_api.rs b/examples/stm32h7/src/bin/low_level_timer_api.rs index cc508c3cf..049d9967d 100644 --- a/examples/stm32h7/src/bin/low_level_timer_api.rs +++ b/examples/stm32h7/src/bin/low_level_timer_api.rs @@ -113,11 +113,11 @@ impl<'d, T: CaptureCompare32bitInstance> SimplePwm32<'d, T> { } pub fn get_max_duty(&self) -> u32 { - T::regs_gp32().arr().read().arr() + T::regs_gp32().arr().read() } pub fn set_duty(&mut self, channel: Channel, duty: u32) { defmt::assert!(duty < self.get_max_duty()); - T::regs_gp32().ccr(channel.index()).modify(|w| w.set_ccr(duty)) + T::regs_gp32().ccr(channel.index()).write_value(duty) } } diff --git a/examples/stm32h7/src/bin/rtc.rs b/examples/stm32h7/src/bin/rtc.rs index c6b9cf57e..0adb48877 100644 --- a/examples/stm32h7/src/bin/rtc.rs +++ b/examples/stm32h7/src/bin/rtc.rs @@ -24,7 +24,7 @@ async fn main(_spawner: Spawner) { .unwrap(); let mut rtc = Rtc::new(p.RTC, RtcConfig::default()); - info!("Got RTC! {:?}", now.timestamp()); + info!("Got RTC! {:?}", now.and_utc().timestamp()); rtc.set_datetime(now.into()).expect("datetime not set"); @@ -32,5 +32,5 @@ async fn main(_spawner: Spawner) { Timer::after_millis(20000).await; let then: NaiveDateTime = rtc.now().unwrap().into(); - info!("Got RTC! {:?}", then.timestamp()); + info!("Got RTC! {:?}", then.and_utc().timestamp()); } diff --git a/examples/stm32l0/src/bin/adc.rs b/examples/stm32l0/src/bin/adc.rs new file mode 100644 index 000000000..97d41ca4b --- /dev/null +++ b/examples/stm32l0/src/bin/adc.rs @@ -0,0 +1,40 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::adc::{Adc, SampleTime}; +use embassy_stm32::peripherals::ADC; +use embassy_stm32::{adc, bind_interrupts}; +use embassy_time::{Delay, Timer}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + ADC1_COMP => adc::InterruptHandler<ADC>; +}); + +#[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, Irqs, &mut Delay); + adc.set_sample_time(SampleTime::CYCLES79_5); + let mut pin = p.PA1; + + let mut vrefint = adc.enable_vref(&mut Delay); + let vrefint_sample = adc.read(&mut vrefint).await; + let convert_to_millivolts = |sample| { + // From https://www.st.com/resource/en/datasheet/stm32l051c6.pdf + // 6.3.3 Embedded internal reference voltage + const VREFINT_MV: u32 = 1224; // mV + + (u32::from(sample) * VREFINT_MV / u32::from(vrefint_sample)) as u16 + }; + + loop { + let v = adc.read(&mut pin).await; + info!("--> {} - {} mV", v, convert_to_millivolts(v)); + Timer::after_millis(100).await; + } +} diff --git a/examples/stm32l0/src/bin/button_exti.rs b/examples/stm32l0/src/bin/button_exti.rs index f517fce04..4945da7ce 100644 --- a/examples/stm32l0/src/bin/button_exti.rs +++ b/examples/stm32l0/src/bin/button_exti.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; -use embassy_stm32::gpio::{Input, Pull}; +use embassy_stm32::gpio::Pull; use embassy_stm32::Config; use {defmt_rtt as _, panic_probe as _}; @@ -13,8 +13,7 @@ async fn main(_spawner: Spawner) { let config = Config::default(); let p = embassy_stm32::init(config); - let button = Input::new(p.PB2, Pull::Up); - let mut button = ExtiInput::new(button, p.EXTI2); + let mut button = ExtiInput::new(p.PB2, p.EXTI2, Pull::Up); info!("Press the USER button..."); diff --git a/examples/stm32l1/src/bin/usb_serial.rs b/examples/stm32l1/src/bin/usb_serial.rs index 7b1e84cbc..f738ea358 100644 --- a/examples/stm32l1/src/bin/usb_serial.rs +++ b/examples/stm32l1/src/bin/usb_serial.rs @@ -27,7 +27,7 @@ async fn main(_spawner: Spawner) { mul: PllMul::MUL6, // PLLVCO = 16*6 = 96Mhz div: PllDiv::DIV3, // 32Mhz clock (16 * 6 / 3) }); - config.rcc.mux = ClockSrc::PLL1_R; + config.rcc.sys = Sysclk::PLL1_R; } let p = embassy_stm32::init(config); diff --git a/examples/stm32l4/src/bin/adc.rs b/examples/stm32l4/src/bin/adc.rs index d01e9f1b3..a9f4604aa 100644 --- a/examples/stm32l4/src/bin/adc.rs +++ b/examples/stm32l4/src/bin/adc.rs @@ -3,7 +3,7 @@ use defmt::*; use embassy_stm32::adc::{Adc, Resolution}; -use embassy_stm32::pac; +use embassy_stm32::Config; use embassy_time::Delay; use {defmt_rtt as _, panic_probe as _}; @@ -11,16 +11,16 @@ use {defmt_rtt as _, panic_probe as _}; fn main() -> ! { info!("Hello World!"); - pac::RCC.ccipr().modify(|w| { - w.set_adcsel(pac::rcc::vals::Adcsel::SYS); - }); - pac::RCC.ahb2enr().modify(|w| w.set_adcen(true)); - - let p = embassy_stm32::init(Default::default()); + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.mux.adcsel = mux::Adcsel::SYS; + } + let p = embassy_stm32::init(config); let mut adc = Adc::new(p.ADC1, &mut Delay); //adc.enable_vref(); - adc.set_resolution(Resolution::EightBit); + adc.set_resolution(Resolution::BITS8); let mut channel = p.PC0; loop { diff --git a/examples/stm32l4/src/bin/button_exti.rs b/examples/stm32l4/src/bin/button_exti.rs index 1e970fdd6..34a08bbc6 100644 --- a/examples/stm32l4/src/bin/button_exti.rs +++ b/examples/stm32l4/src/bin/button_exti.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; -use embassy_stm32::gpio::{Input, Pull}; +use embassy_stm32::gpio::Pull; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] @@ -12,8 +12,7 @@ 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); + let mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::Up); info!("Press the USER button..."); diff --git a/examples/stm32l4/src/bin/dac_dma.rs b/examples/stm32l4/src/bin/dac_dma.rs index 8e5098557..f227812cd 100644 --- a/examples/stm32l4/src/bin/dac_dma.rs +++ b/examples/stm32l4/src/bin/dac_dma.rs @@ -8,7 +8,7 @@ use embassy_stm32::pac::timer::vals::Mms; use embassy_stm32::peripherals::{DAC1, DMA1_CH3, DMA1_CH4, TIM6, TIM7}; use embassy_stm32::rcc::low_level::RccPeripheral; use embassy_stm32::time::Hertz; -use embassy_stm32::timer::low_level::Basic16bitInstance; +use embassy_stm32::timer::low_level::BasicInstance; use micromath::F32Ext; use {defmt_rtt as _, panic_probe as _}; @@ -46,9 +46,9 @@ async fn dac_task1(mut dac: DacCh1<'static, DAC1, DMA1_CH3>) { dac.enable(); TIM6::enable_and_reset(); - 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| { + TIM6::regs_basic().arr().modify(|w| w.set_arr(reload as u16 - 1)); + TIM6::regs_basic().cr2().modify(|w| w.set_mms(Mms::UPDATE)); + TIM6::regs_basic().cr1().modify(|w| { w.set_opm(false); w.set_cen(true); }); @@ -83,9 +83,9 @@ async fn dac_task2(mut dac: DacCh2<'static, DAC1, DMA1_CH4>) { } TIM7::enable_and_reset(); - 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| { + TIM7::regs_basic().arr().modify(|w| w.set_arr(reload as u16 - 1)); + TIM7::regs_basic().cr2().modify(|w| w.set_mms(Mms::UPDATE)); + TIM7::regs_basic().cr1().modify(|w| { w.set_opm(false); w.set_cen(true); }); diff --git a/examples/stm32l4/src/bin/rng.rs b/examples/stm32l4/src/bin/rng.rs index 638b3e9e4..14d0e3c1e 100644 --- a/examples/stm32l4/src/bin/rng.rs +++ b/examples/stm32l4/src/bin/rng.rs @@ -3,7 +3,7 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_stm32::rcc::{ClockSrc, Pll, PllMul, PllPreDiv, PllQDiv, PllRDiv, PllSource}; +use embassy_stm32::rcc::{Pll, PllMul, PllPreDiv, PllQDiv, PllRDiv, PllSource, Sysclk}; use embassy_stm32::rng::Rng; use embassy_stm32::{bind_interrupts, peripherals, rng, Config}; use {defmt_rtt as _, panic_probe as _}; @@ -15,7 +15,7 @@ bind_interrupts!(struct Irqs { #[embassy_executor::main] async fn main(_spawner: Spawner) { let mut config = Config::default(); - config.rcc.mux = ClockSrc::PLL1_R; + config.rcc.sys = Sysclk::PLL1_R; config.rcc.hsi = true; config.rcc.pll = Some(Pll { source: PllSource::HSI, diff --git a/examples/stm32l4/src/bin/rtc.rs b/examples/stm32l4/src/bin/rtc.rs index 526620bfb..f554f0f78 100644 --- a/examples/stm32l4/src/bin/rtc.rs +++ b/examples/stm32l4/src/bin/rtc.rs @@ -15,7 +15,7 @@ async fn main(_spawner: Spawner) { let mut config = Config::default(); { use embassy_stm32::rcc::*; - config.rcc.mux = ClockSrc::PLL1_R; + config.rcc.sys = Sysclk::PLL1_R; config.rcc.hse = Some(Hse { freq: Hertz::mhz(8), mode: HseMode::Oscillator, @@ -40,7 +40,7 @@ async fn main(_spawner: Spawner) { .unwrap(); let mut rtc = Rtc::new(p.RTC, RtcConfig::default()); - info!("Got RTC! {:?}", now.timestamp()); + info!("Got RTC! {:?}", now.and_utc().timestamp()); rtc.set_datetime(now.into()).expect("datetime not set"); @@ -48,5 +48,5 @@ async fn main(_spawner: Spawner) { Timer::after_millis(20000).await; let then: NaiveDateTime = rtc.now().unwrap().into(); - info!("Got RTC! {:?}", then.timestamp()); + info!("Got RTC! {:?}", then.and_utc().timestamp()); } diff --git a/examples/stm32l4/src/bin/spe_adin1110_http_server.rs b/examples/stm32l4/src/bin/spe_adin1110_http_server.rs index 5b4cdfe5e..32bfab6eb 100644 --- a/examples/stm32l4/src/bin/spe_adin1110_http_server.rs +++ b/examples/stm32l4/src/bin/spe_adin1110_http_server.rs @@ -58,9 +58,9 @@ const IP_ADDRESS: Ipv4Cidr = Ipv4Cidr::new(Ipv4Address([192, 168, 1, 5]), 24); const HTTP_LISTEN_PORT: u16 = 80; pub type SpeSpi = Spi<'static, peripherals::SPI2, peripherals::DMA1_CH1, peripherals::DMA1_CH2>; -pub type SpeSpiCs = ExclusiveDevice<SpeSpi, Output<'static, peripherals::PB12>, Delay>; -pub type SpeInt = exti::ExtiInput<'static, peripherals::PB11>; -pub type SpeRst = Output<'static, peripherals::PC7>; +pub type SpeSpiCs = ExclusiveDevice<SpeSpi, Output<'static>, Delay>; +pub type SpeInt = exti::ExtiInput<'static>; +pub type SpeRst = Output<'static>; pub type Adin1110T = ADIN1110<SpeSpiCs>; pub type TempSensI2c = I2c<'static, peripherals::I2C3, peripherals::DMA1_CH6, peripherals::DMA1_CH7>; @@ -75,7 +75,7 @@ async fn main(spawner: Spawner) { use embassy_stm32::rcc::*; // 80Mhz clock (Source: 8 / SrcDiv: 1 * PllMul 20 / ClkDiv 2) // 80MHz highest frequency for flash 0 wait. - config.rcc.mux = ClockSrc::PLL1_R; + config.rcc.sys = Sysclk::PLL1_R; config.rcc.hse = Some(Hse { freq: Hertz::mhz(8), mode: HseMode::Oscillator, @@ -134,8 +134,7 @@ async fn main(spawner: Spawner) { let spe_cfg1 = Input::new(dp.PC9, Pull::None); let _spe_ts_capt = Output::new(dp.PC6, Level::Low, Speed::Low); - let spe_int = Input::new(dp.PB11, Pull::None); - let spe_int = exti::ExtiInput::new(spe_int, dp.EXTI11); + let spe_int = exti::ExtiInput::new(dp.PB11, dp.EXTI11, Pull::None); let spe_spi_cs_n = Output::new(dp.PB12, Level::High, Speed::High); let spe_spi_sclk = dp.PB13; @@ -298,7 +297,7 @@ async fn wait_for_config(stack: &'static Stack<Device<'static>>) -> embassy_net: } #[embassy_executor::task] -async fn heartbeat_led(mut led: Output<'static, peripherals::PE6>) { +async fn heartbeat_led(mut led: Output<'static>) { let mut tmr = Ticker::every(Duration::from_hz(3)); loop { led.toggle(); @@ -308,7 +307,7 @@ async fn heartbeat_led(mut led: Output<'static, peripherals::PE6>) { // ADT7422 #[embassy_executor::task] -async fn temp_task(temp_dev_i2c: TempSensI2c, mut led: Output<'static, peripherals::PG15>) -> ! { +async fn temp_task(temp_dev_i2c: TempSensI2c, mut led: Output<'static>) -> ! { let mut tmr = Ticker::every(Duration::from_hz(1)); let mut temp_sens = ADT7422::new(temp_dev_i2c, 0x48).unwrap(); diff --git a/examples/stm32l4/src/bin/usb_serial.rs b/examples/stm32l4/src/bin/usb_serial.rs index 8cc9a7aed..9247e56a1 100644 --- a/examples/stm32l4/src/bin/usb_serial.rs +++ b/examples/stm32l4/src/bin/usb_serial.rs @@ -23,7 +23,7 @@ async fn main(_spawner: Spawner) { let mut config = Config::default(); config.rcc.hsi48 = Some(Hsi48Config { sync_from_usb: true }); // needed for USB - config.rcc.mux = ClockSrc::PLL1_R; + config.rcc.sys = Sysclk::PLL1_R; config.rcc.hsi = true; config.rcc.pll = Some(Pll { source: PllSource::HSI, diff --git a/examples/stm32l5/Cargo.toml b/examples/stm32l5/Cargo.toml index 0c6beb72c..5bcee178f 100644 --- a/examples/stm32l5/Cargo.toml +++ b/examples/stm32l5/Cargo.toml @@ -13,7 +13,7 @@ embassy-time = { version = "0.3.0", path = "../../embassy-time", features = ["de embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "dhcpv4", "medium-ethernet"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } -usbd-hid = "0.6.0" +usbd-hid = "0.7.0" defmt = "0.3" defmt-rtt = "0.4" diff --git a/examples/stm32l5/src/bin/button_exti.rs b/examples/stm32l5/src/bin/button_exti.rs index 91d0ccc2e..e6639d22b 100644 --- a/examples/stm32l5/src/bin/button_exti.rs +++ b/examples/stm32l5/src/bin/button_exti.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; -use embassy_stm32::gpio::{Input, Pull}; +use embassy_stm32::gpio::Pull; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] @@ -12,8 +12,7 @@ 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); + let mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::Down); info!("Press the USER button..."); diff --git a/examples/stm32l5/src/bin/rng.rs b/examples/stm32l5/src/bin/rng.rs index 50da6c946..0a644e73d 100644 --- a/examples/stm32l5/src/bin/rng.rs +++ b/examples/stm32l5/src/bin/rng.rs @@ -3,7 +3,7 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_stm32::rcc::{ClockSrc, Pll, PllMul, PllPreDiv, PllRDiv, PllSource}; +use embassy_stm32::rcc::{Pll, PllMul, PllPreDiv, PllRDiv, PllSource, Sysclk}; use embassy_stm32::rng::Rng; use embassy_stm32::{bind_interrupts, peripherals, rng, Config}; use {defmt_rtt as _, panic_probe as _}; @@ -16,7 +16,7 @@ bind_interrupts!(struct Irqs { async fn main(_spawner: Spawner) { let mut config = Config::default(); config.rcc.hsi = true; - config.rcc.mux = ClockSrc::PLL1_R; + config.rcc.sys = Sysclk::PLL1_R; config.rcc.pll = Some(Pll { // 64Mhz clock (16 / 1 * 8 / 2) source: PllSource::HSI, diff --git a/examples/stm32l5/src/bin/usb_ethernet.rs b/examples/stm32l5/src/bin/usb_ethernet.rs index 88060b6b0..f6d8b16d0 100644 --- a/examples/stm32l5/src/bin/usb_ethernet.rs +++ b/examples/stm32l5/src/bin/usb_ethernet.rs @@ -45,7 +45,7 @@ async fn net_task(stack: &'static Stack<Device<'static, MTU>>) -> ! { async fn main(spawner: Spawner) { let mut config = Config::default(); config.rcc.hsi = true; - config.rcc.mux = ClockSrc::PLL1_R; + config.rcc.sys = Sysclk::PLL1_R; config.rcc.pll = Some(Pll { // 80Mhz clock (16 / 1 * 10 / 2) source: PllSource::HSI, diff --git a/examples/stm32l5/src/bin/usb_hid_mouse.rs b/examples/stm32l5/src/bin/usb_hid_mouse.rs index 7c8a8ebfb..c51ed96e0 100644 --- a/examples/stm32l5/src/bin/usb_hid_mouse.rs +++ b/examples/stm32l5/src/bin/usb_hid_mouse.rs @@ -22,7 +22,7 @@ bind_interrupts!(struct Irqs { async fn main(_spawner: Spawner) { let mut config = Config::default(); config.rcc.hsi = true; - config.rcc.mux = ClockSrc::PLL1_R; + config.rcc.sys = Sysclk::PLL1_R; config.rcc.pll = Some(Pll { // 80Mhz clock (16 / 1 * 10 / 2) source: PllSource::HSI, diff --git a/examples/stm32l5/src/bin/usb_serial.rs b/examples/stm32l5/src/bin/usb_serial.rs index 75053ce4b..87987f2ce 100644 --- a/examples/stm32l5/src/bin/usb_serial.rs +++ b/examples/stm32l5/src/bin/usb_serial.rs @@ -20,7 +20,7 @@ bind_interrupts!(struct Irqs { async fn main(_spawner: Spawner) { let mut config = Config::default(); config.rcc.hsi = true; - config.rcc.mux = ClockSrc::PLL1_R; + config.rcc.sys = Sysclk::PLL1_R; config.rcc.pll = Some(Pll { // 80Mhz clock (16 / 1 * 10 / 2) source: PllSource::HSI, diff --git a/examples/stm32u5/src/bin/flash.rs b/examples/stm32u5/src/bin/flash.rs new file mode 100644 index 000000000..e4fd6bb9c --- /dev/null +++ b/examples/stm32u5/src/bin/flash.rs @@ -0,0 +1,55 @@ +#![no_std] +#![no_main] + +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +use embassy_stm32::flash::Flash; +use embassy_time::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 Flash!"); + + 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_millis(300).await; + + let mut f = Flash::new_blocking(p.FLASH).into_blocking_regions().bank1_region; + + info!("Reading..."); + let mut buf = [0u8; 32]; + unwrap!(f.blocking_read(ADDR, &mut buf)); + info!("Read: {=[u8]:x}", buf); + + info!("Erasing..."); + unwrap!(f.blocking_erase(ADDR, ADDR + 256 * 1024)); + + info!("Reading..."); + let mut buf = [0u8; 32]; + unwrap!(f.blocking_read(ADDR, &mut buf)); + info!("Read after erase: {=[u8]:x}", buf); + + info!("Writing..."); + 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, + 30, 31, 32 + ] + )); + + info!("Reading..."); + let mut buf = [0u8; 32]; + unwrap!(f.blocking_read(ADDR, &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/stm32u5/src/bin/i2c.rs b/examples/stm32u5/src/bin/i2c.rs new file mode 100644 index 000000000..e376c6bc8 --- /dev/null +++ b/examples/stm32u5/src/bin/i2c.rs @@ -0,0 +1,41 @@ +#![no_std] +#![no_main] + +use defmt::{info, unwrap}; +use embassy_executor::Spawner; +use embassy_stm32::dma::NoDma; +use embassy_stm32::i2c::I2c; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, i2c, peripherals}; +use {defmt_rtt as _, panic_probe as _}; + +const HTS221_ADDRESS: u8 = 0x5F; +const WHOAMI: u8 = 0x0F; + +bind_interrupts!(struct Irqs { + I2C2_EV => i2c::EventInterruptHandler<peripherals::I2C2>; + I2C2_ER => i2c::ErrorInterruptHandler<peripherals::I2C2>; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + let mut i2c = I2c::new( + p.I2C2, + p.PH4, + p.PH5, + Irqs, + NoDma, + NoDma, + Hertz(100_000), + Default::default(), + ); + + let mut data = [0u8; 1]; + unwrap!(i2c.blocking_write_read(HTS221_ADDRESS, &[WHOAMI], &mut data)); + + // HTS221 data sheet is here: https://www.st.com/resource/en/datasheet/hts221.pdf + // 7.1 WHO_AM_I command is x0F which expected response xBC. + info!("Whoami: 0x{:02x}", data[0]); + assert_eq!(0xBC, data[0]); +} diff --git a/examples/stm32u5/src/bin/rng.rs b/examples/stm32u5/src/bin/rng.rs new file mode 100644 index 000000000..3a5bce097 --- /dev/null +++ b/examples/stm32u5/src/bin/rng.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::rng::Rng; +use embassy_stm32::{bind_interrupts, peripherals, rng}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + RNG => rng::InterruptHandler<peripherals::RNG>; +}); + +#[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, Irqs); + + let mut buf = [0u8; 16]; + unwrap!(rng.async_fill_bytes(&mut buf).await); + info!("random bytes: {:02x}", buf); +} diff --git a/examples/stm32u5/src/bin/usb_serial.rs b/examples/stm32u5/src/bin/usb_serial.rs index dca34fd0e..61851e5a2 100644 --- a/examples/stm32u5/src/bin/usb_serial.rs +++ b/examples/stm32u5/src/bin/usb_serial.rs @@ -4,7 +4,6 @@ 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}; @@ -22,22 +21,28 @@ async fn main(_spawner: Spawner) { info!("Hello World!"); let mut config = Config::default(); - config.rcc.mux = ClockSrc::PLL1_R(PllConfig { - source: PllSource::HSI, - m: Pllm::DIV2, - n: Plln::MUL10, - p: Plldiv::DIV1, - q: Plldiv::DIV1, - r: Plldiv::DIV1, - }); - config.rcc.hsi48 = Some(Hsi48Config { sync_from_usb: true }); // needed for USB + { + use embassy_stm32::rcc::*; + config.rcc.hsi = true; + config.rcc.pll1 = Some(Pll { + source: PllSource::HSI, // 16 MHz + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL10, + divp: None, + divq: None, + divr: Some(PllDiv::DIV1), // 160 MHz + }); + config.rcc.sys = Sysclk::PLL1_R; + config.rcc.voltage_range = VoltageScale::RANGE1; + config.rcc.hsi48 = Some(Hsi48Config { sync_from_usb: true }); // needed for USB + } 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; + config.vbus_detection = false; let driver = Driver::new_fs(p.USB_OTG_FS, Irqs, p.PA12, p.PA11, &mut ep_out_buffer, config); // Create embassy-usb Config diff --git a/examples/stm32wb/src/bin/button_exti.rs b/examples/stm32wb/src/bin/button_exti.rs index d34dde3e9..2871fd55f 100644 --- a/examples/stm32wb/src/bin/button_exti.rs +++ b/examples/stm32wb/src/bin/button_exti.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; -use embassy_stm32::gpio::{Input, Pull}; +use embassy_stm32::gpio::Pull; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] @@ -12,8 +12,7 @@ async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); info!("Hello World!"); - let button = Input::new(p.PC4, Pull::Up); - let mut button = ExtiInput::new(button, p.EXTI4); + let mut button = ExtiInput::new(p.PC4, p.EXTI4, Pull::Up); info!("Press the USER button..."); diff --git a/examples/stm32wba/src/bin/button_exti.rs b/examples/stm32wba/src/bin/button_exti.rs index 1e970fdd6..34a08bbc6 100644 --- a/examples/stm32wba/src/bin/button_exti.rs +++ b/examples/stm32wba/src/bin/button_exti.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; -use embassy_stm32::gpio::{Input, Pull}; +use embassy_stm32::gpio::Pull; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] @@ -12,8 +12,7 @@ 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); + let mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::Up); info!("Press the USER button..."); diff --git a/examples/stm32wl/src/bin/button_exti.rs b/examples/stm32wl/src/bin/button_exti.rs index e6ad4b80b..27d5330bd 100644 --- a/examples/stm32wl/src/bin/button_exti.rs +++ b/examples/stm32wl/src/bin/button_exti.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; use embassy_stm32::exti::ExtiInput; -use embassy_stm32::gpio::{Input, Pull}; +use embassy_stm32::gpio::Pull; use {defmt_rtt as _, panic_probe as _}; #[embassy_executor::main] @@ -12,8 +12,7 @@ async fn main(_spawner: Spawner) { let p = embassy_stm32::init(Default::default()); info!("Hello World!"); - let button = Input::new(p.PA0, Pull::Up); - let mut button = ExtiInput::new(button, p.EXTI0); + let mut button = ExtiInput::new(p.PA0, p.EXTI0, Pull::Up); info!("Press the USER button..."); diff --git a/examples/stm32wl/src/bin/random.rs b/examples/stm32wl/src/bin/random.rs index 3610392be..8e9fe02b2 100644 --- a/examples/stm32wl/src/bin/random.rs +++ b/examples/stm32wl/src/bin/random.rs @@ -22,7 +22,7 @@ async fn main(_spawner: Spawner) { mode: HseMode::Bypass, prescaler: HsePrescaler::DIV1, }); - config.rcc.mux = ClockSrc::PLL1_R; + config.rcc.sys = Sysclk::PLL1_R; config.rcc.pll = Some(Pll { source: PllSource::HSE, prediv: PllPreDiv::DIV2, diff --git a/examples/stm32wl/src/bin/rtc.rs b/examples/stm32wl/src/bin/rtc.rs index 4738d5770..cf7d6d220 100644 --- a/examples/stm32wl/src/bin/rtc.rs +++ b/examples/stm32wl/src/bin/rtc.rs @@ -21,7 +21,7 @@ async fn main(_spawner: Spawner) { mode: HseMode::Bypass, prescaler: HsePrescaler::DIV1, }); - config.rcc.mux = ClockSrc::PLL1_R; + config.rcc.sys = Sysclk::PLL1_R; config.rcc.pll = Some(Pll { source: PllSource::HSE, prediv: PllPreDiv::DIV2, @@ -40,7 +40,7 @@ async fn main(_spawner: Spawner) { .unwrap(); let mut rtc = Rtc::new(p.RTC, RtcConfig::default()); - info!("Got RTC! {:?}", now.timestamp()); + info!("Got RTC! {:?}", now.and_utc().timestamp()); rtc.set_datetime(now.into()).expect("datetime not set"); @@ -48,5 +48,5 @@ async fn main(_spawner: Spawner) { Timer::after_millis(20000).await; let then: NaiveDateTime = rtc.now().unwrap().into(); - info!("Got RTC! {:?}", then.timestamp()); + info!("Got RTC! {:?}", then.and_utc().timestamp()); } diff --git a/examples/stm32wl/src/bin/uart_async.rs b/examples/stm32wl/src/bin/uart_async.rs index 8e545834c..3637243a0 100644 --- a/examples/stm32wl/src/bin/uart_async.rs +++ b/examples/stm32wl/src/bin/uart_async.rs @@ -20,7 +20,7 @@ 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::HSE; + config.rcc.sys = embassy_stm32::rcc::Sysclk::HSE; let p = embassy_stm32::init(config); defmt::info!("Starting system"); diff --git a/tests/nrf/src/bin/buffered_uart.rs b/tests/nrf/src/bin/buffered_uart.rs deleted file mode 100644 index 354d787b4..000000000 --- a/tests/nrf/src/bin/buffered_uart.rs +++ /dev/null @@ -1,78 +0,0 @@ -#![no_std] -#![no_main] -teleprobe_meta::target!(b"nrf52840-dk"); - -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<peripherals::UARTE0>; -}); - -#[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/nrf51422/.cargo/config.toml b/tests/nrf51422/.cargo/config.toml new file mode 100644 index 000000000..634805633 --- /dev/null +++ b/tests/nrf51422/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +#runner = "teleprobe local run --chip nRF51422_xxAA --elf" +runner = "teleprobe client run" + +[build] +target = "thumbv6m-none-eabi" + +[env] +DEFMT_LOG = "trace,embassy_hal_internal=debug" diff --git a/tests/nrf51422/Cargo.toml b/tests/nrf51422/Cargo.toml new file mode 100644 index 000000000..07236987b --- /dev/null +++ b/tests/nrf51422/Cargo.toml @@ -0,0 +1,23 @@ +[package] +edition = "2021" +name = "embassy-nrf51-tests" +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.5.0", path = "../../embassy-sync", features = ["defmt", ] } +embassy-executor = { version = "0.5.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt", "task-arena-size-128", "integrated-timers"] } +embassy-time = { version = "0.3.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime"] } +embassy-nrf = { version = "0.1.0", path = "../../embassy-nrf", features = ["defmt", "nrf51", "time-driver-rtc1", "unstable-pac", "time", "gpiote"] } +embedded-io-async = { version = "0.6.1", features = ["defmt-03"] } +embedded-hal-async = { version = "1.0" } + +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"] } diff --git a/tests/nrf51422/build.rs b/tests/nrf51422/build.rs new file mode 100644 index 000000000..13ebbe4ee --- /dev/null +++ b/tests/nrf51422/build.rs @@ -0,0 +1,17 @@ +use std::error::Error; +use std::path::PathBuf; +use std::{env, fs}; + +fn main() -> Result<(), Box<dyn Error>> { + let out = PathBuf::from(env::var("OUT_DIR").unwrap()); + fs::write(out.join("memory.x"), include_bytes!("memory.x")).unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + 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"); + println!("cargo:rustc-link-arg-bins=-Tteleprobe.x"); + + Ok(()) +} diff --git a/tests/nrf51422/memory.x b/tests/nrf51422/memory.x new file mode 100644 index 000000000..a5881e66f --- /dev/null +++ b/tests/nrf51422/memory.x @@ -0,0 +1,5 @@ +MEMORY +{ + FLASH : ORIGIN = 0x00000000, LENGTH = 128K + RAM : ORIGIN = 0x20000000, LENGTH = 16K +} diff --git a/tests/nrf51422/src/bin/gpio.rs b/tests/nrf51422/src/bin/gpio.rs new file mode 100644 index 000000000..6d5a87d0a --- /dev/null +++ b/tests/nrf51422/src/bin/gpio.rs @@ -0,0 +1,28 @@ +#![no_std] +#![no_main] +teleprobe_meta::target!(b"nrf51-dk"); + +use defmt::{assert, info}; +use embassy_executor::Spawner; +use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pull}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_nrf::init(Default::default()); + + let input = Input::new(p.P0_13, Pull::Up); + let mut output = Output::new(p.P0_14, Level::Low, OutputDrive::Standard); + + output.set_low(); + Timer::after_millis(10).await; + assert!(input.is_low()); + + output.set_high(); + Timer::after_millis(10).await; + assert!(input.is_high()); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/tests/nrf51422/src/bin/gpiote.rs b/tests/nrf51422/src/bin/gpiote.rs new file mode 100644 index 000000000..330fe993e --- /dev/null +++ b/tests/nrf51422/src/bin/gpiote.rs @@ -0,0 +1,47 @@ +#![no_std] +#![no_main] +teleprobe_meta::target!(b"nrf51-dk"); + +use defmt::{assert, info}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pull}; +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()); + + let mut input = Input::new(p.P0_13, Pull::Up); + let mut output = Output::new(p.P0_14, Level::Low, OutputDrive::Standard); + + let fut1 = async { + Timer::after_millis(100).await; + output.set_high(); + }; + let fut2 = async { + let start = Instant::now(); + input.wait_for_high().await; + let dur = Instant::now() - start; + assert!((Duration::from_millis(90)..Duration::from_millis(110)).contains(&dur)); + }; + + join(fut1, fut2).await; + + let fut1 = async { + Timer::after_millis(100).await; + output.set_low(); + }; + let fut2 = async { + let start = Instant::now(); + input.wait_for_low().await; + let dur = Instant::now() - start; + assert!((Duration::from_millis(90)..Duration::from_millis(110)).contains(&dur)); + }; + + join(fut1, fut2).await; + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/tests/nrf51422/src/bin/timer.rs b/tests/nrf51422/src/bin/timer.rs new file mode 100644 index 000000000..cf9ea41a8 --- /dev/null +++ b/tests/nrf51422/src/bin/timer.rs @@ -0,0 +1,24 @@ +#![no_std] +#![no_main] +teleprobe_meta::target!(b"nrf51-dk"); + +use defmt::{assert, info}; +use embassy_executor::Spawner; +use embassy_time::{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_millis(100).await; + let end = Instant::now(); + let ms = (end - start).as_millis(); + info!("slept for {} ms", ms); + assert!(ms >= 99); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/tests/nrf/.cargo/config.toml b/tests/nrf52840/.cargo/config.toml similarity index 100% rename from tests/nrf/.cargo/config.toml rename to tests/nrf52840/.cargo/config.toml diff --git a/tests/nrf/Cargo.toml b/tests/nrf52840/Cargo.toml similarity index 100% rename from tests/nrf/Cargo.toml rename to tests/nrf52840/Cargo.toml diff --git a/tests/nrf/build.rs b/tests/nrf52840/build.rs similarity index 100% rename from tests/nrf/build.rs rename to tests/nrf52840/build.rs diff --git a/tests/nrf/memory.x b/tests/nrf52840/memory.x similarity index 100% rename from tests/nrf/memory.x rename to tests/nrf52840/memory.x diff --git a/tests/nrf52840/src/bin/buffered_uart.rs b/tests/nrf52840/src/bin/buffered_uart.rs new file mode 100644 index 000000000..a01d66d85 --- /dev/null +++ b/tests/nrf52840/src/bin/buffered_uart.rs @@ -0,0 +1,81 @@ +#![no_std] +#![no_main] +teleprobe_meta::target!(b"nrf52840-dk"); + +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<peripherals::UARTE0>; +}); + +#[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]; + + // test teardown + recreate of the buffereduarte works fine. + for _ in 0..2 { + let u = BufferedUarte::new( + &mut p.UARTE0, + &mut p.TIMER0, + &mut p.PPI_CH0, + &mut p.PPI_CH1, + &mut p.PPI_GROUP0, + Irqs, + &mut p.P1_03, + &mut 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_full.rs b/tests/nrf52840/src/bin/buffered_uart_full.rs similarity index 98% rename from tests/nrf/src/bin/buffered_uart_full.rs rename to tests/nrf52840/src/bin/buffered_uart_full.rs index e59c75ba9..62edaed25 100644 --- a/tests/nrf/src/bin/buffered_uart_full.rs +++ b/tests/nrf52840/src/bin/buffered_uart_full.rs @@ -23,7 +23,7 @@ async fn main(_spawner: Spawner) { let mut tx_buffer = [0u8; 1024]; let mut rx_buffer = [0u8; 1024]; - let mut u = BufferedUarte::new( + let u = BufferedUarte::new( p.UARTE0, p.TIMER0, p.PPI_CH0, diff --git a/tests/nrf52840/src/bin/buffered_uart_halves.rs b/tests/nrf52840/src/bin/buffered_uart_halves.rs new file mode 100644 index 000000000..54a9fef5b --- /dev/null +++ b/tests/nrf52840/src/bin/buffered_uart_halves.rs @@ -0,0 +1,82 @@ +#![no_std] +#![no_main] +teleprobe_meta::target!(b"nrf52840-dk"); + +use defmt::{assert_eq, *}; +use embassy_executor::Spawner; +use embassy_futures::join::join; +use embassy_nrf::buffered_uarte::{self, BufferedUarteRx, BufferedUarteTx}; +use embassy_nrf::{bind_interrupts, peripherals, uarte}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + UARTE0_UART0 => buffered_uarte::InterruptHandler<peripherals::UARTE0>; + UARTE1 => buffered_uarte::InterruptHandler<peripherals::UARTE1>; +}); + +#[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]; + + // test teardown + recreate of the buffereduarte works fine. + for _ in 0..2 { + const COUNT: usize = 40_000; + + let mut tx = BufferedUarteTx::new(&mut p.UARTE1, Irqs, &mut p.P1_02, config.clone(), &mut tx_buffer); + + let mut rx = BufferedUarteRx::new( + &mut p.UARTE0, + &mut p.TIMER0, + &mut p.PPI_CH0, + &mut p.PPI_CH1, + &mut p.PPI_GROUP0, + Irqs, + &mut p.P1_03, + config.clone(), + &mut rx_buffer, + ); + + let tx_fut = async { + info!("tx initialized!"); + + 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 { + info!("rx initialized!"); + + 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/nrf52840/src/bin/buffered_uart_spam.rs similarity index 100% rename from tests/nrf/src/bin/buffered_uart_spam.rs rename to tests/nrf52840/src/bin/buffered_uart_spam.rs diff --git a/tests/nrf/src/bin/ethernet_enc28j60_perf.rs b/tests/nrf52840/src/bin/ethernet_enc28j60_perf.rs similarity index 94% rename from tests/nrf/src/bin/ethernet_enc28j60_perf.rs rename to tests/nrf52840/src/bin/ethernet_enc28j60_perf.rs index 7dc1941d7..33c2f4235 100644 --- a/tests/nrf/src/bin/ethernet_enc28j60_perf.rs +++ b/tests/nrf52840/src/bin/ethernet_enc28j60_perf.rs @@ -21,10 +21,7 @@ bind_interrupts!(struct Irqs { RNG => embassy_nrf::rng::InterruptHandler<peripherals::RNG>; }); -type MyDriver = Enc28j60< - ExclusiveDevice<Spim<'static, peripherals::SPI3>, Output<'static, peripherals::P0_15>, Delay>, - Output<'static, peripherals::P0_13>, ->; +type MyDriver = Enc28j60<ExclusiveDevice<Spim<'static, peripherals::SPI3>, Output<'static>, Delay>, Output<'static>>; #[embassy_executor::task] async fn net_task(stack: &'static Stack<MyDriver>) -> ! { diff --git a/tests/nrf/src/bin/timer.rs b/tests/nrf52840/src/bin/timer.rs similarity index 96% rename from tests/nrf/src/bin/timer.rs rename to tests/nrf52840/src/bin/timer.rs index 2a147e7ba..117947a94 100644 --- a/tests/nrf/src/bin/timer.rs +++ b/tests/nrf52840/src/bin/timer.rs @@ -18,7 +18,6 @@ async fn main(_spawner: Spawner) { 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/nrf52840/src/bin/wifi_esp_hosted_perf.rs similarity index 90% rename from tests/nrf/src/bin/wifi_esp_hosted_perf.rs rename to tests/nrf52840/src/bin/wifi_esp_hosted_perf.rs index c96064f84..b83edddc4 100644 --- a/tests/nrf/src/bin/wifi_esp_hosted_perf.rs +++ b/tests/nrf52840/src/bin/wifi_esp_hosted_perf.rs @@ -6,7 +6,7 @@ teleprobe_meta::timeout!(120); use defmt::{info, unwrap}; use embassy_executor::Spawner; use embassy_net::{Config, Stack, StackResources}; -use embassy_nrf::gpio::{AnyPin, Input, Level, Output, OutputDrive, Pin, Pull}; +use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pull}; use embassy_nrf::rng::Rng; use embassy_nrf::spim::{self, Spim}; use embassy_nrf::{bind_interrupts, peripherals}; @@ -28,9 +28,9 @@ const WIFI_PASSWORD: &str = "V8YxhKt5CdIAJFud"; async fn wifi_task( runner: hosted::Runner< 'static, - ExclusiveDevice<Spim<'static, peripherals::SPI3>, Output<'static, peripherals::P0_31>, Delay>, - Input<'static, AnyPin>, - Output<'static, peripherals::P1_05>, + ExclusiveDevice<Spim<'static, peripherals::SPI3>, Output<'static>, Delay>, + Input<'static>, + Output<'static>, >, ) -> ! { runner.run().await @@ -53,8 +53,8 @@ async fn main(spawner: Spawner) { 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 handshake = Input::new(p.P1_01, Pull::Up); + let ready = Input::new(p.P1_04, Pull::None); let reset = Output::new(p.P1_05, Level::Low, OutputDrive::Standard); let mut config = spim::Config::default(); diff --git a/tests/rp/.cargo/config.toml b/tests/rp/.cargo/config.toml index 40b5d7000..de7bb0e56 100644 --- a/tests/rp/.cargo/config.toml +++ b/tests/rp/.cargo/config.toml @@ -10,7 +10,7 @@ runner = "teleprobe client run" rustflags = [ # Code-size optimizations. - "-Z", "trap-unreachable=no", + #"-Z", "trap-unreachable=no", "-C", "inline-threshold=5", "-C", "no-vectorize-loops", ] diff --git a/tests/rp/Cargo.toml b/tests/rp/Cargo.toml index 46e1e9a5f..e67f2117d 100644 --- a/tests/rp/Cargo.toml +++ b/tests/rp/Cargo.toml @@ -14,6 +14,7 @@ embassy-rp = { version = "0.1.0", path = "../../embassy-rp", features = [ "defmt embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } embassy-net = { version = "0.4.0", path = "../../embassy-net", features = ["defmt", "tcp", "udp", "dhcpv4", "medium-ethernet"] } embassy-net-wiznet = { version = "0.1.0", path = "../../embassy-net-wiznet", features = ["defmt"] } +embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal/"} cyw43 = { path = "../../cyw43", features = ["defmt", "firmware-logs"] } cyw43-pio = { path = "../../cyw43-pio", features = ["defmt", "overclock"] } perf-client = { path = "../perf-client" } diff --git a/tests/rp/src/bin/cyw43-perf.rs b/tests/rp/src/bin/cyw43-perf.rs index a1b2946e6..b46ae670a 100644 --- a/tests/rp/src/bin/cyw43-perf.rs +++ b/tests/rp/src/bin/cyw43-perf.rs @@ -7,7 +7,7 @@ use defmt::{panic, *}; use embassy_executor::Spawner; use embassy_net::{Config, Stack, StackResources}; use embassy_rp::gpio::{Level, Output}; -use embassy_rp::peripherals::{DMA_CH0, PIN_23, PIN_25, PIO0}; +use embassy_rp::peripherals::{DMA_CH0, PIO0}; use embassy_rp::pio::{InterruptHandler, Pio}; use embassy_rp::{bind_interrupts, rom_data}; use static_cell::StaticCell; @@ -24,9 +24,7 @@ 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>>, -) -> ! { +async fn wifi_task(runner: cyw43::Runner<'static, Output<'static>, PioSpi<'static, PIO0, 0, DMA_CH0>>) -> ! { runner.run().await } diff --git a/tests/rp/src/bin/ethernet_w5100s_perf.rs b/tests/rp/src/bin/ethernet_w5100s_perf.rs index 8c9089d0e..5d5547773 100644 --- a/tests/rp/src/bin/ethernet_w5100s_perf.rs +++ b/tests/rp/src/bin/ethernet_w5100s_perf.rs @@ -10,7 +10,7 @@ use embassy_net_wiznet::chip::W5100S; use embassy_net_wiznet::*; 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::peripherals::SPI0; use embassy_rp::spi::{Async, Config as SpiConfig, Spi}; use embassy_time::Delay; use embedded_hal_bus::spi::ExclusiveDevice; @@ -23,9 +23,9 @@ async fn ethernet_task( runner: Runner< 'static, W5100S, - ExclusiveDevice<Spi<'static, SPI0, Async>, Output<'static, PIN_17>, Delay>, - Input<'static, PIN_21>, - Output<'static, PIN_20>, + ExclusiveDevice<Spi<'static, SPI0, Async>, Output<'static>, Delay>, + Input<'static>, + Output<'static>, >, ) -> ! { runner.run().await diff --git a/tests/rp/src/bin/i2c.rs b/tests/rp/src/bin/i2c.rs index 77d628cf6..153b37999 100644 --- a/tests/rp/src/bin/i2c.rs +++ b/tests/rp/src/bin/i2c.rs @@ -3,7 +3,10 @@ teleprobe_meta::target!(b"rpi-pico"); use defmt::{assert_eq, info, panic, unwrap}; -use embassy_executor::Executor; +use embassy_embedded_hal::SetConfig; +use embassy_executor::{Executor, Spawner}; +use embassy_rp::clocks::{PllConfig, XoscConfig}; +use embassy_rp::config::Config as rpConfig; use embassy_rp::multicore::{spawn_core1, Stack}; use embassy_rp::peripherals::{I2C0, I2C1}; use embassy_rp::{bind_interrupts, i2c, i2c_slave}; @@ -13,7 +16,6 @@ use static_cell::StaticCell; use {defmt_rtt as _, panic_probe as _, panic_probe as _, panic_probe as _}; static mut CORE1_STACK: Stack<1024> = Stack::new(); -static EXECUTOR0: StaticCell<Executor> = StaticCell::new(); static EXECUTOR1: StaticCell<Executor> = StaticCell::new(); use crate::i2c::AbortReason; @@ -44,10 +46,7 @@ async fn device_task(mut dev: i2c_slave::I2cSlave<'static, I2C1>) -> ! { Ok(x) => match x { i2c_slave::ReadStatus::Done => break, i2c_slave::ReadStatus::NeedMoreBytes => count += 1, - i2c_slave::ReadStatus::LeftoverBytes(x) => { - info!("tried to write {} extra bytes", x); - break; - } + i2c_slave::ReadStatus::LeftoverBytes(x) => panic!("tried to write {} extra bytes", x), }, Err(e) => match e { embassy_rp::i2c_slave::Error::Abort(AbortReason::Other(n)) => panic!("Other {:b}", n), @@ -80,7 +79,7 @@ async fn device_task(mut dev: i2c_slave::I2cSlave<'static, I2C1>) -> ! { _ => panic!("Invalid write length {}", len), }, Ok(i2c_slave::Command::WriteRead(len)) => { - info!("device recieved write read: {:x}", buf[..len]); + info!("device received write read: {:x}", buf[..len]); match buf[0] { 0xC2 => { let resp_buff = [0xD1, 0xD2, 0xD3, 0xD4]; @@ -92,6 +91,8 @@ async fn device_task(mut dev: i2c_slave::I2cSlave<'static, I2C1>) -> ! { resp_buff[i] = i as u8; } dev.respond_to_read(&resp_buff).await.unwrap(); + // reset count for next round of tests + count = 0xD0; } x => panic!("Invalid Write Read {:x}", x), } @@ -104,8 +105,7 @@ async fn device_task(mut dev: i2c_slave::I2cSlave<'static, I2C1>) -> ! { } } -#[embassy_executor::task] -async fn controller_task(mut con: i2c::I2c<'static, I2C0, i2c::Async>) { +async fn controller_task(con: &mut i2c::I2c<'static, I2C0, i2c::Async>) { info!("Device start"); { @@ -179,33 +179,55 @@ async fn controller_task(mut con: i2c::I2c<'static, I2C0, i2c::Async>) { info!("large write_read - OK") } - info!("Test OK"); - cortex_m::asm::bkpt(); -} - -#[cortex_m_rt::entry] -fn main() -> ! { - let p = embassy_rp::init(Default::default()); - info!("Hello World!"); - - let d_sda = p.PIN_19; - let d_scl = p.PIN_18; - let mut config = i2c_slave::Config::default(); - config.addr = DEV_ADDR as u16; - let device = i2c_slave::I2cSlave::new(p.I2C1, d_sda, d_scl, Irqs, config); - - spawn_core1(p.CORE1, unsafe { &mut CORE1_STACK }, move || { - let executor1 = EXECUTOR1.init(Executor::new()); - executor1.run(|spawner| unwrap!(spawner.spawn(device_task(device)))); - }); - - let executor0 = EXECUTOR0.init(Executor::new()); - - let c_sda = p.PIN_21; - let c_scl = p.PIN_20; - let mut config = i2c::Config::default(); - config.frequency = 5_000; - let controller = i2c::I2c::new_async(p.I2C0, c_sda, c_scl, Irqs, config); - - executor0.run(|spawner| unwrap!(spawner.spawn(controller_task(controller)))); + #[embassy_executor::main] + async fn main(_core0_spawner: Spawner) { + let mut config = rpConfig::default(); + // Configure clk_sys to 48MHz to support 1kHz scl. + // In theory it can go lower, but we won't bother to test below 1kHz. + config.clocks.xosc = Some(XoscConfig { + hz: 12_000_000, + delay_multiplier: 128, + sys_pll: Some(PllConfig { + refdiv: 1, + fbdiv: 120, + post_div1: 6, + post_div2: 5, + }), + usb_pll: Some(PllConfig { + refdiv: 1, + fbdiv: 120, + post_div1: 6, + post_div2: 5, + }), + }); + + let p = embassy_rp::init(config); + info!("Hello World!"); + + let d_sda = p.PIN_19; + let d_scl = p.PIN_18; + let mut config = i2c_slave::Config::default(); + config.addr = DEV_ADDR as u16; + let device = i2c_slave::I2cSlave::new(p.I2C1, d_sda, d_scl, Irqs, config); + + spawn_core1(p.CORE1, unsafe { &mut CORE1_STACK }, move || { + let executor1 = EXECUTOR1.init(Executor::new()); + executor1.run(|spawner| unwrap!(spawner.spawn(device_task(device)))); + }); + + let c_sda = p.PIN_21; + let c_scl = p.PIN_20; + let mut controller = i2c::I2c::new_async(p.I2C0, c_sda, c_scl, Irqs, Default::default()); + + for freq in [1000, 100_000, 400_000, 1_000_000] { + info!("testing at {}hz", freq); + let mut config = i2c::Config::default(); + config.frequency = freq; + controller.set_config(&config).unwrap(); + controller_task(&mut controller).await; + } + + info!("Test OK"); + cortex_m::asm::bkpt(); + } } diff --git a/tests/rp/src/bin/uart.rs b/tests/rp/src/bin/uart.rs index f4d641175..6e6e5517b 100644 --- a/tests/rp/src/bin/uart.rs +++ b/tests/rp/src/bin/uart.rs @@ -21,7 +21,7 @@ fn read1<const N: usize>(uart: &mut UartRx<'_, impl Instance, Blocking>) -> Resu Ok(buf) } -async fn send(pin: &mut Output<'_, impl embassy_rp::gpio::Pin>, v: u8, parity: Option<bool>) { +async fn send(pin: &mut Output<'_>, v: u8, parity: Option<bool>) { pin.set_low(); Timer::after_millis(1).await; for i in 0..8 { @@ -116,7 +116,7 @@ async fn main(_spawner: Spawner) { 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) { + async fn chr(pin: &mut Output<'_>, v: u8, parity: u8) { send(pin, v, Some(parity != 0)).await; } @@ -142,7 +142,7 @@ async fn main(_spawner: Spawner) { 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) { + async fn chr(pin: &mut Output<'_>, v: u8, good: bool) { if good { send(pin, v, None).await; } else { diff --git a/tests/rp/src/bin/uart_buffered.rs b/tests/rp/src/bin/uart_buffered.rs index 14647e44a..d68c23cbd 100644 --- a/tests/rp/src/bin/uart_buffered.rs +++ b/tests/rp/src/bin/uart_buffered.rs @@ -36,7 +36,7 @@ async fn read1<const N: usize>(uart: &mut BufferedUartRx<'_, impl Instance>) -> } } -async fn send(pin: &mut Output<'_, impl embassy_rp::gpio::Pin>, v: u8, parity: Option<bool>) { +async fn send(pin: &mut Output<'_>, v: u8, parity: Option<bool>) { pin.set_low(); Timer::after_millis(1).await; for i in 0..8 { @@ -161,7 +161,7 @@ async fn main(_spawner: Spawner) { 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) { + async fn chr(pin: &mut Output<'_>, v: u8, parity: u32) { send(pin, v, Some(parity != 0)).await; } @@ -208,7 +208,7 @@ async fn main(_spawner: Spawner) { 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) { + async fn chr(pin: &mut Output<'_>, v: u8, good: bool) { if good { send(pin, v, None).await; } else { diff --git a/tests/rp/src/bin/uart_dma.rs b/tests/rp/src/bin/uart_dma.rs index 130d8599e..edc87175a 100644 --- a/tests/rp/src/bin/uart_dma.rs +++ b/tests/rp/src/bin/uart_dma.rs @@ -27,7 +27,7 @@ async fn read1<const N: usize>(uart: &mut UartRx<'_, impl Instance, Async>) -> R Ok(buf) } -async fn send(pin: &mut Output<'_, impl embassy_rp::gpio::Pin>, v: u8, parity: Option<bool>) { +async fn send(pin: &mut Output<'_>, v: u8, parity: Option<bool>) { pin.set_low(); Timer::after_millis(1).await; for i in 0..8 { @@ -160,7 +160,7 @@ async fn main(_spawner: Spawner) { 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) { + async fn chr(pin: &mut Output<'_>, v: u8, parity: u32) { send(pin, v, Some(parity != 0)).await; } @@ -205,7 +205,7 @@ async fn main(_spawner: Spawner) { 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) { + async fn chr(pin: &mut Output<'_>, v: u8, good: bool) { if good { send(pin, v, None).await; } else { diff --git a/tests/stm32/.cargo/config.toml b/tests/stm32/.cargo/config.toml index 2e3f055d4..528bd3451 100644 --- a/tests/stm32/.cargo/config.toml +++ b/tests/stm32/.cargo/config.toml @@ -1,6 +1,6 @@ [unstable] -build-std = ["core"] -build-std-features = ["panic_immediate_abort"] +#build-std = ["core"] +#build-std-features = ["panic_immediate_abort"] [target.'cfg(all(target_arch = "arm", target_os = "none"))'] runner = "teleprobe client run" @@ -8,16 +8,16 @@ runner = "teleprobe client run" rustflags = [ # Code-size optimizations. - "-Z", "trap-unreachable=no", + #"-Z", "trap-unreachable=no", "-C", "inline-threshold=5", "-C", "no-vectorize-loops", ] [build] -target = "thumbv6m-none-eabi" +#target = "thumbv6m-none-eabi" #target = "thumbv7m-none-eabi" -#target = "thumbv7em-none-eabi" +target = "thumbv7em-none-eabi" #target = "thumbv8m.main-none-eabihf" [env] -DEFMT_LOG = "trace,embassy_hal_internal=debug,embassy_net_esp_hosted=debug,smoltcp=info" \ No newline at end of file +DEFMT_LOG = "trace,embassy_hal_internal=debug,embassy_net_esp_hosted=debug,smoltcp=info" diff --git a/tests/stm32/Cargo.toml b/tests/stm32/Cargo.toml index bf85f05d2..bfe003a11 100644 --- a/tests/stm32/Cargo.toml +++ b/tests/stm32/Cargo.toml @@ -14,29 +14,34 @@ stm32f429zi = ["embassy-stm32/stm32f429zi", "chrono", "eth", "stop", "can", "not stm32f446re = ["embassy-stm32/stm32f446re", "chrono", "stop", "can", "not-gpdma", "dac", "sdmmc"] stm32f767zi = ["embassy-stm32/stm32f767zi", "chrono", "not-gpdma", "eth", "rng"] stm32g071rb = ["embassy-stm32/stm32g071rb", "cm0", "not-gpdma", "dac"] -stm32g491re = ["embassy-stm32/stm32g491re", "chrono", "stop", "not-gpdma", "rng"] -stm32h563zi = ["embassy-stm32/stm32h563zi", "chrono", "eth", "rng"] -stm32h753zi = ["embassy-stm32/stm32h753zi", "chrono", "not-gpdma", "eth", "rng"] -stm32h755zi = ["embassy-stm32/stm32h755zi-cm7", "chrono", "not-gpdma", "eth", "dac", "rng"] -stm32h7a3zi = ["embassy-stm32/stm32h7a3zi", "not-gpdma", "rng"] +stm32g491re = ["embassy-stm32/stm32g491re", "chrono", "stop", "not-gpdma", "rng", "fdcan"] +stm32h563zi = ["embassy-stm32/stm32h563zi", "chrono", "eth", "rng", "hash"] +stm32h753zi = ["embassy-stm32/stm32h753zi", "chrono", "not-gpdma", "eth", "rng", "fdcan", "hash", "cryp"] +stm32h755zi = ["embassy-stm32/stm32h755zi-cm7", "chrono", "not-gpdma", "eth", "dac", "rng", "fdcan", "hash", "cryp"] +stm32h7a3zi = ["embassy-stm32/stm32h7a3zi", "not-gpdma", "rng", "fdcan"] stm32l073rz = ["embassy-stm32/stm32l073rz", "cm0", "not-gpdma", "rng"] stm32l152re = ["embassy-stm32/stm32l152re", "chrono", "not-gpdma"] stm32l496zg = ["embassy-stm32/stm32l496zg", "not-gpdma", "rng"] -stm32l4a6zg = ["embassy-stm32/stm32l4a6zg", "chrono", "not-gpdma", "rng"] +stm32l4a6zg = ["embassy-stm32/stm32l4a6zg", "chrono", "not-gpdma", "rng", "hash"] stm32l4r5zi = ["embassy-stm32/stm32l4r5zi", "chrono", "not-gpdma", "rng"] -stm32l552ze = ["embassy-stm32/stm32l552ze", "not-gpdma", "rng"] -stm32u585ai = ["embassy-stm32/stm32u585ai", "chrono", "rng"] -stm32u5a5zj = ["embassy-stm32/stm32u5a5zj", "chrono", "rng"] +stm32l552ze = ["embassy-stm32/stm32l552ze", "not-gpdma", "rng", "hash"] +stm32u585ai = ["embassy-stm32/stm32u585ai", "chrono", "rng", "hash"] +stm32u5a5zj = ["embassy-stm32/stm32u5a5zj", "chrono", "rng", "hash"] stm32wb55rg = ["embassy-stm32/stm32wb55rg", "chrono", "not-gpdma", "ble", "mac" , "rng"] -stm32wba52cg = ["embassy-stm32/stm32wba52cg", "chrono", "rng"] +stm32wba52cg = ["embassy-stm32/stm32wba52cg", "chrono", "rng", "hash"] stm32wl55jc = ["embassy-stm32/stm32wl55jc-cm4", "not-gpdma", "rng", "chrono"] +stm32f091rc = ["embassy-stm32/stm32f091rc", "cm0", "not-gpdma", "chrono"] +stm32h503rb = ["embassy-stm32/stm32h503rb", "rng"] +cryp = [] +hash = [] eth = ["embassy-executor/task-arena-size-16384"] rng = [] sdmmc = [] stop = ["embassy-stm32/low-power", "embassy-stm32/low-power-debug-with-sleep"] chrono = ["embassy-stm32/chrono", "dep:chrono"] can = [] +fdcan = [] ble = ["dep:embassy-stm32-wpan", "embassy-stm32-wpan/ble"] mac = ["dep:embassy-stm32-wpan", "embassy-stm32-wpan/mac"] embassy-stm32-wpan = [] @@ -65,6 +70,7 @@ cortex-m-rt = "0.7.0" embedded-hal = "0.2.6" embedded-hal-1 = { package = "embedded-hal", version = "1.0" } embedded-hal-async = { version = "1.0" } +embedded-can = { version = "0.4" } micromath = "2.0.0" panic-probe = { version = "0.3.0", features = ["print-defmt"] } rand_core = { version = "0.6", default-features = false } @@ -73,6 +79,9 @@ static_cell = "2" portable-atomic = { version = "1.5", features = [] } chrono = { version = "^0.4", default-features = false, optional = true} +sha2 = { version = "0.10.8", default-features = false } +hmac = "0.12.1" +aes-gcm = {version = "0.10.3", default-features = false, features = ["aes", "heapless"] } # BEGIN TESTS # Generated by gen_test.py. DO NOT EDIT. @@ -81,6 +90,11 @@ name = "can" path = "src/bin/can.rs" required-features = [ "can",] +[[bin]] +name = "cryp" +path = "src/bin/cryp.rs" +required-features = [ "cryp",] + [[bin]] name = "dac" path = "src/bin/dac.rs" @@ -96,11 +110,21 @@ name = "eth" path = "src/bin/eth.rs" required-features = [ "eth",] +[[bin]] +name = "fdcan" +path = "src/bin/fdcan.rs" +required-features = [ "fdcan",] + [[bin]] name = "gpio" path = "src/bin/gpio.rs" required-features = [] +[[bin]] +name = "hash" +path = "src/bin/hash.rs" +required-features = [ "hash",] + [[bin]] name = "rng" path = "src/bin/rng.rs" diff --git a/tests/stm32/build.rs b/tests/stm32/build.rs index f32a7b2f8..176adff62 100644 --- a/tests/stm32/build.rs +++ b/tests/stm32/build.rs @@ -16,6 +16,9 @@ fn main() -> Result<(), Box<dyn Error>> { feature = "stm32l073rz", // wrong ram size in stm32-data feature = "stm32wl55jc", + feature = "stm32u5a5zj", + // no VTOR, so interrupts can't work when running from RAM + feature = "stm32f091rc", )) { println!("cargo:rustc-link-arg-bins=-Tlink.x"); println!("cargo:rerun-if-changed=link.x"); diff --git a/tests/stm32/src/bin/cryp.rs b/tests/stm32/src/bin/cryp.rs new file mode 100644 index 000000000..f105abf26 --- /dev/null +++ b/tests/stm32/src/bin/cryp.rs @@ -0,0 +1,69 @@ +// required-features: cryp +#![no_std] +#![no_main] + +#[path = "../common.rs"] +mod common; + +use aes_gcm::aead::heapless::Vec; +use aes_gcm::aead::{AeadInPlace, KeyInit}; +use aes_gcm::Aes128Gcm; +use common::*; +use embassy_executor::Spawner; +use embassy_stm32::cryp::*; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p: embassy_stm32::Peripherals = embassy_stm32::init(config()); + + const PAYLOAD1: &[u8] = b"payload data 1 ;zdfhzdfhS;GKJASBDG;ASKDJBAL,zdfhzdfhzdfhzdfhvljhb,jhbjhb,sdhsdghsdhsfhsghzdfhzdfhzdfhzdfdhsdthsthsdhsgaadfhhgkdgfuoyguoft6783567"; + const PAYLOAD2: &[u8] = b"payload data 2 ;SKEzdfhzdfhzbhgvljhb,jhbjhb,sdhsdghsdhsfhsghshsfhshstsdthadfhsdfjhsfgjsfgjxfgjzdhgDFghSDGHjtfjtjszftjzsdtjhstdsdhsdhsdhsdhsdthsthsdhsgfh"; + const AAD1: &[u8] = b"additional data 1 stdargadrhaethaethjatjatjaetjartjstrjsfkk;'jopofyuisrteytweTASTUIKFUKIXTRDTEREharhaeryhaterjartjarthaethjrtjarthaetrhartjatejatrjsrtjartjyt1"; + const AAD2: &[u8] = b"additional data 2 stdhthsthsthsrthsrthsrtjdykjdukdyuldadfhsdghsdghsdghsadghjk'hioethjrtjarthaetrhartjatecfgjhzdfhgzdfhzdfghzdfhzdfhzfhjatrjsrtjartjytjfytjfyg"; + + let hw_cryp = Cryp::new(p.CRYP); + let key: [u8; 16] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; + let mut ciphertext: [u8; PAYLOAD1.len() + PAYLOAD2.len()] = [0; PAYLOAD1.len() + PAYLOAD2.len()]; + let mut plaintext: [u8; PAYLOAD1.len() + PAYLOAD2.len()] = [0; PAYLOAD1.len() + PAYLOAD2.len()]; + let iv: [u8; 12] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]; + + // Encrypt in hardware using AES-GCM 128-bit + let aes_gcm = AesGcm::new(&key, &iv); + let mut gcm_encrypt = hw_cryp.start(&aes_gcm, Direction::Encrypt); + hw_cryp.aad_blocking(&mut gcm_encrypt, AAD1, false); + hw_cryp.aad_blocking(&mut gcm_encrypt, AAD2, true); + hw_cryp.payload_blocking(&mut gcm_encrypt, PAYLOAD1, &mut ciphertext[..PAYLOAD1.len()], false); + hw_cryp.payload_blocking(&mut gcm_encrypt, PAYLOAD2, &mut ciphertext[PAYLOAD1.len()..], true); + let encrypt_tag = hw_cryp.finish_blocking(gcm_encrypt); + + // Decrypt in hardware using AES-GCM 128-bit + let mut gcm_decrypt = hw_cryp.start(&aes_gcm, Direction::Decrypt); + hw_cryp.aad_blocking(&mut gcm_decrypt, AAD1, false); + hw_cryp.aad_blocking(&mut gcm_decrypt, AAD2, true); + hw_cryp.payload_blocking(&mut gcm_decrypt, &ciphertext, &mut plaintext, true); + let decrypt_tag = hw_cryp.finish_blocking(gcm_decrypt); + + info!("AES-GCM Ciphertext: {:?}", ciphertext); + info!("AES-GCM Plaintext: {:?}", plaintext); + defmt::assert!(PAYLOAD1 == &plaintext[..PAYLOAD1.len()]); + defmt::assert!(PAYLOAD2 == &plaintext[PAYLOAD1.len()..]); + defmt::assert!(encrypt_tag == decrypt_tag); + + // Encrypt in software using AES-GCM 128-bit + let mut payload_vec: Vec<u8, { PAYLOAD1.len() + PAYLOAD2.len() + 16 }> = Vec::from_slice(&PAYLOAD1).unwrap(); + payload_vec.extend_from_slice(&PAYLOAD2).unwrap(); + let cipher = Aes128Gcm::new(&key.into()); + let mut aad: Vec<u8, { AAD1.len() + AAD2.len() }> = Vec::from_slice(&AAD1).unwrap(); + aad.extend_from_slice(&AAD2).unwrap(); + let _ = cipher.encrypt_in_place(&iv.into(), &aad, &mut payload_vec); + + defmt::assert!(ciphertext == payload_vec[0..ciphertext.len()]); + defmt::assert!(encrypt_tag == payload_vec[ciphertext.len()..ciphertext.len() + encrypt_tag.len()]); + + // Decrypt in software using AES-GCM 128-bit + let _ = cipher.decrypt_in_place(&iv.into(), &aad, &mut payload_vec); + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/tests/stm32/src/bin/fdcan.rs b/tests/stm32/src/bin/fdcan.rs new file mode 100644 index 000000000..dd78d7fb3 --- /dev/null +++ b/tests/stm32/src/bin/fdcan.rs @@ -0,0 +1,190 @@ +#![no_std] +#![no_main] + +// required-features: fdcan + +#[path = "../common.rs"] +mod common; +use common::*; +use defmt::assert; +use embassy_executor::Spawner; +use embassy_stm32::peripherals::*; +use embassy_stm32::{bind_interrupts, can, Config}; +use embassy_time::{Duration, Instant}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + FDCAN1_IT0 => can::IT0InterruptHandler<FDCAN1>; + FDCAN1_IT1 => can::IT1InterruptHandler<FDCAN1>; +}); + +struct TestOptions { + config: Config, + max_latency: Duration, + second_fifo_working: bool, +} + +#[cfg(any(feature = "stm32h755zi", feature = "stm32h753zi", feature = "stm32h563zi"))] +fn options() -> TestOptions { + use embassy_stm32::rcc; + info!("H75 config"); + let mut c = config(); + c.rcc.hse = Some(rcc::Hse { + freq: embassy_stm32::time::Hertz(25_000_000), + mode: rcc::HseMode::Oscillator, + }); + c.rcc.mux.fdcansel = rcc::mux::Fdcansel::HSE; + TestOptions { + config: c, + max_latency: Duration::from_micros(1200), + second_fifo_working: false, + } +} + +#[cfg(any(feature = "stm32h7a3zi"))] +fn options() -> TestOptions { + use embassy_stm32::rcc; + info!("H7a config"); + let mut c = config(); + c.rcc.hse = Some(rcc::Hse { + freq: embassy_stm32::time::Hertz(25_000_000), + mode: rcc::HseMode::Oscillator, + }); + c.rcc.mux.fdcansel = rcc::mux::Fdcansel::HSE; + TestOptions { + config: c, + max_latency: Duration::from_micros(1200), + second_fifo_working: false, + } +} + +#[cfg(any(feature = "stm32g491re", feature = "stm32g431cb"))] +fn options() -> TestOptions { + info!("G4 config"); + TestOptions { + config: config(), + max_latency: Duration::from_micros(500), + second_fifo_working: true, + } +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + //let peripherals = embassy_stm32::init(config()); + + let options = options(); + let peripherals = embassy_stm32::init(options.config); + + let mut can = can::FdcanConfigurator::new(peripherals.FDCAN1, peripherals.PB8, peripherals.PB9, Irqs); + + // 250k bps + can.set_bitrate(250_000); + + can.set_extended_filter( + can::filter::ExtendedFilterSlot::_0, + can::filter::ExtendedFilter::accept_all_into_fifo1(), + ); + + let mut can = can.into_internal_loopback_mode(); + + info!("CAN Configured"); + + let mut i: u8 = 0; + loop { + let tx_frame = can::frame::ClassicFrame::new_standard(0x123, &[i; 1]).unwrap(); + + info!("Transmitting frame..."); + let tx_ts = Instant::now(); + can.write(&tx_frame).await; + + let (frame, timestamp) = can.read().await.unwrap(); + info!("Frame received!"); + + // Check data. + assert!(i == frame.data()[0], "{} == {}", i, frame.data()[0]); + + info!("loopback time {}", timestamp); + info!("loopback frame {=u8}", frame.data()[0]); + let latency = timestamp.saturating_duration_since(tx_ts); + info!("loopback latency {} us", latency.as_micros()); + + // Theoretical minimum latency is 55us, actual is usually ~80us + const MIN_LATENCY: Duration = Duration::from_micros(50); + // Was failing at 150 but we are not getting a real time stamp. I'm not + // sure if there are other delays + assert!( + MIN_LATENCY <= latency && latency <= options.max_latency, + "{} <= {} <= {}", + MIN_LATENCY, + latency, + options.max_latency + ); + + i += 1; + if i > 10 { + break; + } + } + + let max_buffered = if options.second_fifo_working { 6 } else { 3 }; + + // Below here, check that we can receive from both FIFO0 and FIFO0 + // Above we configured FIFO1 for extended ID packets. There are only 3 slots + // in each FIFO so make sure we write enough to fill them both up before reading. + for i in 0..3 { + // Try filling up the RX FIFO0 buffers with standard packets + let tx_frame = can::frame::ClassicFrame::new_standard(0x123, &[i; 1]).unwrap(); + info!("Transmitting frame {}", i); + can.write(&tx_frame).await; + } + for i in 3..max_buffered { + // Try filling up the RX FIFO0 buffers with extended packets + let tx_frame = can::frame::ClassicFrame::new_extended(0x1232344, &[i; 1]).unwrap(); + info!("Transmitting frame {}", i); + can.write(&tx_frame).await; + } + + // Try and receive all 6 packets + for i in 0..max_buffered { + let (frame, _ts) = can.read().await.unwrap(); + match frame.id() { + embedded_can::Id::Extended(id) => { + info!("Extended received! {:x} {} {}", id.as_raw(), frame.data()[0], i); + } + embedded_can::Id::Standard(id) => { + info!("Standard received! {:x} {} {}", id.as_raw(), frame.data()[0], i); + } + } + } + + // Test again with a split + let (mut tx, mut rx) = can.split(); + for i in 0..3 { + // Try filling up the RX FIFO0 buffers with standard packets + let tx_frame = can::frame::ClassicFrame::new_standard(0x123, &[i; 1]).unwrap(); + info!("Transmitting frame {}", i); + tx.write(&tx_frame).await; + } + for i in 3..max_buffered { + // Try filling up the RX FIFO0 buffers with extended packets + let tx_frame = can::frame::ClassicFrame::new_extended(0x1232344, &[i; 1]).unwrap(); + info!("Transmitting frame {}", i); + tx.write(&tx_frame).await; + } + + // Try and receive all 6 packets + for i in 0..max_buffered { + let (frame, _ts) = rx.read().await.unwrap(); + match frame.id() { + embedded_can::Id::Extended(id) => { + info!("Extended received! {:x} {} {}", id.as_raw(), frame.data()[0], i); + } + embedded_can::Id::Standard(id) => { + info!("Standard received! {:x} {} {}", id.as_raw(), frame.data()[0], i); + } + } + } + + info!("Test OK"); + cortex_m::asm::bkpt(); +} diff --git a/tests/stm32/src/bin/hash.rs b/tests/stm32/src/bin/hash.rs new file mode 100644 index 000000000..8cc5d593f --- /dev/null +++ b/tests/stm32/src/bin/hash.rs @@ -0,0 +1,101 @@ +// required-features: hash +#![no_std] +#![no_main] + +#[path = "../common.rs"] +mod common; +use common::*; +use embassy_executor::Spawner; +use embassy_stm32::dma::NoDma; +use embassy_stm32::hash::*; +use embassy_stm32::{bind_interrupts, hash, peripherals}; +use hmac::{Hmac, Mac}; +use sha2::{Digest, Sha224, Sha256}; +use {defmt_rtt as _, panic_probe as _}; + +type HmacSha256 = Hmac<Sha256>; + +#[cfg(any(feature = "stm32l4a6zg", feature = "stm32h755zi", feature = "stm32h753zi"))] +bind_interrupts!(struct Irqs { + HASH_RNG => hash::InterruptHandler<peripherals::HASH>; +}); + +#[cfg(any( + feature = "stm32wba52cg", + feature = "stm32l552ze", + feature = "stm32h563zi", + feature = "stm32h503rb", + feature = "stm32u5a5zj", + feature = "stm32u585ai" +))] +bind_interrupts!(struct Irqs { + HASH => hash::InterruptHandler<peripherals::HASH>; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p: embassy_stm32::Peripherals = embassy_stm32::init(config()); + let mut hw_hasher = Hash::new(p.HASH, NoDma, Irqs); + + let test_1: &[u8] = b"as;dfhaslfhas;oifvnasd;nifvnhasd;nifvhndlkfghsd;nvfnahssdfgsdafgsasdfasdfasdfasdfasdfghjklmnbvcalskdjghalskdjgfbaslkdjfgbalskdjgbalskdjbdfhsdfhsfghsfghfgh"; + let test_2: &[u8] = b"fdhalksdjfhlasdjkfhalskdjfhgal;skdjfgalskdhfjgalskdjfglafgadfgdfgdafgaadsfgfgdfgadrgsyfthxfgjfhklhjkfgukhulkvhlvhukgfhfsrghzdhxyfufynufyuszeradrtydyytserr"; + let test_3: &[u8] = b"a.ewtkluGWEBR.KAJRBTA,RMNRBG,FDMGB.kger.tkasjrbt.akrjtba.krjtba.ktmyna,nmbvtyliasd;gdrtba,sfvs.kgjzshd.gkbsr.tksejb.SDkfBSE.gkfgb>ESkfbSE>gkJSBESE>kbSE>fk"; + + // Start an SHA-256 digest. + let mut sha256context = hw_hasher.start(Algorithm::SHA256, DataType::Width8, None); + hw_hasher.update_blocking(&mut sha256context, test_1); + + // Interrupt the SHA-256 digest to compute an SHA-224 digest. + let mut sha224context = hw_hasher.start(Algorithm::SHA224, DataType::Width8, None); + hw_hasher.update_blocking(&mut sha224context, test_3); + let mut sha224_digest_buffer: [u8; 28] = [0; 28]; + let _ = hw_hasher.finish_blocking(sha224context, &mut sha224_digest_buffer); + + // Finish the SHA-256 digest. + hw_hasher.update_blocking(&mut sha256context, test_2); + let mut sha256_digest_buffer: [u8; 32] = [0; 32]; + let _ = hw_hasher.finish_blocking(sha256context, &mut sha256_digest_buffer); + + // Compute the SHA-256 digest in software. + let mut sw_sha256_hasher = Sha256::new(); + sw_sha256_hasher.update(test_1); + sw_sha256_hasher.update(test_2); + let sw_sha256_digest = sw_sha256_hasher.finalize(); + + //Compute the SHA-224 digest in software. + let mut sw_sha224_hasher = Sha224::new(); + sw_sha224_hasher.update(test_3); + let sw_sha224_digest = sw_sha224_hasher.finalize(); + + // Compare the SHA-256 digests. + info!("Hardware SHA-256 Digest: {:?}", sha256_digest_buffer); + info!("Software SHA-256 Digest: {:?}", sw_sha256_digest[..]); + defmt::assert!(sha256_digest_buffer == sw_sha256_digest[..]); + + // Compare the SHA-224 digests. + info!("Hardware SHA-256 Digest: {:?}", sha224_digest_buffer); + info!("Software SHA-256 Digest: {:?}", sw_sha224_digest[..]); + defmt::assert!(sha224_digest_buffer == sw_sha224_digest[..]); + + let hmac_key: [u8; 64] = [0x55; 64]; + + // Compute HMAC in hardware. + let mut sha256hmac_context = hw_hasher.start(Algorithm::SHA256, DataType::Width8, Some(&hmac_key)); + hw_hasher.update_blocking(&mut sha256hmac_context, test_1); + hw_hasher.update_blocking(&mut sha256hmac_context, test_2); + let mut hw_hmac: [u8; 32] = [0; 32]; + hw_hasher.finish_blocking(sha256hmac_context, &mut hw_hmac); + + // Compute HMAC in software. + let mut sw_mac = HmacSha256::new_from_slice(&hmac_key).unwrap(); + sw_mac.update(test_1); + sw_mac.update(test_2); + let sw_hmac = sw_mac.finalize().into_bytes(); + + info!("Hardware HMAC: {:?}", hw_hmac); + info!("Software HMAC: {:?}", sw_hmac[..]); + defmt::assert!(hw_hmac == sw_hmac[..]); + + 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 index f5d618db4..0c110421d 100644 --- a/tests/stm32/src/bin/usart_rx_ringbuffered.rs +++ b/tests/stm32/src/bin/usart_rx_ringbuffered.rs @@ -74,7 +74,7 @@ async fn transmit_task(mut tx: UartTx<'static, peris::UART, peris::UART_TX_DMA>) } #[embassy_executor::task] -async fn receive_task(mut rx: RingBufferedUartRx<'static, peris::UART, peris::UART_RX_DMA>) { +async fn receive_task(mut rx: RingBufferedUartRx<'static, peris::UART>) { info!("Ready to receive..."); let mut rng = ChaCha8Rng::seed_from_u64(1337); diff --git a/tests/stm32/src/common.rs b/tests/stm32/src/common.rs index 313380b35..3297ea7e2 100644 --- a/tests/stm32/src/common.rs +++ b/tests/stm32/src/common.rs @@ -2,6 +2,8 @@ pub use defmt::*; #[allow(unused)] +use embassy_stm32::rcc::*; +#[allow(unused)] use embassy_stm32::time::Hertz; use embassy_stm32::Config; use {defmt_rtt as _, panic_probe as _}; @@ -54,6 +56,10 @@ teleprobe_meta::target!(b"nucleo-stm32l496zg"); teleprobe_meta::target!(b"nucleo-stm32wl55jc"); #[cfg(feature = "stm32wba52cg")] teleprobe_meta::target!(b"nucleo-stm32wba52cg"); +#[cfg(feature = "stm32f091rc")] +teleprobe_meta::target!(b"nucleo-stm32f091rc"); +#[cfg(feature = "stm32h503rb")] +teleprobe_meta::target!(b"nucleo-stm32h503rb"); macro_rules! define_peris { ($($name:ident = $peri:ident,)* $(@irq $irq_name:ident = $irq_code:tt,)*) => { @@ -85,6 +91,12 @@ macro_rules! define_peris { }; } +#[cfg(feature = "stm32f091rc")] +define_peris!( + UART = USART1, UART_TX = PA9, UART_RX = PA10, UART_TX_DMA = DMA1_CH4, UART_RX_DMA = DMA1_CH5, + SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PA7, SPI_MISO = PA6, SPI_TX_DMA = DMA1_CH3, SPI_RX_DMA = DMA1_CH2, + @irq UART = {USART1 => embassy_stm32::usart::InterruptHandler<embassy_stm32::peripherals::USART1>;}, +); #[cfg(feature = "stm32f103c8")] define_peris!( UART = USART1, UART_TX = PA9, UART_RX = PA10, UART_TX_DMA = DMA1_CH4, UART_RX_DMA = DMA1_CH5, @@ -157,6 +169,12 @@ define_peris!( SPI = SPI4, SPI_SCK = PE12, SPI_MOSI = PE14, SPI_MISO = PE13, SPI_TX_DMA = GPDMA1_CH0, SPI_RX_DMA = GPDMA1_CH1, @irq UART = {LPUART1 => embassy_stm32::usart::InterruptHandler<embassy_stm32::peripherals::LPUART1>;}, ); +#[cfg(feature = "stm32h503rb")] +define_peris!( + UART = USART1, UART_TX = PB14, UART_RX = PB15, UART_TX_DMA = GPDMA1_CH0, UART_RX_DMA = GPDMA1_CH1, + SPI = SPI1, SPI_SCK = PA5, SPI_MOSI = PA7, SPI_MISO = PA6, SPI_TX_DMA = GPDMA1_CH0, SPI_RX_DMA = GPDMA1_CH1, + @irq UART = {USART1 => embassy_stm32::usart::InterruptHandler<embassy_stm32::peripherals::USART1>;}, +); #[cfg(feature = "stm32c031c6")] define_peris!( UART = USART1, UART_TX = PB6, UART_RX = PB7, UART_TX_DMA = DMA1_CH1, UART_RX_DMA = DMA1_CH2, @@ -242,14 +260,68 @@ pub fn config() -> Config { #[allow(unused_mut)] let mut config = Config::default(); + #[cfg(feature = "stm32c031c6")] + { + config.rcc.hsi = Some(Hsi { + sys_div: HsiSysDiv::DIV1, // 48Mhz + ker_div: HsiKerDiv::DIV3, // 16Mhz + }); + config.rcc.sys = Sysclk::HSISYS; + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV1; + } + + #[cfg(feature = "stm32g071rb")] + { + config.rcc.hsi = true; + config.rcc.pll = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL16, + divp: None, + divq: None, + divr: Some(PllRDiv::DIV4), // 16 / 1 * 16 / 4 = 64 Mhz + }); + config.rcc.sys = Sysclk::PLL1_R; + } #[cfg(feature = "stm32wb55rg")] { config.rcc = embassy_stm32::rcc::WPAN_DEFAULT; } + #[cfg(feature = "stm32f091rc")] + { + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::Bypass, + }); + config.rcc.pll = Some(Pll { + src: PllSource::HSE, + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL6, + }); + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV1; + } + #[cfg(feature = "stm32f103c8")] + { + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::Oscillator, + }); + config.rcc.pll = Some(Pll { + src: PllSource::HSE, + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL9, + }); + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV2; + config.rcc.apb2_pre = APBPrescaler::DIV1; + } #[cfg(feature = "stm32f207zg")] { - use embassy_stm32::rcc::*; // By default, HSE on the board comes from a 8 MHz clock signal (not a crystal) config.rcc.hse = Some(Hse { freq: Hertz(8_000_000), @@ -276,9 +348,25 @@ pub fn config() -> Config { config.rcc.apb2_pre = APBPrescaler::DIV2; } + #[cfg(feature = "stm32f303ze")] + { + config.rcc.hse = Some(Hse { + freq: Hertz(8_000_000), + mode: HseMode::Bypass, + }); + config.rcc.pll = Some(Pll { + src: PllSource::HSE, + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL9, + }); + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV2; + config.rcc.apb2_pre = APBPrescaler::DIV1; + } + #[cfg(feature = "stm32f429zi")] { - use embassy_stm32::rcc::*; config.rcc.hse = Some(Hse { freq: Hertz(8_000_000), mode: HseMode::Bypass, @@ -299,7 +387,6 @@ pub fn config() -> Config { #[cfg(feature = "stm32f446re")] { - use embassy_stm32::rcc::*; config.rcc.hse = Some(Hse { freq: Hertz(8_000_000), mode: HseMode::Oscillator, @@ -320,7 +407,6 @@ pub fn config() -> Config { #[cfg(feature = "stm32f767zi")] { - use embassy_stm32::rcc::*; config.rcc.hse = Some(Hse { freq: Hertz(8_000_000), mode: HseMode::Bypass, @@ -341,7 +427,6 @@ pub fn config() -> Config { #[cfg(feature = "stm32h563zi")] { - use embassy_stm32::rcc::*; config.rcc.hsi = None; config.rcc.hsi48 = Some(Default::default()); // needed for RNG config.rcc.hse = Some(Hse { @@ -364,9 +449,50 @@ pub fn config() -> Config { config.rcc.voltage_scale = VoltageScale::Scale0; } + #[cfg(feature = "stm32h503rb")] + { + config.rcc.hsi = None; + config.rcc.hsi48 = Some(Default::default()); // needed for RNG + config.rcc.hse = Some(Hse { + freq: Hertz(24_000_000), + mode: HseMode::Oscillator, + }); + config.rcc.pll1 = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV6, + mul: PllMul::MUL125, + divp: Some(PllDiv::DIV2), + divq: Some(PllDiv::DIV2), + divr: None, + }); + config.rcc.ahb_pre = AHBPrescaler::DIV1; + config.rcc.apb1_pre = APBPrescaler::DIV1; + config.rcc.apb2_pre = APBPrescaler::DIV1; + config.rcc.apb3_pre = APBPrescaler::DIV1; + config.rcc.sys = Sysclk::PLL1_P; + config.rcc.voltage_scale = VoltageScale::Scale0; + } + + #[cfg(feature = "stm32g491re")] + { + config.rcc.hse = Some(Hse { + freq: Hertz(24_000_000), + mode: HseMode::Oscillator, + }); + config.rcc.pll = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV6, + mul: PllMul::MUL85, + divp: None, + divq: Some(PllQDiv::DIV8), // 42.5 Mhz for fdcan. + divr: Some(PllRDiv::DIV2), // Main system clock at 170 MHz + }); + config.rcc.mux.fdcansel = mux::Fdcansel::PLL1_Q; + config.rcc.sys = Sysclk::PLL1_R; + } + #[cfg(any(feature = "stm32h755zi", feature = "stm32h753zi"))] { - use embassy_stm32::rcc::*; config.rcc.hsi = Some(HSIPrescaler::DIV1); config.rcc.csi = true; config.rcc.hsi48 = Some(Default::default()); // needed for RNG @@ -393,12 +519,15 @@ pub fn config() -> Config { config.rcc.apb3_pre = APBPrescaler::DIV2; // 100 Mhz config.rcc.apb4_pre = APBPrescaler::DIV2; // 100 Mhz config.rcc.voltage_scale = VoltageScale::Scale1; - config.rcc.adc_clock_source = AdcClockSource::PLL2_P; + config.rcc.mux.adcsel = mux::Adcsel::PLL2_P; + #[cfg(any(feature = "stm32h755zi"))] + { + config.rcc.supply_config = SupplyConfig::DirectSMPS; + } } #[cfg(any(feature = "stm32h7a3zi"))] { - use embassy_stm32::rcc::*; config.rcc.hsi = Some(HSIPrescaler::DIV1); config.rcc.csi = true; config.rcc.hsi48 = Some(Default::default()); // needed for RNG @@ -425,13 +554,12 @@ pub fn config() -> Config { config.rcc.apb3_pre = APBPrescaler::DIV2; // 140 Mhz config.rcc.apb4_pre = APBPrescaler::DIV2; // 140 Mhz config.rcc.voltage_scale = VoltageScale::Scale0; - config.rcc.adc_clock_source = AdcClockSource::PLL2_P; + config.rcc.mux.adcsel = mux::Adcsel::PLL2_P; } #[cfg(any(feature = "stm32l496zg", feature = "stm32l4a6zg", feature = "stm32l4r5zi"))] { - use embassy_stm32::rcc::*; - config.rcc.mux = ClockSrc::PLL1_R; + config.rcc.sys = Sysclk::PLL1_R; config.rcc.hsi = true; config.rcc.pll = Some(Pll { source: PllSource::HSI, @@ -445,13 +573,12 @@ pub fn config() -> Config { #[cfg(feature = "stm32wl55jc")] { - use embassy_stm32::rcc::*; config.rcc.hse = Some(Hse { freq: Hertz(32_000_000), mode: HseMode::Bypass, prescaler: HsePrescaler::DIV1, }); - config.rcc.mux = ClockSrc::PLL1_R; + config.rcc.sys = Sysclk::PLL1_R; config.rcc.pll = Some(Pll { source: PllSource::HSE, prediv: PllPreDiv::DIV2, @@ -464,9 +591,8 @@ pub fn config() -> Config { #[cfg(any(feature = "stm32l552ze"))] { - use embassy_stm32::rcc::*; config.rcc.hsi = true; - config.rcc.mux = ClockSrc::PLL1_R; + config.rcc.sys = Sysclk::PLL1_R; config.rcc.pll = Some(Pll { // 110Mhz clock (16 / 4 * 55 / 2) source: PllSource::HSI, @@ -480,42 +606,46 @@ pub fn config() -> Config { #[cfg(any(feature = "stm32u585ai", feature = "stm32u5a5zj"))] { - use embassy_stm32::rcc::*; - config.rcc.mux = ClockSrc::MSI(Msirange::RANGE_48MHZ); + config.rcc.hsi = true; + config.rcc.pll1 = Some(Pll { + source: PllSource::HSI, // 16 MHz + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL10, + divp: None, + divq: None, + divr: Some(PllDiv::DIV1), // 160 MHz + }); + config.rcc.sys = Sysclk::PLL1_R; + config.rcc.voltage_range = VoltageScale::RANGE1; + config.rcc.hsi48 = Some(Hsi48Config { sync_from_usb: true }); // needed for USB } #[cfg(feature = "stm32wba52cg")] { - use embassy_stm32::rcc::*; - config.rcc.mux = ClockSrc::HSI; - - embassy_stm32::pac::RCC.ccipr2().write(|w| { - w.set_rngsel(embassy_stm32::pac::rcc::vals::Rngsel::HSI); - }); + config.rcc.sys = Sysclk::HSI; + config.rcc.mux.rngsel = mux::Rngsel::HSI; } #[cfg(feature = "stm32l073rz")] { - use embassy_stm32::rcc::*; config.rcc.hsi = true; config.rcc.pll = Some(Pll { source: PllSource::HSI, mul: PllMul::MUL4, div: PllDiv::DIV2, // 32Mhz clock (16 * 4 / 2) }); - config.rcc.mux = ClockSrc::PLL1_R; + config.rcc.sys = Sysclk::PLL1_R; } #[cfg(any(feature = "stm32l152re"))] { - use embassy_stm32::rcc::*; config.rcc.hsi = true; config.rcc.pll = Some(Pll { source: PllSource::HSI, mul: PllMul::MUL4, div: PllDiv::DIV2, // 32Mhz clock (16 * 4 / 2) }); - config.rcc.mux = ClockSrc::PLL1_R; + config.rcc.sys = Sysclk::PLL1_R; } config diff --git a/tests/stm32/teleprobe.sh b/tests/stm32/teleprobe.sh deleted file mode 100755 index 6eec6ca93..000000000 --- a/tests/stm32/teleprobe.sh +++ /dev/null @@ -1,12 +0,0 @@ -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